diff --git a/assets/cubyz/ui/text_input.png b/assets/cubyz/ui/text_input.png index 33c419093..fd35a6883 100644 Binary files a/assets/cubyz/ui/text_input.png and b/assets/cubyz/ui/text_input.png differ diff --git a/src/graphics.zig b/src/graphics.zig index 2ebf399be..911a30ed0 100644 --- a/src/graphics.zig +++ b/src/graphics.zig @@ -30,13 +30,13 @@ fn fileToString(allocator: Allocator, path: []const u8) ![]u8 { } pub const draw = struct { - var color: i32 = 0; + var color: u32 = 0; var clip: ?Vec4i = null; var translation: Vec2f = Vec2f{0, 0}; var scale: f32 = 1; pub fn setColor(newColor: u32) void { - color = @bitCast(i32, newColor); + color = newColor; } /// Returns the previous translation. @@ -52,6 +52,7 @@ pub const draw = struct { /// Returns the previous translation. pub fn setScale(newScale: f32) f32 { + std.debug.assert(newScale >= 0); const oldScale = scale; scale *= newScale; return oldScale; @@ -63,11 +64,12 @@ pub const draw = struct { /// Returns the previous clip. pub fn setClip(clipRect: Vec2f) ?Vec4i { + std.debug.assert(@reduce(.And, clipRect >= Vec2f{0, 0})); var newClip = Vec4i { - @floatToInt(i32, translation[0]), - main.Window.height - @floatToInt(i32, translation[1] + clipRect[1]*scale), - @floatToInt(i32, clipRect[0]*scale), - @floatToInt(i32, clipRect[1]*scale), + std.math.lossyCast(i32, translation[0]), + main.Window.height - std.math.lossyCast(i32, translation[1] + clipRect[1]*scale), + std.math.lossyCast(i32, clipRect[0]*scale), + std.math.lossyCast(i32, clipRect[1]*scale), }; if(clip) |oldClip| { if (newClip[0] < oldClip[0]) { @@ -84,6 +86,8 @@ pub const draw = struct { if (newClip[1] + newClip[3] > oldClip[1] + oldClip[3]) { newClip[3] -= (newClip[1] + newClip[3]) - (oldClip[1] + oldClip[3]); } + newClip[2] = @max(newClip[2], 0); + newClip[3] = @max(newClip[3], 0); } else { c.glEnable(c.GL_SCISSOR_TEST); } @@ -151,7 +155,7 @@ pub const draw = struct { c.glUniform2f(rectUniforms.screen, @intToFloat(f32, Window.width), @intToFloat(f32, Window.height)); c.glUniform2f(rectUniforms.start, pos[0], pos[1]); c.glUniform2f(rectUniforms.size, dim[0], dim[1]); - c.glUniform1i(rectUniforms.rectColor, color); + c.glUniform1i(rectUniforms.rectColor, @bitCast(i32, color)); c.glBindVertexArray(rectVAO); c.glDrawArrays(c.GL_TRIANGLE_STRIP, 0, 4); @@ -204,7 +208,7 @@ pub const draw = struct { c.glUniform2f(lineUniforms.screen, @intToFloat(f32, Window.width), @intToFloat(f32, Window.height)); c.glUniform2f(lineUniforms.start, pos1[0], pos1[1]); c.glUniform2f(lineUniforms.direction, pos2[0] - pos1[0], pos2[1] - pos1[1]); - c.glUniform1i(lineUniforms.lineColor, color); + c.glUniform1i(lineUniforms.lineColor, @bitCast(i32, color)); c.glBindVertexArray(lineVAO); c.glDrawArrays(c.GL_LINE_STRIP, 0, 2); @@ -250,7 +254,7 @@ pub const draw = struct { c.glUniform2f(lineUniforms.screen, @intToFloat(f32, Window.width), @intToFloat(f32, Window.height)); c.glUniform2f(lineUniforms.start, pos[0], pos[1]); // Move the coordinates, so they are in the center of a pixel. c.glUniform2f(lineUniforms.direction, dim[0] - 1, dim[1] - 1); // The height is a lot smaller because the inner edge of the rect is drawn. - c.glUniform1i(lineUniforms.lineColor, color); + c.glUniform1i(lineUniforms.lineColor, @bitCast(i32, color)); c.glBindVertexArray(lineVAO); c.glDrawArrays(c.GL_LINE_LOOP, 0, 5); @@ -303,7 +307,7 @@ pub const draw = struct { c.glUniform2f(circleUniforms.screen, @intToFloat(f32, Window.width), @intToFloat(f32, Window.height)); c.glUniform2f(circleUniforms.center, center[0], center[1]); // Move the coordinates, so they are in the center of a pixel. c.glUniform1f(circleUniforms.radius, radius); // The height is a lot smaller because the inner edge of the rect is drawn. - c.glUniform1i(circleUniforms.circleColor, color); + c.glUniform1i(circleUniforms.circleColor, @bitCast(i32, color)); c.glBindVertexArray(circleVAO); c.glDrawArrays(c.GL_TRIANGLE_STRIP, 0, 4); @@ -341,7 +345,7 @@ pub const draw = struct { c.glUniform2f(imageUniforms.screen, @intToFloat(f32, Window.width), @intToFloat(f32, Window.height)); c.glUniform2f(imageUniforms.start, pos[0], pos[1]); c.glUniform2f(imageUniforms.size, dim[0], dim[1]); - c.glUniform1i(imageUniforms.color, color); + c.glUniform1i(imageUniforms.color, @bitCast(i32, color)); c.glBindVertexArray(rectVAO); c.glDrawArrays(c.GL_TRIANGLE_STRIP, 0, 4); @@ -359,7 +363,7 @@ pub const draw = struct { c.glUniform2f(uniforms.screen, @intToFloat(f32, Window.width), @intToFloat(f32, Window.height)); c.glUniform2f(uniforms.start, pos[0], pos[1]); c.glUniform2f(uniforms.size, dim[0], dim[1]); - c.glUniform1i(uniforms.color, color); + c.glUniform1i(uniforms.color, @bitCast(i32, color)); c.glUniform1f(uniforms.scale, scale); c.glBindVertexArray(rectVAO); @@ -382,7 +386,7 @@ pub const TextBuffer = struct { }; pub const FontEffect = packed struct(u28) { - color: u24 = 0, + color: u24 = 0xffffff, bold: bool = false, italic: bool = false, underline: bool = false, @@ -741,6 +745,7 @@ pub const TextBuffer = struct { TextRendering.shader.bind(); c.glUniform2f(TextRendering.uniforms.scene, @intToFloat(f32, main.Window.width), @intToFloat(f32, main.Window.height)); c.glUniform1f(TextRendering.uniforms.ratio, draw.scale); + c.glUniform1f(TextRendering.uniforms.alpha, @intToFloat(f32, draw.color >> 24) / 255.0); c.glActiveTexture(c.GL_TEXTURE0); c.glBindTexture(c.GL_TEXTURE_2D, TextRendering.glyphTexture[0]); c.glBindVertexArray(draw.rectVAO); @@ -767,7 +772,7 @@ pub const TextBuffer = struct { y = 0; if(line.isUnderline) y += 15 else y += 8; - draw.setColor(line.color | @as(u32, 0xff000000)); + draw.setColor(line.color | (@as(u32, 0xff000000) & draw.color)); for(lineWraps, 0..) |lineWrap, j| { const lineStart = @max(0, line.start); const lineEnd = @min(lineWrap, line.end); @@ -856,6 +861,7 @@ const TextRendering = struct { const swap = glyphTexture[1]; glyphTexture[1] = glyphTexture[0]; glyphTexture[0] = swap; + c.glActiveTexture(c.GL_TEXTURE0); c.glBindTexture(c.GL_TEXTURE_2D, glyphTexture[0]); c.glTexImage2D(c.GL_TEXTURE_2D, 0, c.GL_R8, newWidth, textureHeight, 0, c.GL_RED, c.GL_UNSIGNED_BYTE, null); c.glCopyImageSubData( @@ -1349,8 +1355,7 @@ pub const TextureArray = struct { c.glTexSubImage3D(c.GL_TEXTURE_2D_ARRAY, lod, 0, 0, @intCast(c_int, i), curWidth, curHeight, 1, c.GL_RGBA, c.GL_UNSIGNED_BYTE, lodBuffer[lod].ptr); } } - c.glTexParameteri(c.GL_TEXTURE_2D, c.GL_TEXTURE_MAX_LOD, maxLOD); - c.glTexParameteri(c.GL_TEXTURE_2D, c.GL_TEXTURE_BASE_LEVEL, 5); + c.glTexParameteri(c.GL_TEXTURE_2D_ARRAY, c.GL_TEXTURE_MAX_LOD, maxLOD); //glGenerateMipmap(GL_TEXTURE_2D_ARRAY); c.glTexParameteri(c.GL_TEXTURE_2D_ARRAY, c.GL_TEXTURE_MIN_FILTER, c.GL_NEAREST_MIPMAP_LINEAR); c.glTexParameteri(c.GL_TEXTURE_2D_ARRAY, c.GL_TEXTURE_MAG_FILTER, c.GL_NEAREST); diff --git a/src/gui/GuiWindow.zig b/src/gui/GuiWindow.zig index 4fa854729..bff8a2754 100644 --- a/src/gui/GuiWindow.zig +++ b/src/gui/GuiWindow.zig @@ -63,6 +63,8 @@ isHud: bool = false, /// Called every frame. renderFn: *const fn()Allocator.Error!void = &defaultErrorFunction, +/// Called every frame before rendering. +updateFn: *const fn()Allocator.Error!void = &defaultErrorFunction, /// Called every frame for the currently selected window. updateSelectedFn: *const fn()Allocator.Error!void = &defaultErrorFunction, /// Called every frame for the currently hovered window. @@ -310,6 +312,10 @@ fn positionRelativeToConnectedWindow(self: *GuiWindow, other: *GuiWindow, i: usi } } +pub fn update(self: *GuiWindow) !void { + try self.updateFn(); +} + pub fn updateSelected(self: *GuiWindow, mousePosition: Vec2f) !void { try self.updateSelectedFn(); const windowSize = main.Window.getWindowSize()/@splat(2, gui.scale); @@ -478,7 +484,7 @@ pub fn render(self: *const GuiWindow, mousePosition: Vec2f) !void { draw.restoreTranslation(oldTranslation); draw.restoreScale(oldScale); if(self.showTitleBar) { - var text = try graphics.TextBuffer.init(gui.allocator, self.title, .{}, false, .center); + var text = try graphics.TextBuffer.init(gui.allocator, self.title, .{.color=0}, false, .center); defer text.deinit(); const titleDimension = try text.calculateLineBreaks(16*self.scale, self.size[0]); try text.render(self.pos[0] + self.size[0]/2 - titleDimension[0]/2, self.pos[1], 16*self.scale); diff --git a/src/gui/components/Label.zig b/src/gui/components/Label.zig index 9284503a9..8ff997e2c 100644 --- a/src/gui/components/Label.zig +++ b/src/gui/components/Label.zig @@ -18,6 +18,7 @@ const fontSize: f32 = 16; pos: Vec2f, size: Vec2f, text: TextBuffer, +alpha: f32 = 1, pub fn init(pos: Vec2f, maxWidth: f32, text: []const u8, alignment: TextBuffer.Alignment) Allocator.Error!*Label { const self = try gui.allocator.create(Label); @@ -49,5 +50,6 @@ pub fn updateText(self: *Label, newText: []const u8) !void { } pub fn render(self: *Label, _: Vec2f) !void { + draw.setColor(@floatToInt(u32, self.alpha*255) << 24); try self.text.render(self.pos[0], self.pos[1], fontSize); } \ No newline at end of file diff --git a/src/gui/components/MutexComponent.zig b/src/gui/components/MutexComponent.zig new file mode 100644 index 000000000..8fe174cec --- /dev/null +++ b/src/gui/components/MutexComponent.zig @@ -0,0 +1,79 @@ +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 ScrollBar = GuiComponent.ScrollBar; + +const MutexComponent = @This(); + +const scrollBarWidth = 5; +const border: f32 = 3; + +pos: Vec2f = undefined, +size: Vec2f = undefined, +child: GuiComponent = undefined, +mutex: std.Thread.Mutex = .{}, + +pub fn updateInner(self: *MutexComponent, _other: anytype) Allocator.Error!void { + std.debug.assert(!self.mutex.tryLock()); // self.mutex must be locked before calling! + var other: GuiComponent = undefined; + if(@TypeOf(_other) == GuiComponent) { + other = _other; + } else { + other = _other.toComponent(); + } + self.child = other; + self.pos = other.pos(); + self.size = other.size(); +} + +pub fn deinit(self: *MutexComponent) void { + std.debug.assert(!self.mutex.tryLock()); // self.mutex must be locked before calling! + self.child.deinit(); +} + +pub fn toComponent(self: *MutexComponent) GuiComponent { + return GuiComponent { + .mutexComponent = self + }; +} + +pub fn updateSelected(self: *MutexComponent) void { + self.mutex.lock(); + defer self.mutex.unlock(); + self.child.updateSelected(); +} + +pub fn updateHovered(self: *MutexComponent, mousePosition: Vec2f) void { + self.mutex.lock(); + defer self.mutex.unlock(); + self.child.updateHovered(mousePosition); +} + +pub fn render(self: *MutexComponent, mousePosition: Vec2f) anyerror!void { // TODO: Remove anyerror once error union inference works in recursive loops. + self.mutex.lock(); + defer self.mutex.unlock(); + try self.child.render(mousePosition); + self.pos = self.child.pos(); + self.size = self.child.size(); +} + +pub fn mainButtonPressed(self: *MutexComponent, mousePosition: Vec2f) void { + self.mutex.lock(); + defer self.mutex.unlock(); + self.child.mainButtonPressed(mousePosition); +} + +pub fn mainButtonReleased(self: *MutexComponent, mousePosition: Vec2f) void { + self.mutex.lock(); + defer self.mutex.unlock(); + self.child.mainButtonReleased(mousePosition); +} \ No newline at end of file diff --git a/src/gui/components/TextInput.zig b/src/gui/components/TextInput.zig index 7f984de01..60ed885f3 100644 --- a/src/gui/components/TextInput.zig +++ b/src/gui/components/TextInput.zig @@ -33,6 +33,7 @@ maxWidth: f32, maxHeight: f32, textSize: Vec2f = undefined, scrollBar: *ScrollBar, +onNewline: ?*const fn() void, pub fn __init() !void { texture = try Texture.initFromFile("assets/cubyz/ui/text_input.png"); @@ -42,7 +43,7 @@ pub fn __deinit() void { texture.deinit(); } -pub fn init(pos: Vec2f, maxWidth: f32, maxHeight: f32, text: []const u8) Allocator.Error!*TextInput { +pub fn init(pos: Vec2f, maxWidth: f32, maxHeight: f32, text: []const u8, onNewline: ?*const fn() void) Allocator.Error!*TextInput { const scrollBar = try ScrollBar.init(undefined, scrollBarWidth, maxHeight - 2*border, 0); const self = try gui.allocator.create(TextInput); self.* = TextInput { @@ -53,6 +54,7 @@ pub fn init(pos: Vec2f, maxWidth: f32, maxHeight: f32, text: []const u8) Allocat .maxWidth = maxWidth, .maxHeight = maxHeight, .scrollBar = scrollBar, + .onNewline = onNewline, }; try self.currentString.appendSlice(text); self.textSize = try self.textBuffer.calculateLineBreaks(fontSize, maxWidth - 2*border - scrollBarWidth); @@ -66,6 +68,15 @@ pub fn deinit(self: *const TextInput) void { gui.allocator.destroy(self); } +pub fn clear(self: *TextInput) !void { + if(self.cursor != null) { + self.cursor = 0; + self.selectionStart = null; + } + self.currentString.clearRetainingCapacity(); + try self.reloadText(); +} + pub fn toComponent(self: *TextInput) GuiComponent { return GuiComponent { .textInput = self @@ -420,7 +431,11 @@ pub fn cut(self: *TextInput, mods: main.Key.Modifiers) void { } } -pub fn newline(self: *TextInput, _: main.Key.Modifiers) void { +pub fn newline(self: *TextInput, mods: main.Key.Modifiers) void { + if(!mods.shift) if(self.onNewline) |onNewline| { + onNewline(); + return; + }; self.inputCharacter('\n') catch |err| { std.log.err("Error while entering text: {s}", .{@errorName(err)}); }; diff --git a/src/gui/gui.zig b/src/gui/gui.zig index 03cea3fb1..174b631ed 100644 --- a/src/gui/gui.zig +++ b/src/gui/gui.zig @@ -17,7 +17,7 @@ const TextInput = @import("components/TextInput.zig"); pub const GuiComponent = @import("gui_component.zig").GuiComponent; pub const GuiWindow = @import("GuiWindow.zig"); -const windowlist = @import("windows/_windowlist.zig"); +pub const windowlist = @import("windows/_windowlist.zig"); var windowList: std.ArrayList(*GuiWindow) = undefined; var hudWindows: std.ArrayList(*GuiWindow) = undefined; @@ -402,6 +402,9 @@ pub fn updateAndRenderGui() !void { break; } } + for(openWindows.items) |window| { + try window.update(); + } for(openWindows.items) |window| { const oldScale = draw.setScale(scale); defer draw.restoreScale(oldScale); diff --git a/src/gui/gui_component.zig b/src/gui/gui_component.zig index 078578627..54bcadac7 100644 --- a/src/gui/gui_component.zig +++ b/src/gui/gui_component.zig @@ -10,6 +10,7 @@ pub const GuiComponent = union(enum) { pub const CheckBox = @import("components/CheckBox.zig"); pub const HorizontalList = @import("components/HorizontalList.zig"); pub const Label = @import("components/Label.zig"); + pub const MutexComponent = @import("components/MutexComponent.zig"); pub const Slider = @import("components/Slider.zig"); pub const ScrollBar = @import("components/ScrollBar.zig"); pub const TextInput = @import("components/TextInput.zig"); @@ -20,6 +21,7 @@ pub const GuiComponent = union(enum) { checkBox: *CheckBox, horizontalList: *HorizontalList, label: *Label, + mutexComponent: *MutexComponent, scrollBar: *ScrollBar, slider: *Slider, textInput: *TextInput, diff --git a/src/gui/windows/_windowlist.zig b/src/gui/windows/_windowlist.zig index a96615c39..cf4c2417f 100644 --- a/src/gui/windows/_windowlist.zig +++ b/src/gui/windows/_windowlist.zig @@ -1,5 +1,6 @@ pub const change_name = @import("change_name.zig"); +pub const chat = @import("chat.zig"); pub const controls = @import("controls.zig"); pub const crosshair = @import("crosshair.zig"); pub const graphics = @import("graphics.zig"); diff --git a/src/gui/windows/change_name.zig b/src/gui/windows/change_name.zig index 605588382..b0ee66c02 100644 --- a/src/gui/windows/change_name.zig +++ b/src/gui/windows/change_name.zig @@ -49,13 +49,13 @@ pub fn onOpen() Allocator.Error!void { if(settings.playerName.len == 0) { try list.add(try Label.init(.{0, 0}, width, "Please enter your name!", .center)); } else { - try list.add(try Label.init(.{0, 0}, width, "#ff0000Warning: #000000You loose access to your inventory data when changing the name!", .center)); + try list.add(try Label.init(.{0, 0}, width, "#ff0000Warning: #ffffffYou loose access to your inventory data when changing the name!", .center)); } try list.add(try Label.init(.{0, 0}, width, "Cubyz supports formatting your username using a markdown-like syntax:", .center)); try list.add(try Label.init(.{0, 0}, width, "\\**italic*\\* \\*\\***bold**\\*\\* \\__underlined_\\_ \\_\\___strike-through__\\_\\_", .center)); try list.add(try Label.init(.{0, 0}, width, "Even colors are possible, using the hexadecimal color code:", .center)); - try list.add(try Label.init(.{0, 0}, width, "\\##ff0000ff#00000000#00000000#ff0000red#000000 \\##ff0000ff#00770077#00000000#ff7700orange#000000 \\##00000000#00ff00ff#00000000#00ff00green#000000 \\##00000000#00000000#0000ffff#0000ffblue", .center)); - textComponent = try TextInput.init(.{0, 0}, width, 32, "quanturmdoelvloper"); + try list.add(try Label.init(.{0, 0}, width, "\\##ff0000ff#ffffff00#ffffff00#ff0000red#ffffff \\##ff0000ff#00770077#ffffff00#ff7700orange#ffffff \\##ffffff00#00ff00ff#ffffff00#00ff00green#ffffff \\##ffffff00#ffffff00#0000ffff#0000ffblue", .center)); + textComponent = try TextInput.init(.{0, 0}, width, 32, "quanturmdoelvloper", &apply); try list.add(textComponent); try list.add(try Button.init(.{0, 0}, 100, "Apply", &apply)); list.finish(.center); diff --git a/src/gui/windows/chat.zig b/src/gui/windows/chat.zig new file mode 100644 index 000000000..d98fa9bba --- /dev/null +++ b/src/gui/windows/chat.zig @@ -0,0 +1,132 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; + +const main = @import("root"); +const Vec2f = main.vec.Vec2f; + +const gui = @import("../gui.zig"); +const GuiComponent = gui.GuiComponent; +const GuiWindow = gui.GuiWindow; +const Button = @import("../components/Button.zig"); +const Label = GuiComponent.Label; +const MutexComponent = GuiComponent.MutexComponent; +const TextInput = GuiComponent.TextInput; +const VerticalList = @import("../components/VerticalList.zig"); + +var components: [1]GuiComponent = undefined; +pub var window: GuiWindow = GuiWindow { + .contentSize = Vec2f{128, 256}, + .id = "cubyz:chat", + .title = "Chat", + .onOpenFn = &onOpen, + .onCloseFn = &onClose, + .updateFn = &update, + .renderFn = &render, + .components = &components, + .showTitleBar = false, + .hasBackground = false, + .isHud = true, +}; + +const padding: f32 = 8; +const messageTimeout: i32 = 10000; +const messageFade: i32 = 1000; + +var mutexComponent: MutexComponent = .{}; +var history: std.ArrayList(*Label) = undefined; +var expirationTime: std.ArrayList(i32) = undefined; +var historyStart: u32 = 0; +var fadeOutEnd: u32 = 0; +var input: *TextInput = undefined; +var hideInput: bool = true; + +fn refresh(deleteOld: bool) Allocator.Error!void { + std.debug.assert(!mutexComponent.mutex.tryLock()); // mutex must be locked! + if(deleteOld) { + components[0].mutexComponent.child.verticalList.children.clearRetainingCapacity(); + components[0].deinit(); + } + var list = try VerticalList.init(.{padding, 16 + padding}, 300, 0); + for(history.items[if(hideInput) historyStart else 0 ..]) |msg| { + msg.pos = .{0, 0}; + try list.add(msg); + } + if(!hideInput) { + input.pos = .{0, 0}; + try list.add(input); + } + list.finish(.center); + list.scrollBar.currentState = 1; + try mutexComponent.updateInner(list); + components[0] = mutexComponent.toComponent(); + window.contentSize = components[0].pos() + components[0].size() + @splat(2, @as(f32, padding)); + gui.updateWindowPositions(); +} + +pub fn onOpen() Allocator.Error!void { + history = std.ArrayList(*Label).init(main.globalAllocator); + expirationTime = std.ArrayList(i32).init(main.globalAllocator); + historyStart = 0; + input = try TextInput.init(.{0, 0}, 256, 32, "", &sendMessage); + mutexComponent.mutex.lock(); + defer mutexComponent.mutex.unlock(); + try refresh(false); +} + +pub fn onClose() void { + mutexComponent.mutex.lock(); + defer mutexComponent.mutex.unlock(); + for(history.items) |label| { + label.deinit(); + } + history.deinit(); + expirationTime.deinit(); + input.deinit(); + components[0].mutexComponent.child.verticalList.children.clearRetainingCapacity(); + components[0].deinit(); +} + +pub fn update() Allocator.Error!void { + mutexComponent.mutex.lock(); + defer mutexComponent.mutex.unlock(); + while(fadeOutEnd < history.items.len and @truncate(i32, std.time.milliTimestamp()) -% expirationTime.items[fadeOutEnd] >= 0) { + fadeOutEnd += 1; + } + for(expirationTime.items[historyStart..fadeOutEnd], history.items[historyStart..fadeOutEnd]) |time, label| { + if(@truncate(i32, std.time.milliTimestamp()) -% time >= messageFade) { + historyStart += 1; + hideInput = main.Window.grabbed; + try refresh(true); + } else { + label.alpha = 1.0 - @intToFloat(f32, @truncate(i32, std.time.milliTimestamp()) -% time)/@intToFloat(f32, messageFade); + } + } + if(hideInput != main.Window.grabbed) { + hideInput = main.Window.grabbed; + try refresh(true); + } +} + +pub fn render() Allocator.Error!void { + if(!hideInput) { + main.graphics.draw.setColor(0x80000000); + main.graphics.draw.rect(.{0, 0}, window.contentSize); + } +} + +pub fn addMessage(message: []const u8) Allocator.Error!void { + mutexComponent.mutex.lock(); + defer mutexComponent.mutex.unlock(); + try history.append(try Label.init(.{0, 0}, 256, message, .left)); + try expirationTime.append(@truncate(i32, std.time.milliTimestamp()) +% messageTimeout); + try refresh(true); +} + +pub fn sendMessage() void { + main.network.Protocols.chat.send(main.game.world.?.conn, input.currentString.items) catch |err| { + std.log.err("Got error while trying to send chat message: {s}", .{@errorName(err)}); + }; + input.clear() catch |err| { + std.log.err("Got error while trying to send chat message: {s}", .{@errorName(err)}); + }; +} \ No newline at end of file diff --git a/src/gui/windows/multiplayer.zig b/src/gui/windows/multiplayer.zig index b75eb5aca..fcdf2ffb0 100644 --- a/src/gui/windows/multiplayer.zig +++ b/src/gui/windows/multiplayer.zig @@ -21,6 +21,7 @@ pub var window = GuiWindow { .title = "Multiplayer", .onOpenFn = &onOpen, .onCloseFn = &onClose, + .updateFn = &update, .components = &components, }; @@ -37,7 +38,7 @@ const width: f32 = 420; fn flawedDiscoverIpAddress() !void { connection = try ConnectionManager.init(12347, true); // TODO: default port ipAddress = try std.fmt.allocPrint(main.globalAllocator, "{}", .{connection.?.externalAddress}); - gotIpAddress.store(true, .Monotonic); + gotIpAddress.store(true, .Release); } fn discoverIpAddress() void { @@ -94,7 +95,7 @@ pub fn onOpen() Allocator.Error!void { ipAddressLabel = try Label.init(.{0, 0}, width, " ", .center); try list.add(ipAddressLabel); try list.add(try Button.init(.{0, 0}, 100, "Copy IP", ©Ip)); - try list.add(try TextInput.init(.{0, 0}, width, 32, settings.lastUsedIPAddress)); + try list.add(try TextInput.init(.{0, 0}, width, 32, settings.lastUsedIPAddress, &join)); try list.add(try Button.init(.{0, 0}, 100, "Join", &join)); list.finish(.center); components[0] = list.toComponent(); @@ -127,8 +128,8 @@ pub fn onClose() void { } } -pub fn render() void { - if(gotIpAddress.load(.Monotonic)) { +pub fn update() Allocator.Error!void { + if(gotIpAddress.load(.Acquire)) { gotIpAddress.store(false, .Monotonic); try ipAddressLabel.updateText(ipAddress); } diff --git a/src/main.zig b/src/main.zig index 4ee21ddaf..803d77a75 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,6 +1,6 @@ const std = @import("std"); -const gui = @import("gui"); +pub const gui = @import("gui"); pub const assets = @import("assets.zig"); pub const blocks = @import("blocks.zig"); diff --git a/src/network.zig b/src/network.zig index 225746748..57763d3bd 100644 --- a/src/network.zig +++ b/src/network.zig @@ -1201,6 +1201,38 @@ pub const Protocols: struct { // addHeaderAndSendUnimportant(user, TIME_AND_BIOME, data.toString().getBytes(StandardCharsets.UTF_8)); // } }, + chat: type = struct { + const id: u8 = 10; + fn receive(conn: *Connection, data: []const u8) !void { + _ = conn; + // TODO: +// if(conn instanceof User) { +// User user = (User)conn; +// if(msg.startsWith("/")) { +// CommandExecutor.execute(msg, user); +// } else { +// msg = "["+user.name+"#ffffff] "+msg; +// sendToClients(msg); +// } +// } else { + try main.gui.windowlist.chat.addMessage(data); +// } + } + + pub fn send(conn: *Connection, data: []const u8) !void { + try conn.sendImportant(id, data); + } +// TODO +// public void sendToClients(String msg) { +// Logger.log("chat", msg, "\033[0;32m"); +// synchronized(this) { +// for(User user : Server.users) { +// send(user, msg); +// } +// } +// } +//} + }, } = .{};