From 61e2e417a219fc112171fa1cd51695184a9b9942 Mon Sep 17 00:00:00 2001 From: IntegratedQuantum Date: Fri, 24 Feb 2023 13:25:59 +0100 Subject: [PATCH] Add textures for the button and window. --- assets/cubyz/shaders/ui/button.fs | 20 ++++++ assets/cubyz/shaders/ui/button.vs | 27 ++++++++ assets/cubyz/ui/button.png | Bin 0 -> 263 bytes assets/cubyz/ui/oak_log_side.png | Bin 0 -> 843 bytes assets/cubyz/ui/window_background.png | Bin 0 -> 846 bytes assets/cubyz/ui/window_title.png | Bin 0 -> 706 bytes build.zig | 6 +- src/graphics.zig | 48 ++++++++++++++ src/gui/GuiWindow.zig | 62 ++++++++++++++++-- src/gui/components/Button.zig | 88 ++++++++++++++------------ src/gui/gui.zig | 5 ++ src/main.zig | 2 +- 12 files changed, 209 insertions(+), 49 deletions(-) create mode 100644 assets/cubyz/shaders/ui/button.fs create mode 100644 assets/cubyz/shaders/ui/button.vs create mode 100644 assets/cubyz/ui/button.png create mode 100644 assets/cubyz/ui/oak_log_side.png create mode 100644 assets/cubyz/ui/window_background.png create mode 100644 assets/cubyz/ui/window_title.png diff --git a/assets/cubyz/shaders/ui/button.fs b/assets/cubyz/shaders/ui/button.fs new file mode 100644 index 00000000..97e6d134 --- /dev/null +++ b/assets/cubyz/shaders/ui/button.fs @@ -0,0 +1,20 @@ +#version 330 + +layout (location=0) out vec4 frag_color; +uniform sampler2D image; + +flat in vec4 fColor; +in vec2 startCoord; + +uniform bool pressed; +uniform float scale; +uniform vec2 randomOffset; + +void main() { + frag_color = texture(image, (gl_FragCoord.xy - startCoord)/(2*scale)/textureSize(image, 0) + randomOffset); + frag_color.a *= fColor.a; + frag_color.rgb += fColor.rgb; + if(pressed) { + frag_color.rgb *= 0.8; + } +} \ No newline at end of file diff --git a/assets/cubyz/shaders/ui/button.vs b/assets/cubyz/shaders/ui/button.vs new file mode 100644 index 00000000..7cc2fa57 --- /dev/null +++ b/assets/cubyz/shaders/ui/button.vs @@ -0,0 +1,27 @@ +#version 330 core + +layout (location=0) in vec2 vertex_pos; + +out vec2 startCoord; +flat out vec4 fColor; + +//in pixel +uniform vec2 start; +uniform vec2 size; +uniform vec2 screen; + +uniform int color; + +void main() { + + // Convert to opengl coordinates: + vec2 position_percentage = (start + vertex_pos*size)/screen; + startCoord.x = start.x; + startCoord.y = screen.y - start.y; + + vec2 position = vec2(position_percentage.x, -position_percentage.y)*2+vec2(-1, 1); + + gl_Position = vec4(position, 0, 1); + + fColor = vec4((color & 0xff0000)>>16, (color & 0xff00)>>8, color & 0xff, (color>>24) & 255)/255.0; +} \ No newline at end of file diff --git a/assets/cubyz/ui/button.png b/assets/cubyz/ui/button.png new file mode 100644 index 0000000000000000000000000000000000000000..828fee434efb73a607f07b8efab40c078e1ddab2 GIT binary patch literal 263 zcmV+i0r>ujP)N%s`=;{iYUuU0a_*#xL{1sxGGGNO935cHh8E`<%kQ7k%OMw3aQ32&UL(@DM z{0faaFZ-eQON3|=8pHx}xUENC@5Y}3g{1`wPz(6iC@VeDHtJfAf)`4~t|Fe1OsD_= N002ovPDHLkV1mOTX$=4X literal 0 HcmV?d00001 diff --git a/assets/cubyz/ui/oak_log_side.png b/assets/cubyz/ui/oak_log_side.png new file mode 100644 index 0000000000000000000000000000000000000000..d9cfeedfcec7548ceab3b3955109ba26c909c6bc GIT binary patch literal 843 zcmV-R1GM~!P)EX>4Tx04R}tkv&MmP!xqvTcuShf_4yb2vVIah>AFB6^c+H)C#RSn7s54nlvOS zE{=k0!NH%!s)LKOt`4q(Aov5~=H{g6A|>9J6k5di;PO7sd*^W9eSp7SW~$jS4yc-C zq!Mu%zW3+qQF0~&d;;+-(+!JwgLrz= z(mC%FM_5r(h|h_~3~G@0k?XR{Z=4Gb`*~)>NT=qBBg8_Xjpa6GMMEW?B91DmM*04X z%L?Z$&T6^Jntk#YhI87=GS{hwkia6AAVPqQDoQBBLX38e6cZ^rk9+tB9luB}nOr3> zax9<%6_Voz|AXJNHS?1bZc;c7biUa3#|RMI1sXNm{yw(t#t9I32ClT0zfuQgK1r{& zwCEAgyA51iw={VVxZD8-o^;8O94SEM&*y>nGy0}1(0>bbueoz;p5ycZNYkv6H^9Lm zFj}DOb&q$4+UNG~o#y<0057C+jAH>>2mk;824YJ`L;(K){{a7>y{D4^000SaNLh0L z01FcU01FcV0GgZ_00007bV*G`2jvM82?-A~@j#;h000?uMObu0Z*6U5Zgc=ca%Ew3 zWn>_CX>@2HM@dakSAh-}0003ZNklf9!IFPS%!Sl`KM{P~J-ySKm2g@DHwT VnC8b$MbH2M002ovPDHLkV1igNbjSbz literal 0 HcmV?d00001 diff --git a/assets/cubyz/ui/window_background.png b/assets/cubyz/ui/window_background.png new file mode 100644 index 0000000000000000000000000000000000000000..443b6e6e843b9b1bef33c88500cc66e2ec520c42 GIT binary patch literal 846 zcmV-U1F`&xP)EX>4Tx04R}tkv&MmKpe$iQ>A`X94uJGAwzYtA}Zo2RV;#q(pG5I!Q|2}Xws0R zxHt-~1qVMCs}3&Cx;nTDg5U>;o12rOinMK1=?jUdJ)X6muzVhU}?*F8LZy$kcK_UHZ_eM;73fJY>rW4d7xZxGLH zS~}-_;s7g13h_Ddm_ZjLe&o9B@*C&8!vfC?7^%cOae!FNx3Jv8tYE0b)5IY~)hJ&` zyR2~D;;fb`tZ`5NLVs3UNphX$FrrvO3~`8%Q9%(USP0Xqkzyi2`;j*OA;%vlmrSlA z7&#VDh6>5?ga5(rZq3}(q?_cA0G%(k{V@Xgc7b};w!e>UyM6+ApMfi_=`YuS*-z4| zO)Yc;^lt+f*G)~{11@)f;FB&Hk|TL(3b`Ecen#Jv0S0b?o;A0(#y(CTfE0DLd;=UD z0;74#UT^d6uGZfEJ=5s#2d5fxyp8q!VE_OC24YJ`L;yzsLjXZwmwmYa000SaNLh0L z01FcU01FcV0GgZ_00007bV*G`2jvSF3>g+yjhl-A000?uMObu0Z*6U5Zgc=ca%Ew3 zWn>_CX>@2HM@dakSAh-}0003cNkl z`vHJcdk((wSrcrZH-YnUz0!)nq^j68ff({)9we&v6#&PNW8lBfvi~-V8OGUBg9UZn zX#c_ppzE(}p4Rx98NW)FnmkojD+cA58_AVudly_yWJLm YU(W!pg~R|m5C8xG07*qoM6N<$g3bYSMF0Q* literal 0 HcmV?d00001 diff --git a/assets/cubyz/ui/window_title.png b/assets/cubyz/ui/window_title.png new file mode 100644 index 0000000000000000000000000000000000000000..0f75fc324d1edade4f8b19746fc4bd5d6c38afe9 GIT binary patch literal 706 zcmV;z0zLhSP)EX>4Tx04R}tkv&MmKpe$iQ$^8=gB^-EWT;LSL`8JdDionYs1;guFuC*#nlvOS zE{=k0!NHHks)LKOt`4q(Aou~|>f)s6A|?JWDYS_3;J6>}?mh0_0YbgZG^=9_&~)2O zCE{WxyDE0QLO=w)m_bZtmN6$uN%)Shdj$A;7vov}=l&dhYR+OnKqQ`JhG`RT5KnE| z2Iqa^Fe}O`@j3CRNf#u3C`-Nm{=^dvC_t@Xllfh#8Fk#DPPEV zta9Gstd*;*c~AbrP)=W2<~q$GB(R7jND!f*iW17O5u;Tn#X^eq<39dD*DsMvAy)~E z91EyGgY5dj|KNAGR(@jKOA5t-&KJk|7zVm_fkw@7zKZvz+CElt@2E_Z;)lOdb3D+Or^`8@D`M&FbL25y0#HLthkK29HiG7( z?|8XObUB}FKLJhWSi&ZabrJz$Q$?26?FdvRl95hkA~+saz;3O8x*b*Bjv@ph{~dC+ zpC0c)hN?2IG7c!R6u3P*e_oQjK0}yL{pnCP-BJh9wx7i6rGI3i_@% literal 0 HcmV?d00001 diff --git a/build.zig b/build.zig index 67ec218c..be86fb3b 100644 --- a/build.zig +++ b/build.zig @@ -45,8 +45,10 @@ pub fn build(b: *std.build.Builder) !void { } exe.addCSourceFiles(&[_][]const u8{"lib/glad.c", "lib/stb_image.c"}, &[_][]const u8{"-g"}); exe.addAnonymousModule("gui", .{.source_file = .{.path = "src/gui/gui.zig"}}); - exe.addModule("freetype", freetype.module(b)); - exe.addModule("harfbuzz", freetype.harfbuzzModule(b)); + const harfbuzzModule = freetype.harfbuzzModule(b); + const freetypeModule = harfbuzzModule.dependencies.get("freetype").?; + exe.addModule("harfbuzz", harfbuzzModule); + exe.addModule("freetype", freetypeModule); freetype.link(b, exe, .{ .harfbuzz = .{} }); //exe.sanitize_thread = true; exe.install(); diff --git a/src/graphics.zig b/src/graphics.zig index 81a86972..06972e37 100644 --- a/src/graphics.zig +++ b/src/graphics.zig @@ -346,6 +346,25 @@ pub const draw = struct { c.glDrawArrays(c.GL_TRIANGLE_STRIP, 0, 4); } + // ---------------------------------------------------------------------------- + + pub fn customShadedRect(uniforms: anytype, _pos: Vec2f, _dim: Vec2f) void { + var pos = _pos; + var dim = _dim; + pos *= @splat(2, scale); + pos += translation; + dim *= @splat(2, scale); + + 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.glUniform1f(uniforms.scale, scale); + + c.glBindVertexArray(rectVAO); + c.glDrawArrays(c.GL_TRIANGLE_STRIP, 0, 4); + } + // ---------------------------------------------------------------------------- pub fn text(_text: []const u8, x: f32, y: f32, fontSize: f32) !void { @@ -1220,6 +1239,35 @@ pub const TextureArray = struct { } }; +pub const Texture = struct { + textureID: c_uint, + + pub fn init() Texture { + var self: Texture = undefined; + c.glGenTextures(1, &self.textureID); + return self; + } + + pub fn deinit(self: Texture) void { + c.glDeleteTextures(1, &self.textureID); + } + + pub fn bind(self: Texture) void { + c.glBindTexture(c.GL_TEXTURE_2D, self.textureID); + } + + /// (Re-)Generates the GPU buffer. + pub fn generate(self: Texture, image: Image) !void { + self.bind(); + + c.glTexImage2D(c.GL_TEXTURE_2D, 0, c.GL_RGBA8, image.width, image.height, 0, c.GL_RGBA, c.GL_UNSIGNED_BYTE, image.imageData.ptr); + c.glTexParameteri(c.GL_TEXTURE_2D, c.GL_TEXTURE_MIN_FILTER, c.GL_NEAREST); + c.glTexParameteri(c.GL_TEXTURE_2D, c.GL_TEXTURE_MAG_FILTER, c.GL_NEAREST); + c.glTexParameteri(c.GL_TEXTURE_2D, c.GL_TEXTURE_WRAP_S, c.GL_REPEAT); + c.glTexParameteri(c.GL_TEXTURE_2D, c.GL_TEXTURE_WRAP_T, c.GL_REPEAT); + } +}; + pub const Color = extern struct { r: u8, g: u8, diff --git a/src/gui/GuiWindow.zig b/src/gui/GuiWindow.zig index cb674593..7663cfd0 100644 --- a/src/gui/GuiWindow.zig +++ b/src/gui/GuiWindow.zig @@ -4,6 +4,9 @@ const Allocator = std.mem.Allocator; const main = @import("root"); const graphics = main.graphics; const draw = graphics.draw; +const Image = graphics.Image; +const Shader = graphics.Shader; +const Texture = graphics.Texture; const settings = main.settings; const vec = main.vec; const Vec2f = vec.Vec2f; @@ -66,6 +69,43 @@ onCloseFn: *const fn()void = &defaultFunction, var grabPosition: ?Vec2f = null; var selfPositionWhenGrabbed: Vec2f = undefined; +var backgroundTexture: Texture = undefined; +var titleTexture: Texture = undefined; +var shader: Shader = undefined; +var windowUniforms: struct { + screen: c_int, + start: c_int, + size: c_int, + color: c_int, + scale: c_int, + + image: c_int, + randomOffset: c_int, +} = undefined; + +pub fn __init() !void { + shader = try Shader.create("assets/cubyz/shaders/ui/button.vs", "assets/cubyz/shaders/ui/button.fs"); + windowUniforms = shader.bulkGetUniformLocation(@TypeOf(windowUniforms)); + shader.bind(); + graphics.c.glUniform1i(windowUniforms.image, 0); + + backgroundTexture = Texture.init(); + const backgroundImage = try Image.readFromFile(main.threadAllocator, "assets/cubyz/ui/window_background.png"); + defer backgroundImage.deinit(main.threadAllocator); + try backgroundTexture.generate(backgroundImage); + + titleTexture = Texture.init(); + const titleImage = try Image.readFromFile(main.threadAllocator, "assets/cubyz/ui/window_title.png"); + defer titleImage.deinit(main.threadAllocator); + try titleTexture.generate(titleImage); +} + +pub fn __deinit() void { + shader.delete(); + backgroundTexture.deinit(); + titleTexture.deinit(); +} + pub fn defaultFunction() void {} pub fn defaultErrorFunction() Allocator.Error!void {} @@ -351,21 +391,31 @@ pub fn render(self: *const GuiWindow) !void { mousePosition /= @splat(2, scale); const oldTranslation = draw.setTranslation(self.pos); const oldScale = draw.setScale(scale); + draw.setColor(0xff000000); + graphics.c.glActiveTexture(graphics.c.GL_TEXTURE0); + shader.bind(); + backgroundTexture.bind(); + draw.customShadedRect(windowUniforms, .{0, 0}, self.size); for(self.components) |*component| { try component.render(mousePosition); } + if(self.showTitleBar) { + graphics.c.glActiveTexture(graphics.c.GL_TEXTURE0); + shader.bind(); + titleTexture.bind(); + if(self == gui.selectedWindow) { + draw.setColor(0xff000040); + } else { + draw.setColor(0xff000000); + } + draw.customShadedRect(windowUniforms, .{0, 0}, .{self.size[0], 16}); + } draw.restoreTranslation(oldTranslation); draw.restoreScale(oldScale); if(self.showTitleBar) { var text = try graphics.TextBuffer.init(main.threadAllocator, self.title, .{}, false); defer text.deinit(); const titleDimension = try text.calculateLineBreaks(16*scale, self.size[0]*scale); - if(self == gui.selectedWindow) { - draw.setColor(0xff80b080); - } else { - draw.setColor(0xffb08080); - } - draw.rect(self.pos, Vec2f{self.size[0]*scale, titleDimension[1]}); try text.render(self.pos[0] + self.size[0]*scale/2 - titleDimension[0]/2, self.pos[1], 16*scale); } if(self == gui.selectedWindow and grabPosition != null) { diff --git a/src/gui/components/Button.zig b/src/gui/components/Button.zig index d926f830..d84c7ccd 100644 --- a/src/gui/components/Button.zig +++ b/src/gui/components/Button.zig @@ -8,6 +8,7 @@ const Image = graphics.Image; const Shader = graphics.Shader; const TextBuffer = graphics.TextBuffer; const Texture = graphics.Texture; +const random = main.random; const vec = main.vec; const Vec2f = vec.Vec2f; @@ -19,15 +20,50 @@ const Button = @This(); const border: f32 = 3; const fontSize: f32 = 16; +var texture: Texture = undefined; +pub var shader: Shader = undefined; +var buttonUniforms: struct { + screen: c_int, + start: c_int, + size: c_int, + color: c_int, + scale: c_int, + + image: c_int, + pressed: c_int, + randomOffset: c_int, +} = undefined; + pressed: bool = false, onAction: *const fn() void, text: TextBuffer, textSize: Vec2f = undefined, +randomOffset: Vec2f = undefined, + +pub fn __init() !void { + shader = try Shader.create("assets/cubyz/shaders/ui/button.vs", "assets/cubyz/shaders/ui/button.fs"); + buttonUniforms = shader.bulkGetUniformLocation(@TypeOf(buttonUniforms)); + shader.bind(); + graphics.c.glUniform1i(buttonUniforms.image, 0); + texture = Texture.init(); + const image = try Image.readFromFile(main.threadAllocator, "assets/cubyz/ui/button.png"); + defer image.deinit(main.threadAllocator); + try texture.generate(image); +} + +pub fn __deinit() void { + shader.delete(); + texture.deinit(); +} pub fn init(pos: Vec2f, width: f32, allocator: Allocator, text: []const u8, onAction: *const fn() void) Allocator.Error!GuiComponent { var self = Button { .onAction = onAction, .text = try TextBuffer.init(allocator, text, .{}, false), + .randomOffset = Vec2f{ + random.nextFloat(&main.seed), + random.nextFloat(&main.seed), + }, }; self.textSize = try self.text.calculateLineBreaks(fontSize, width - 3*border); return GuiComponent { @@ -54,49 +90,21 @@ pub fn mainButtonReleased(self: *Button, component: *const GuiComponent, mousePo } } -const buttonColor: [5]u32 = [_]u32{ - 0xff9ca6bf, // center - 0xffa6b0cc, // top - 0xffa0aac4, // right - 0xff919ab3, // bottom - 0xff97a1ba, // left -}; - -const buttonPressedColor: [5]u32 = [_]u32{ - 0xff929ab3, // center - 0xff878fa6, // top - 0xff8e96ad, // right - 0xff9ca5bf, // bottom - 0xff969fb8, // left -}; - -const buttonHoveredColor: [5]u32 = [_]u32{ - 0xff9ca6dd, // center - 0xffa6b0ea, // top - 0xffa0aae2, // right - 0xff919ad1, // bottom - 0xff97a1d8, // left -}; - pub fn render(self: *Button, component: *const GuiComponent, mousePosition: Vec2f) !void { - // TODO: I should really just add a proper button texture. - var colors = &buttonColor; - if(component.contains(mousePosition)) { - colors = &buttonHoveredColor; - } + graphics.c.glActiveTexture(graphics.c.GL_TEXTURE0); + texture.bind(); + shader.bind(); + graphics.c.glUniform2f(buttonUniforms.randomOffset, self.randomOffset[0], self.randomOffset[1]); + graphics.c.glUniform1i(buttonUniforms.pressed, 0); if(self.pressed) { - colors = &buttonPressedColor; + draw.setColor(0xff000000); + graphics.c.glUniform1i(buttonUniforms.pressed, 1); + } else if(component.contains(mousePosition)) { + draw.setColor(0xff000040); + } else { + draw.setColor(0xff000000); } - draw.setColor(colors[0]); - draw.rect(component.pos + @splat(2, border), component.size - @splat(2, 2*border)); - draw.setColor(colors[1]); - draw.rect(component.pos, Vec2f{component.size[0] - border, border}); - draw.setColor(colors[2]); - draw.rect(component.pos + Vec2f{component.size[0] - border, 0}, Vec2f{border, component.size[1] - border}); - draw.setColor(colors[3]); - draw.rect(component.pos + Vec2f{border, component.size[1] - border}, Vec2f{component.size[0] - border, border}); - draw.setColor(colors[4]); - draw.rect(component.pos + Vec2f{0, border}, Vec2f{border, component.size[1] - border}); + draw.customShadedRect(buttonUniforms, component.pos, component.size); const textPos = component.pos + component.size/@splat(2, @as(f32, 2.0)) - self.textSize/@splat(2, @as(f32, 2.0)); try self.text.render(textPos[0], textPos[1], fontSize); } \ No newline at end of file diff --git a/src/gui/gui.zig b/src/gui/gui.zig index 4e7967ac..4750c26a 100644 --- a/src/gui/gui.zig +++ b/src/gui/gui.zig @@ -7,6 +7,7 @@ const settings = main.settings; const vec = main.vec; const Vec2f = vec.Vec2f; +const Button = @import("components/Button.zig"); pub const GuiComponent = @import("GuiComponent.zig"); pub const GuiWindow = @import("GuiWindow.zig"); @@ -24,6 +25,8 @@ pub fn init() !void { inline for(@typeInfo(windowlist).Struct.decls) |decl| { try @field(windowlist, decl.name).init(); } + try GuiWindow.__init(); + try Button.__init(); } pub fn deinit() void { @@ -33,6 +36,8 @@ pub fn deinit() void { window.onCloseFn(); } openWindows.deinit(); + GuiWindow.__deinit(); + Button.__deinit(); } pub fn addWindow(window: *GuiWindow, isHudWindow: bool) !void { diff --git a/src/main.zig b/src/main.zig index a57aaa09..be905f9a 100644 --- a/src/main.zig +++ b/src/main.zig @@ -174,7 +174,7 @@ pub const Window = struct { } } fn glDebugOutput(_: c_uint, _: c_uint, _: c_uint, severity: c_uint, length: c_int, message: [*c]const u8, _: ?*const anyopaque) callconv(.C) void { - if(severity == c.GL_DEBUG_SEVERITY_HIGH) { + if(severity == c.GL_DEBUG_SEVERITY_HIGH) { // TODO: Capture the stack traces. std.log.err("OpenGL {}:{s}", .{severity, message[0..@intCast(usize, length)]}); } else if(severity == c.GL_DEBUG_SEVERITY_MEDIUM) { std.log.warn("OpenGL {}:{s}", .{severity, message[0..@intCast(usize, length)]});