mirror of
https://github.com/PixelGuys/Cubyz.git
synced 2025-09-17 08:21:38 -04:00
Add a TextInput component that supports multiline text editing.
This commit is contained in:
parent
b12f004d41
commit
c2e888031f
@ -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;
|
||||
|
@ -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);
|
||||
|
129
src/graphics.zig
129
src/graphics.zig
@ -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;
|
||||
|
@ -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,
|
||||
};
|
||||
|
||||
|
362
src/gui/components/TextInput.zig
Normal file
362
src/gui/components/TextInput.zig
Normal 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});
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
@ -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));
|
||||
|
58
src/main.zig
58
src/main.zig
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user