Add a TextInput component that supports multiline text editing.

This commit is contained in:
IntegratedQuantum 2023-03-07 20:53:57 +01:00
parent b12f004d41
commit c2e888031f
8 changed files with 601 additions and 27 deletions

View File

@ -97,7 +97,7 @@ RayMarchResult rayMarching(vec3 startPosition, vec3 direction) { // TODO: Mipmap
vec3 stepInIndex = step*vec3(1 << 10, 1 << 5, 1);
int overflowMask = 1<<14 | 1<<9 | 1<<4;
vec3 t1 = (floor(startPosition) - startPosition)/direction;
vec3 tDelta = 1/(direction);
vec3 tDelta = 1/direction;
vec3 t2 = t1 + tDelta;
tDelta = abs(tDelta);
vec3 tMax = max(t1, t2) - tDelta;

View File

@ -246,7 +246,7 @@ void mainItemDrop() {
// Implementation of "A Fast Voxel Traversal Algorithm for Ray Tracing" http://www.cse.yorku.ca/~amana/research/grid.pdf
ivec3 step = ivec3(sign(direction));
vec3 t1 = (floor(startPosition) - startPosition)/direction;
vec3 tDelta = 1/(direction);
vec3 tDelta = 1/direction;
vec3 t2 = t1 + tDelta;
tDelta = abs(tDelta);
vec3 tMax = max(t1, t2);

View File

@ -402,6 +402,7 @@ pub const TextBuffer = struct {
codepoint: u32,
cluster: u32,
fontEffect: FontEffect,
characterIndex: u32,
};
buffer: harfbuzz.Buffer,
@ -445,63 +446,71 @@ pub const TextBuffer = struct {
currentFontEffect: FontEffect,
parsedText: std.ArrayList(u32),
fontEffects: std.ArrayList(FontEffect),
characterIndex: std.ArrayList(u32),
showControlCharacters: bool,
curChar: u21 = undefined,
curIndex: u32 = 0,
fn appendControlGetNext(self: *Parser, char: u32) !?u21 {
fn appendControlGetNext(self: *Parser) !?void {
if(self.showControlCharacters) {
try self.fontEffects.append(.{.color = 0x808080});
try self.parsedText.append(char);
try self.parsedText.append(self.curChar);
try self.characterIndex.append(self.curIndex);
}
return self.unicodeIterator.nextCodepoint();
self.curIndex = @intCast(u32, self.unicodeIterator.i);
self.curChar = self.unicodeIterator.nextCodepoint() orelse return null;
}
fn appendGetNext(self: *Parser, char: u32) !?u21 {
fn appendGetNext(self: *Parser) !?void {
try self.fontEffects.append(self.currentFontEffect);
try self.parsedText.append(char);
return self.unicodeIterator.nextCodepoint();
try self.parsedText.append(self.curChar);
try self.characterIndex.append(self.curIndex);
self.curIndex = @intCast(u32, self.unicodeIterator.i);
self.curChar = self.unicodeIterator.nextCodepoint() orelse return null;
}
fn parse(self: *Parser) !void {
var char = self.unicodeIterator.nextCodepoint() orelse return;
while(true) switch(char) {
self.curIndex = @intCast(u32, self.unicodeIterator.i);
self.curChar = self.unicodeIterator.nextCodepoint() orelse return;
while(true) switch(self.curChar) {
'*' => {
char = try self.appendControlGetNext(char) orelse return;
if(char == '*') {
char = try self.appendControlGetNext(char) orelse return;
try self.appendControlGetNext() orelse return;
if(self.curChar == '*') {
try self.appendControlGetNext() orelse return;
self.currentFontEffect.bold = !self.currentFontEffect.bold;
} else {
self.currentFontEffect.italic = !self.currentFontEffect.italic;
}
},
'_' => {
char = try self.appendControlGetNext(char) orelse return;
if(char == '_') {
char = try self.appendControlGetNext(char) orelse return;
try self.appendControlGetNext() orelse return;
if(self.curChar == '_') {
try self.appendControlGetNext() orelse return;
self.currentFontEffect.strikethrough = !self.currentFontEffect.strikethrough;
} else {
self.currentFontEffect.underline = !self.currentFontEffect.underline;
}
},
'\\' => {
char = try self.appendControlGetNext(char) orelse return;
char = try self.appendGetNext(char) orelse return;
try self.appendControlGetNext() orelse return;
try self.appendGetNext() orelse return;
},
'#' => {
char = try self.appendControlGetNext(char) orelse return;
try self.appendControlGetNext() orelse return;
var shift: u5 = 20;
while(true) : (shift -= 4) {
self.currentFontEffect.color = (self.currentFontEffect.color & ~(@as(u24, 0xf) << shift)) | @as(u24, switch(char) {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9' => char - '0',
'a', 'b', 'c', 'd', 'e', 'f' => char - 'a' + 10,
'A', 'B', 'C', 'D', 'E', 'F' => char - 'A' + 10,
self.currentFontEffect.color = (self.currentFontEffect.color & ~(@as(u24, 0xf) << shift)) | @as(u24, switch(self.curChar) {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9' => self.curChar - '0',
'a', 'b', 'c', 'd', 'e', 'f' => self.curChar - 'a' + 10,
'A', 'B', 'C', 'D', 'E', 'F' => self.curChar - 'A' + 10,
else => 0,
}) << shift;
char = try self.appendControlGetNext(char) orelse return;
try self.appendControlGetNext() orelse return;
if(shift == 0) break;
}
},
else => {
char = try self.appendGetNext(char) orelse return;
try self.appendGetNext() orelse return;
}
};
}
@ -515,10 +524,12 @@ pub const TextBuffer = struct {
.currentFontEffect = initialFontEffect,
.parsedText = std.ArrayList(u32).init(main.threadAllocator),
.fontEffects = std.ArrayList(FontEffect).init(allocator),
.characterIndex = std.ArrayList(u32).init(allocator),
.showControlCharacters = showControlCharacters
};
defer parser.fontEffects.deinit();
defer parser.parsedText.deinit();
defer parser.characterIndex.deinit();
self.lines = std.ArrayList(Line).init(allocator);
self.lineBreakIndices = std.ArrayList(u32).init(allocator);
try parser.parse();
@ -565,6 +576,7 @@ pub const TextBuffer = struct {
glyph.codepoint = glyphInfos[i].codepoint;
glyph.cluster = glyphInfos[i].cluster;
glyph.fontEffect = parser.fontEffects.items[textIndexGuess[i]];
glyph.characterIndex = parser.characterIndex.items[textIndexGuess[i]];
}
// Find the lines:
@ -581,8 +593,46 @@ pub const TextBuffer = struct {
self.lineBreakIndices.deinit();
}
pub fn mousePosToIndex(self: TextBuffer, mousePos: Vec2f, bufferLen: usize) u32 {
var line: usize = @floatToInt(usize, @max(0, mousePos[1]/16.0));
line = @min(line, self.lineBreakIndices.items.len - 2);
var x: f32 = 0;
const start = self.lineBreakIndices.items[line];
const end = self.lineBreakIndices.items[line + 1];
for(self.glyphs[start..end]) |glyph| {
if(mousePos[0] < x + glyph.x_advance/2) {
return @intCast(u32, glyph.characterIndex);
}
x += glyph.x_advance;
}
return @intCast(u32, if(end < self.glyphs.len) self.glyphs[end-1].characterIndex else bufferLen);
}
pub fn indexToCursorPos(self: TextBuffer, index: u32) Vec2f {
var x: f32 = 0;
var y: f32 = 0;
var i: usize = 0;
while(true) {
for(self.glyphs[self.lineBreakIndices.items[i]..self.lineBreakIndices.items[i+1]]) |glyph| {
if(glyph.characterIndex == index) {
return .{x, y};
}
x += glyph.x_advance;
y -= glyph.y_advance;
}
i += 1;
if(i >= self.lineBreakIndices.items.len - 1) {
return .{x, y};
}
x = 0;
y += 16;
}
}
/// Returns the calculated dimensions of the text block.
pub fn calculateLineBreaks(self: *TextBuffer, fontSize: f32, maxLineWidth: f32) !Vec2f {
pub fn calculateLineBreaks(self: *TextBuffer, fontSize: f32, maxLineWidth: f32) !Vec2f { // TODO: Support newlines.
self.lineBreakIndices.clearRetainingCapacity();
try self.lineBreakIndices.append(0);
var scaledMaxWidth = maxLineWidth/fontSize*16.0;
@ -609,6 +659,37 @@ pub const TextBuffer = struct {
return Vec2f{totalWidth*fontSize/16.0, @intToFloat(f32, self.lineBreakIndices.items.len - 1)*fontSize};
}
pub fn drawSelection(self: TextBuffer, pos: Vec2f, selectionStart: u32, selectionEnd: u32) !void {
std.debug.assert(selectionStart <= selectionEnd);
var x: f32 = 0;
var y: f32 = 0;
var i: usize = 0;
var j: usize = 0;
// Find the start row:
outer: while(i < self.lineBreakIndices.items.len - 1) : (i += 1) {
while(j < self.lineBreakIndices.items[i+1]) : (j += 1) {
const glyph = self.glyphs[j];
if(glyph.characterIndex >= selectionStart) break :outer;
x += glyph.x_advance;
y -= glyph.y_advance;
}
x = 0;
y += 16;
}
while(i < self.lineBreakIndices.items.len - 1) : (i += 1) {
const startX = x;
while(j < self.lineBreakIndices.items[i+1] and j < selectionEnd) : (j += 1) {
const glyph = self.glyphs[j];
if(glyph.characterIndex >= selectionEnd) break;
x += glyph.x_advance;
y -= glyph.y_advance;
}
draw.rect(pos + Vec2f{startX, y}, .{x - startX, 16});
x = 0;
y += 16;
}
}
pub fn render(self: TextBuffer, _x: f32, _y: f32, _fontSize: f32) !void {
var x = _x;
var y = _y;

View File

@ -8,6 +8,7 @@ pub const Button = @import("components/Button.zig");
pub const CheckBox = @import("components/CheckBox.zig");
pub const Label = @import("components/Label.zig");
pub const Slider = @import("components/Slider.zig");
pub const TextInput = @import("components/TextInput.zig");
pub const VerticalList = @import("components/VerticalList.zig");
const GuiComponent = @This();
@ -21,6 +22,7 @@ const Impl = union(enum) {
checkBox: CheckBox,
label: Label,
slider: Slider,
textInput: TextInput,
verticalList: VerticalList,
};

View File

@ -0,0 +1,362 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const main = @import("root");
const graphics = main.graphics;
const draw = graphics.draw;
const TextBuffer = graphics.TextBuffer;
const vec = main.vec;
const Vec2f = vec.Vec2f;
const gui = @import("../gui.zig");
const GuiComponent = gui.GuiComponent;
const TextInput = @This();
const fontSize: f32 = 16;
pressed: bool = false,
cursor: ?u32 = null,
selectionStart: ?u32 = null,
currentString: std.ArrayList(u8),
textBuffer: TextBuffer,
maxWidth: f32,
textSize: Vec2f = undefined,
// TODO: Make this scrollable.
pub fn init(allocator: Allocator, pos: Vec2f, maxWidth: f32, text: []const u8) Allocator.Error!GuiComponent {
var self = TextInput {
.currentString = std.ArrayList(u8).init(allocator),
.textBuffer = try TextBuffer.init(allocator, text, .{}, true),
.maxWidth = maxWidth,
};
try self.currentString.appendSlice(text);
self.textSize = try self.textBuffer.calculateLineBreaks(fontSize, maxWidth);
return GuiComponent {
.pos = pos,
.size = self.textSize,
.impl = .{.textInput = self}
};
}
pub fn deinit(self: TextInput) void {
self.textBuffer.deinit();
self.currentString.deinit();
}
pub fn mainButtonPressed(self: *TextInput, pos: Vec2f, _: Vec2f, mousePosition: Vec2f) void {
self.cursor = null;
self.selectionStart = self.textBuffer.mousePosToIndex(mousePosition - pos, self.currentString.items.len);
self.pressed = true;
}
pub fn mainButtonReleased(self: *TextInput, pos: Vec2f, _: Vec2f, mousePosition: Vec2f) void {
if(self.pressed) {
self.cursor = self.textBuffer.mousePosToIndex(mousePosition - pos, self.currentString.items.len);
if(self.cursor == self.selectionStart) {
self.selectionStart = null;
}
self.pressed = false;
gui.setSelectedTextInput(self);
}
}
pub fn deselect(self: *TextInput) void {
self.cursor = null;
self.selectionStart = null;
}
fn reloadText(self: *TextInput) !void {
self.textBuffer.deinit();
self.textBuffer = try TextBuffer.init(self.currentString.allocator, self.currentString.items, .{}, true);
self.textSize = try self.textBuffer.calculateLineBreaks(fontSize, self.maxWidth);
}
fn moveCursorLeft(self: *TextInput, mods: main.Key.Modifiers) void {
if(mods.control) {
const text = self.currentString.items;
if(self.cursor.? == 0) return;
self.cursor.? -= 1;
// Find end of previous "word":
while(!std.ascii.isAlphabetic(text[self.cursor.?]) and std.ascii.isASCII(text[self.cursor.?])) {
if(self.cursor.? == 0) return;
self.cursor.? -= 1;
}
// Find the start of the previous "word":
while(std.ascii.isAlphabetic(text[self.cursor.?]) or !std.ascii.isASCII(text[self.cursor.?])) {
if(self.cursor.? == 0) return;
self.cursor.? -= 1;
}
self.cursor.? += 1;
} else {
while(self.cursor.? > 0) {
self.cursor.? -= 1;
if((std.unicode.utf8ByteSequenceLength(self.currentString.items[self.cursor.?]) catch 0) != 0) break; // Ugly hack to check if we found a valid start byte.
}
}
}
pub fn left(self: *TextInput, mods: main.Key.Modifiers) void {
if(self.cursor) |*cursor| {
if(mods.shift) {
if(self.selectionStart == null) {
self.selectionStart = cursor.*;
}
self.moveCursorLeft(mods);
if(self.selectionStart == self.cursor) {
self.selectionStart = null;
}
} else {
if(self.selectionStart) |selectionStart| {
cursor.* = @min(cursor.*, selectionStart);
self.selectionStart = null;
} else {
self.moveCursorLeft(mods);
}
}
}
}
fn moveCursorRight(self: *TextInput, mods: main.Key.Modifiers) void {
if(self.cursor.? < self.currentString.items.len) {
if(mods.control) {
const text = self.currentString.items;
// Find start of next "word":
while(!std.ascii.isAlphabetic(text[self.cursor.?]) and std.ascii.isASCII(text[self.cursor.?])) {
self.cursor.? += 1;
if(self.cursor.? >= self.currentString.items.len) return;
}
// Find the end of the next "word":
while(std.ascii.isAlphabetic(text[self.cursor.?]) or !std.ascii.isASCII(text[self.cursor.?])) {
self.cursor.? += 1;
if(self.cursor.? >= self.currentString.items.len) return;
}
} else {
self.cursor.? += std.unicode.utf8ByteSequenceLength(self.currentString.items[self.cursor.?]) catch unreachable;
}
}
}
pub fn right(self: *TextInput, mods: main.Key.Modifiers) void {
if(self.cursor) |*cursor| {
if(mods.shift) {
if(self.selectionStart == null) {
self.selectionStart = cursor.*;
}
self.moveCursorRight(mods);
if(self.selectionStart == self.cursor) {
self.selectionStart = null;
}
} else {
if(self.selectionStart) |selectionStart| {
cursor.* = @max(cursor.*, selectionStart);
self.selectionStart = null;
} else {
self.moveCursorRight(mods);
}
}
}
}
fn moveCursorVertically(self: *TextInput, relativeLines: f32) void {
self.cursor = self.textBuffer.mousePosToIndex(self.textBuffer.indexToCursorPos(self.cursor.?) + Vec2f{0, 16*relativeLines}, self.currentString.items.len);
}
pub fn down(self: *TextInput, mods: main.Key.Modifiers) void {
if(self.cursor) |*cursor| {
if(mods.shift) {
if(self.selectionStart == null) {
self.selectionStart = cursor.*;
}
self.moveCursorVertically(1);
if(self.selectionStart == self.cursor) {
self.selectionStart = null;
}
} else {
if(self.selectionStart) |selectionStart| {
cursor.* = @max(cursor.*, selectionStart);
self.selectionStart = null;
} else {
self.moveCursorVertically(1);
}
}
}
}
pub fn up(self: *TextInput, mods: main.Key.Modifiers) void {
if(self.cursor) |*cursor| {
if(mods.shift) {
if(self.selectionStart == null) {
self.selectionStart = cursor.*;
}
self.moveCursorVertically(-1);
if(self.selectionStart == self.cursor) {
self.selectionStart = null;
}
} else {
if(self.selectionStart) |selectionStart| {
cursor.* = @min(cursor.*, selectionStart);
self.selectionStart = null;
} else {
self.moveCursorVertically(-1);
}
}
}
}
fn moveCursorToStart(self: *TextInput, mods: main.Key.Modifiers) void {
if(mods.control) {
self.cursor.? = 0;
} else {
self.cursor.? = @intCast(u32, if(std.mem.lastIndexOf(u8, self.currentString.items[0..self.cursor.?], "\n")) |nextPos| nextPos + 1 else 0);
}
}
pub fn gotoStart(self: *TextInput, mods: main.Key.Modifiers) void {
if(self.cursor) |*cursor| {
if(mods.shift) {
if(self.selectionStart == null) {
self.selectionStart = cursor.*;
}
self.moveCursorToStart(mods);
if(self.selectionStart == self.cursor) {
self.selectionStart = null;
}
} else {
if(self.selectionStart) |selectionStart| {
cursor.* = @min(cursor.*, selectionStart);
self.selectionStart = null;
} else {
self.moveCursorToStart(mods);
}
}
}
}
fn moveCursorToEnd(self: *TextInput, mods: main.Key.Modifiers) void {
if(mods.control) {
self.cursor.? = @intCast(u32, self.currentString.items.len);
} else {
self.cursor.? = @intCast(u32, std.mem.indexOf(u8, self.currentString.items[self.cursor.?..], "\n") orelse self.currentString.items.len);
}
}
pub fn gotoEnd(self: *TextInput, mods: main.Key.Modifiers) void {
if(self.cursor) |*cursor| {
if(mods.shift) {
if(self.selectionStart == null) {
self.selectionStart = cursor.*;
}
self.moveCursorToEnd(mods);
if(self.selectionStart == self.cursor) {
self.selectionStart = null;
}
} else {
if(self.selectionStart) |selectionStart| {
cursor.* = @min(cursor.*, selectionStart);
self.selectionStart = null;
} else {
self.moveCursorToEnd(mods);
}
}
}
}
fn deleteSelection(self: *TextInput) void {
if(self.selectionStart) |selectionStart| {
const start = @min(selectionStart, self.cursor.?);
const end = @max(selectionStart, self.cursor.?);
self.currentString.replaceRange(start, end - start, &[0]u8{}) catch unreachable;
self.cursor.? = start;
self.selectionStart = null;
}
}
pub fn deleteLeft(self: *TextInput, _: main.Key.Modifiers) void {
if(self.cursor == null) return;
if(self.selectionStart == null) {
self.selectionStart = self.cursor;
self.moveCursorLeft(.{});
}
self.deleteSelection();
self.reloadText() catch |err| {
std.log.err("Error while deleting text: {s}", .{@errorName(err)});
};
}
pub fn deleteRight(self: *TextInput, _: main.Key.Modifiers) void {
if(self.cursor == null) return;
if(self.selectionStart == null) {
self.selectionStart = self.cursor;
self.moveCursorRight(.{});
}
self.deleteSelection();
self.reloadText() catch |err| {
std.log.err("Error while deleting text: {s}", .{@errorName(err)});
};
}
pub fn inputCharacter(self: *TextInput, character: u21) !void {
if(self.cursor) |*cursor| {
self.deleteSelection();
var buf: [4]u8 = undefined;
var utf8 = buf[0..try std.unicode.utf8Encode(character, &buf)];
try self.currentString.insertSlice(cursor.*, utf8);
try self.reloadText();
cursor.* += @intCast(u32, utf8.len);
}
}
pub fn copy(self: *TextInput, mods: main.Key.Modifiers) void {
if(mods.control) {
if(self.cursor) |cursor| {
if(self.selectionStart) |selectionStart| {
const start = @min(cursor, selectionStart);
const end = @max(cursor, selectionStart);
main.Window.setClipboardString(self.currentString.items[start..end]);
}
}
}
}
pub fn paste(self: *TextInput, mods: main.Key.Modifiers) void {
if(mods.control) {
const string = main.Window.getClipboardString();
self.deleteSelection();
self.currentString.insertSlice(self.cursor.?, string) catch |err| {
std.log.err("Error while pasting text: {s}", .{@errorName(err)});
};
self.cursor.? += @intCast(u32, string.len);
self.reloadText() catch |err| {
std.log.err("Error while pasting text: {s}", .{@errorName(err)});
};
}
}
pub fn cut(self: *TextInput, mods: main.Key.Modifiers) void {
if(mods.control) {
self.copy(mods);
self.deleteSelection();
self.reloadText() catch |err| {
std.log.err("Error while cutting text: {s}", .{@errorName(err)});
};
}
}
pub fn render(self: *TextInput, pos: Vec2f, _: Vec2f, mousePosition: Vec2f) !void {
try self.textBuffer.render(pos[0], pos[1], fontSize);
if(self.pressed) {
self.cursor = self.textBuffer.mousePosToIndex(mousePosition - pos, self.currentString.items.len);
}
if(self.cursor) |cursor| {
if(self.selectionStart) |selectionStart| {
draw.setColor(0x440000ff);
try self.textBuffer.drawSelection(pos, @min(selectionStart, cursor), @max(selectionStart, cursor));
}
draw.setColor(0xff000000);
const cursorPos = pos + self.textBuffer.indexToCursorPos(cursor);
draw.line(cursorPos, cursorPos + Vec2f{0, 16});
}
}

View File

@ -11,6 +11,7 @@ const Vec2f = vec.Vec2f;
const Button = @import("components/Button.zig");
const CheckBox = @import("components/CheckBox.zig");
const Slider = @import("components/Slider.zig");
const TextInput = @import("components/TextInput.zig");
pub const GuiComponent = @import("GuiComponent.zig");
pub const GuiWindow = @import("GuiWindow.zig");
@ -20,6 +21,7 @@ var windowList: std.ArrayList(*GuiWindow) = undefined;
var hudWindows: std.ArrayList(*GuiWindow) = undefined;
pub var openWindows: std.ArrayList(*GuiWindow) = undefined;
pub var selectedWindow: ?*GuiWindow = null; // TODO: Make private.
pub var selectedTextInput: ?*TextInput = null;
pub fn init() !void {
windowList = std.ArrayList(*GuiWindow).init(main.globalAllocator);
@ -111,8 +113,76 @@ pub fn closeWindow(window: *GuiWindow) void {
window.onCloseFn();
}
pub fn setSelectedTextInput(newSelectedTextInput: ?*TextInput) void {
if(selectedTextInput) |current| {
if(current != newSelectedTextInput) {
current.deselect();
}
}
selectedTextInput = newSelectedTextInput;
}
pub const textCallbacks = struct {
pub fn left(mods: main.Key.Modifiers) void {
if(selectedTextInput) |current| {
current.left(mods);
}
}
pub fn right(mods: main.Key.Modifiers) void {
if(selectedTextInput) |current| {
current.right(mods);
}
}
pub fn down(mods: main.Key.Modifiers) void {
if(selectedTextInput) |current| {
current.down(mods);
}
}
pub fn up(mods: main.Key.Modifiers) void {
if(selectedTextInput) |current| {
current.up(mods);
}
}
pub fn gotoStart(mods: main.Key.Modifiers) void {
if(selectedTextInput) |current| {
current.gotoStart(mods);
}
}
pub fn gotoEnd(mods: main.Key.Modifiers) void {
if(selectedTextInput) |current| {
current.gotoEnd(mods);
}
}
pub fn deleteLeft(mods: main.Key.Modifiers) void {
if(selectedTextInput) |current| {
current.deleteLeft(mods);
}
}
pub fn deleteRight(mods: main.Key.Modifiers) void {
if(selectedTextInput) |current| {
current.deleteRight(mods);
}
}
pub fn copy(mods: main.Key.Modifiers) void {
if(selectedTextInput) |current| {
current.copy(mods);
}
}
pub fn paste(mods: main.Key.Modifiers) void {
if(selectedTextInput) |current| {
current.paste(mods);
}
}
pub fn cut(mods: main.Key.Modifiers) void {
if(selectedTextInput) |current| {
current.cut(mods);
}
}
};
pub fn mainButtonPressed() void {
selectedWindow = null;
selectedTextInput = null;
var selectedI: usize = 0;
for(openWindows.items, 0..) |window, i| {
var mousePosition = main.Window.getMousePosition();

View File

@ -8,6 +8,7 @@ const gui = @import("../gui.zig");
const GuiComponent = gui.GuiComponent;
const GuiWindow = gui.GuiWindow;
const Button = @import("../components/Button.zig");
const TextInput = @import("../components/TextInput.zig");
const VerticalList = @import("../components/VerticalList.zig");
var window: GuiWindow = undefined;
@ -28,6 +29,8 @@ const padding: f32 = 8;
pub fn onOpen() Allocator.Error!void {
var list = try VerticalList.init(main.globalAllocator);
// TODO Please change your name bla bla
try list.add(try TextInput.init(main.globalAllocator, .{0, 16}, 128, "gr da jkwa hfeka fuei ofuiewo atg78o4ea74e8t z57 t4738qa0 47a80 t47803a t478aqv t487 5t478a0 tg478a09 t748ao t7489a rt4e5 okv5895 678v54vgvo6r z8or z578v rox74et8ys9otv 4z3789so z4oa9t z489saoyt z"));
// TODO
//try list.add(try Button.init(.{0, 16}, 128, main.globalAllocator, "Singleplayer TODO", &buttonCallbackTest));
//try list.add(try Button.init(.{0, 16}, 128, main.globalAllocator, "Multiplayer TODO", &buttonCallbackTest));

View File

@ -68,6 +68,16 @@ pub const Key = struct {
scancode: c_int = 0,
releaseAction: ?*const fn() void = null,
pressAction: ?*const fn() void = null,
repeatAction: ?*const fn(Modifiers) void = null,
pub const Modifiers = packed struct(u6) {
shift: bool = false,
control: bool = false,
alt: bool = false,
super: bool = false,
capsLock: bool = false,
numLock: bool = false,
};
pub fn getName(self: Key) []const u8 {
if(self.mouseButton == -1) {
@ -149,6 +159,7 @@ pub fn setNextKeypressListener(listener: ?*const fn(c_int, c_int, c_int) void) !
nextKeypressListener = listener;
}
pub var keyboard: struct {
// Gameplay:
forward: Key = Key{.key = c.GLFW_KEY_W},
left: Key = Key{.key = c.GLFW_KEY_A},
backward: Key = Key{.key = c.GLFW_KEY_S},
@ -157,9 +168,23 @@ pub var keyboard: struct {
jump: Key = Key{.key = c.GLFW_KEY_SPACE},
fall: Key = Key{.key = c.GLFW_KEY_LEFT_SHIFT},
fullscreen: Key = Key{.key = c.GLFW_KEY_F11, .releaseAction = &Window.toggleFullscreen},
// Gui:
mainGuiButton: Key = Key{.mouseButton = c.GLFW_MOUSE_BUTTON_LEFT, .pressAction = &gui.mainButtonPressed, .releaseAction = &gui.mainButtonReleased},
rightMouseButton: Key = Key{.mouseButton = c.GLFW_MOUSE_BUTTON_RIGHT},
middleMouseButton: Key = Key{.mouseButton = c.GLFW_MOUSE_BUTTON_MIDDLE},
// text:
textCursorLeft: Key = Key{.key = c.GLFW_KEY_LEFT, .repeatAction = &gui.textCallbacks.left},
textCursorRight: Key = Key{.key = c.GLFW_KEY_RIGHT, .repeatAction = &gui.textCallbacks.right},
textCursorDown: Key = Key{.key = c.GLFW_KEY_DOWN, .repeatAction = &gui.textCallbacks.down},
textCursorUp: Key = Key{.key = c.GLFW_KEY_UP, .repeatAction = &gui.textCallbacks.up},
textGotoStart: Key = Key{.key = c.GLFW_KEY_HOME, .repeatAction = &gui.textCallbacks.gotoStart},
textGotoEnd: Key = Key{.key = c.GLFW_KEY_END, .repeatAction = &gui.textCallbacks.gotoEnd},
textDeleteLeft: Key = Key{.key = c.GLFW_KEY_BACKSPACE, .repeatAction = &gui.textCallbacks.deleteLeft},
textDeleteRight: Key = Key{.key = c.GLFW_KEY_DELETE, .repeatAction = &gui.textCallbacks.deleteRight},
textCopy: Key = Key{.key = c.GLFW_KEY_C, .repeatAction = &gui.textCallbacks.copy},
textPaste: Key = Key{.key = c.GLFW_KEY_V, .repeatAction = &gui.textCallbacks.paste},
textCut: Key = Key{.key = c.GLFW_KEY_X, .repeatAction = &gui.textCallbacks.cut},
} = .{};
pub const Window = struct {
@ -173,7 +198,6 @@ pub const Window = struct {
std.log.err("GLFW Error({}): {s}", .{errorCode, description});
}
fn keyCallback(_: ?*c.GLFWwindow, key: c_int, scancode: c_int, action: c_int, mods: c_int) callconv(.C) void {
_ = mods;
if(action == c.GLFW_PRESS) {
inline for(@typeInfo(@TypeOf(keyboard)).Struct.fields) |field| {
if(key == @field(keyboard, field.name).key) {
@ -182,6 +206,9 @@ pub const Window = struct {
if(@field(keyboard, field.name).pressAction) |pressAction| {
pressAction();
}
if(@field(keyboard, field.name).repeatAction) |repeatAction| {
repeatAction(@bitCast(Key.Modifiers, @intCast(u6, mods)));
}
}
}
}
@ -200,8 +227,26 @@ pub const Window = struct {
}
}
}
} else if(action == c.GLFW_REPEAT) {
inline for(@typeInfo(@TypeOf(keyboard)).Struct.fields) |field| {
if(key == @field(keyboard, field.name).key) {
if(key != c.GLFW_KEY_UNKNOWN or scancode == @field(keyboard, field.name).scancode) {
if(@field(keyboard, field.name).repeatAction) |repeatAction| {
repeatAction(@bitCast(Key.Modifiers, @intCast(u6, mods)));
}
}
}
}
}
}
fn charCallback(_: ?*c.GLFWwindow, codepoint: c_uint) callconv(.C) void {
if(gui.selectedTextInput) |textInput| {
textInput.inputCharacter(@intCast(u21, codepoint)) catch |err| {
std.log.err("Error while adding character to textInput: {s}", .{@errorName(err)});
};
}
}
fn framebufferSize(_: ?*c.GLFWwindow, newWidth: c_int, newHeight: c_int) callconv(.C) void {
std.log.info("Framebuffer: {}, {}", .{newWidth, newHeight});
width = @intCast(u31, newWidth);
@ -297,6 +342,16 @@ pub const Window = struct {
c.glfwSwapInterval(@boolToInt(settings.vsync));
}
pub fn getClipboardString() []const u8 {
return std.mem.span(c.glfwGetClipboardString(window));
}
pub fn setClipboardString(string: []const u8) void {
const nullTerminatedString = threadAllocator.dupeZ(u8, string) catch return;
defer threadAllocator.free(nullTerminatedString);
c.glfwSetClipboardString(window, nullTerminatedString.ptr);
}
fn init() !void {
_ = c.glfwSetErrorCallback(GLFWCallbacks.errorCallback);
@ -313,6 +368,7 @@ pub const Window = struct {
window = c.glfwCreateWindow(width, height, "Cubyz", null, null) orelse return error.GLFWFailed;
_ = c.glfwSetKeyCallback(window, GLFWCallbacks.keyCallback);
_ = c.glfwSetCharCallback(window, GLFWCallbacks.charCallback);
_ = c.glfwSetFramebufferSizeCallback(window, GLFWCallbacks.framebufferSize);
_ = c.glfwSetCursorPosCallback(window, GLFWCallbacks.cursorPosition);
_ = c.glfwSetMouseButtonCallback(window, GLFWCallbacks.mouseButton);