diff --git a/assets/cubyz/ui/tooltip_background.png b/assets/cubyz/ui/tooltip_background.png new file mode 100644 index 0000000000..a0c1d5307f Binary files /dev/null and b/assets/cubyz/ui/tooltip_background.png differ diff --git a/src/graphics.zig b/src/graphics.zig index 90264913c2..bb1f852881 100644 --- a/src/graphics.zig +++ b/src/graphics.zig @@ -470,6 +470,30 @@ pub const draw = struct { // MARK: draw c.glDrawArrays(c.GL_TRIANGLE_STRIP, 0, 4); } + fn drawSlice(destMin: Vec2f, destMax: Vec2f, uvMin: Vec2f, uvMax: Vec2f) void { + boundSubImage(destMin, destMax - destMin, uvMin, uvMax - uvMin); + } + + pub fn bound9SliceImage(pos: Vec2f, dim: Vec2f, textureSize: Vec2f, slices: Vec2f, sliceScale: f32) void { + const widthSlice = slices[0]*sliceScale; + const heightSlice = slices[1]*sliceScale; + + // Construct UV + const u: Vec2f = .{slices[0]/textureSize[0], (textureSize[0] - slices[0])/textureSize[0]}; + const v: Vec2f = .{slices[1]/textureSize[1], (textureSize[1] - slices[1])/textureSize[1]}; + + // Draw all Slices + drawSlice(.{pos[0], pos[1]}, .{pos[0] + widthSlice, pos[1] + heightSlice}, .{0, 0}, .{u[0], v[0]}); + drawSlice(.{pos[0] + widthSlice, pos[1]}, .{pos[0] + dim[0] - widthSlice, pos[1] + heightSlice}, .{u[0], 0}, .{u[1], v[0]}); + drawSlice(.{pos[0] + dim[0] - widthSlice, pos[1]}, .{pos[0] + dim[0], pos[1] + heightSlice}, .{u[1], 0}, .{1, v[0]}); + drawSlice(.{pos[0], pos[1] + heightSlice}, .{pos[0] + widthSlice, pos[1] + dim[1] - heightSlice}, .{0, v[0]}, .{u[0], v[1]}); + drawSlice(.{pos[0] + widthSlice, pos[1] + heightSlice}, .{pos[0] + dim[0] - widthSlice, pos[1] + dim[1] - heightSlice}, .{u[0], v[0]}, .{u[1], v[1]}); + drawSlice(.{pos[0] + dim[0] - widthSlice, pos[1] + heightSlice}, .{pos[0] + dim[0], pos[1] + dim[1] - heightSlice}, .{u[1], v[0]}, .{1, v[1]}); + drawSlice(.{pos[0], pos[1] + dim[1] - heightSlice}, .{pos[0] + widthSlice, pos[1] + dim[1]}, .{0, v[1]}, .{u[0], 1}); + drawSlice(.{pos[0] + widthSlice, pos[1] + dim[1] - heightSlice}, .{pos[0] + dim[0] - widthSlice, pos[1] + dim[1]}, .{u[0], v[1]}, .{u[1], 1}); + drawSlice(.{pos[0] + dim[0] - widthSlice, pos[1] + dim[1] - heightSlice}, .{pos[0] + dim[0], pos[1] + dim[1]}, .{u[1], v[1]}, .{1, 1}); + } + pub fn customShadedImage(uniforms: anytype, _pos: Vec2f, _dim: Vec2f) void { var pos = _pos; var dim = _dim; diff --git a/src/gui/components/Button.zig b/src/gui/components/Button.zig index 9e00bf11f9..a6ad6de5ff 100644 --- a/src/gui/components/Button.zig +++ b/src/gui/components/Button.zig @@ -162,23 +162,12 @@ pub fn render(self: *Button, mousePosition: Vec2f) void { self.hovered = false; draw.customShadedRect(buttonUniforms, self.pos + Vec2f{2, 2}, self.size - Vec2f{4, 4}); } - { // Draw the outline using the 9-slice texture. - const cornerSize = (textures.outlineTextureSize - Vec2f{1, 1}); - const cornerSizeUV = (textures.outlineTextureSize - Vec2f{1, 1})/Vec2f{2, 2}/textures.outlineTextureSize; - const lowerTexture = (textures.outlineTextureSize - Vec2f{1, 1})/Vec2f{2, 2}/textures.outlineTextureSize; - const upperTexture = (textures.outlineTextureSize + Vec2f{1, 1})/Vec2f{2, 2}/textures.outlineTextureSize; - textures.outlineTexture.bindTo(0); - // Corners: - graphics.draw.boundSubImage(self.pos + Vec2f{0, 0}, cornerSize, .{0, 0}, cornerSizeUV); - graphics.draw.boundSubImage(self.pos + Vec2f{self.size[0], 0} - Vec2f{cornerSize[0], 0}, cornerSize, .{upperTexture[0], 0}, cornerSizeUV); - graphics.draw.boundSubImage(self.pos + Vec2f{0, self.size[1]} - Vec2f{0, cornerSize[1]}, cornerSize, .{0, upperTexture[1]}, cornerSizeUV); - graphics.draw.boundSubImage(self.pos + self.size - cornerSize, cornerSize, upperTexture, cornerSizeUV); - // Edges: - graphics.draw.boundSubImage(self.pos + Vec2f{cornerSize[0], 0}, Vec2f{self.size[0] - 2*cornerSize[0], cornerSize[1]}, .{lowerTexture[0], 0}, .{upperTexture[0] - lowerTexture[0], cornerSizeUV[1]}); - graphics.draw.boundSubImage(self.pos + Vec2f{cornerSize[0], self.size[1] - cornerSize[1]}, Vec2f{self.size[0] - 2*cornerSize[0], cornerSize[1]}, .{lowerTexture[0], upperTexture[1]}, .{upperTexture[0] - lowerTexture[0], cornerSizeUV[1]}); - graphics.draw.boundSubImage(self.pos + Vec2f{0, cornerSize[1]}, Vec2f{cornerSize[0], self.size[1] - 2*cornerSize[1]}, .{0, lowerTexture[1]}, .{cornerSizeUV[0], upperTexture[1] - lowerTexture[1]}); - graphics.draw.boundSubImage(self.pos + Vec2f{self.size[0] - cornerSize[0], cornerSize[1]}, Vec2f{cornerSize[0], self.size[1] - 2*cornerSize[1]}, .{upperTexture[0], lowerTexture[1]}, .{cornerSizeUV[0], upperTexture[1] - lowerTexture[1]}); - } + + const cornerSize = (textures.outlineTextureSize - Vec2f{1, 1})/Vec2f{2, 2}; + + textures.outlineTexture.bindTo(0); + graphics.draw.bound9SliceImage(self.pos, self.size, textures.outlineTextureSize, cornerSize, 2); + const oldColor = draw.setColor(if (self.disabled) 0xff808080 else 0xffffffff); defer draw.restoreColor(oldColor); const textPos = self.pos + self.size/@as(Vec2f, @splat(2.0)) - self.child.size()/@as(Vec2f, @splat(2.0)); diff --git a/src/gui/gui.zig b/src/gui/gui.zig index ffbaa588ea..88af8bf9a8 100644 --- a/src/gui/gui.zig +++ b/src/gui/gui.zig @@ -23,6 +23,7 @@ const gui_component = @import("gui_component.zig"); pub const GuiComponent = gui_component.GuiComponent; pub const GuiWindow = @import("GuiWindow.zig"); +pub const tooltip = @import("tooltip.zig"); pub const windowlist = @import("windows/_list.zig"); const gamepad_cursor = @import("gamepad_cursor.zig"); @@ -147,6 +148,7 @@ pub fn init() void { // MARK: init() ContinuousSlider.globalInit(); DiscreteSlider.globalInit(); TextInput.globalInit(); + tooltip.globalInit(); load(); gamepad_cursor.init(); } @@ -167,6 +169,7 @@ pub fn deinit() void { ContinuousSlider.globalDeinit(); DiscreteSlider.globalDeinit(); TextInput.globalDeinit(); + tooltip.globalDeinit(); inline for (@typeInfo(windowlist).@"struct".decls) |decl| { const WindowStruct = @field(windowlist, decl.name); if (@hasDecl(WindowStruct, "deinit")) { @@ -784,38 +787,8 @@ pub const inventory = struct { // MARK: inventory // Draw tooltip: const hovered = hoveredItemSlot orelse return; if (carried.getAmount(0) == 0) { - if (hovered.inventory.getItem(hovered.itemSlot).getTooltip()) |tooltip| { - var textBuffer = graphics.TextBuffer.init(main.stackAllocator, tooltip, .{}, false, .left); - defer textBuffer.deinit(); - const fontSize = 16; - var size = textBuffer.calculateLineBreaks(fontSize, 300); - size[0] = 0; - for (textBuffer.lineBreaks.items) |lineBreak| { - size[0] = @max(size[0], lineBreak.width); - } - const windowSize = main.Window.getWindowSize()/@as(Vec2f, @splat(scale)); - const xOffset = 18; - const padding: f32 = 1; - const border: f32 = padding + 1; - var pos = mousePos; - if (pos[0] + size[0] + border + xOffset >= windowSize[0]) { - pos[0] -= size[0] + xOffset; - } else { - pos[0] += xOffset; - } - pos[1] = @min(pos[1] - fontSize, windowSize[1] - size[1] - border); - pos = @max(pos, Vec2f{border, border}); - { - const oldColor = draw.setColor(0xffffff00); - defer draw.restoreColor(oldColor); - draw.rect(pos - @as(Vec2f, @splat(border)), size + @as(Vec2f, @splat(2*border))); - } - { - const oldColor = draw.setColor(0xff000000); - defer draw.restoreColor(oldColor); - draw.rect(pos - @as(Vec2f, @splat(padding)), size + @as(Vec2f, @splat(2*padding))); - } - textBuffer.render(pos[0], pos[1], fontSize); + if (hovered.inventory.getItem(hovered.itemSlot).getTooltip()) |tooltipContent| { + tooltip.renderFromText(tooltipContent, mousePos); } } } diff --git a/src/gui/tooltip.zig b/src/gui/tooltip.zig new file mode 100644 index 0000000000..02a47ca45c --- /dev/null +++ b/src/gui/tooltip.zig @@ -0,0 +1,64 @@ +const std = @import("std"); + +const main = @import("main"); +const graphics = main.graphics; +const draw = graphics.draw; +const Texture = graphics.Texture; +const vec = main.vec; +const Vec2f = vec.Vec2f; +const Vec4f = vec.Vec4f; +const gui = main.gui; +const GuiComponent = gui.GuiComponent; + +var tooltipTexture: Texture = undefined; +var cornerSize: Vec2f = undefined; + +const fontSize: f32 = 16; +const offsetFromMouse: f32 = 4; + +pub fn globalInit() void { + tooltipTexture = Texture.initFromFile("assets/cubyz/ui/tooltip_background.png"); + cornerSize = (@as(Vec2f, @floatFromInt(tooltipTexture.size())) - Vec2f{1, 1})/Vec2f{2, 2}; +} + +pub fn globalDeinit() void { + tooltipTexture.deinit(); +} + +pub fn render(guiComponent: *GuiComponent, pos: Vec2f) void { + const size = guiComponent.size() + Vec2f{cornerSize[0]*2, cornerSize[1]*2}; + tooltipTexture.bindTo(0); + + const windowSize = main.Window.getWindowSize()/@as(Vec2f, @splat(gui.scale)); + var renderPos = pos + Vec2f{offsetFromMouse, 0}; + if (renderPos[0] + size[0] > windowSize[0]) { + renderPos = pos - Vec2f{size[0] + cornerSize[0]*2, 0}; + } + renderPos = @max(Vec2f{0, 0}, renderPos); + if (renderPos[1] + size[1] > windowSize[1]) { + renderPos[1] += windowSize[1] - (renderPos[1] + size[1]); + } + + draw.bound9SliceImage(renderPos, size, @floatFromInt(tooltipTexture.size()), cornerSize, 1); + + const adjustment = renderPos - guiComponent.pos() + Vec2f{cornerSize[0], cornerSize[1]}; + const oldTranslation = draw.setTranslation(adjustment); + defer draw.restoreTranslation(oldTranslation); + const oldClip = draw.setClip(guiComponent.size()); + defer draw.restoreClip(oldClip); + guiComponent.render(guiComponent.pos()); +} + +pub fn renderFromText(text: []const u8, pos: Vec2f) void { + var label = GuiComponent.Label.init(Vec2f{0, 0}, 300, text, .left); + defer label.deinit(); + var size = label.text.calculateLineBreaks(fontSize, 300); + size[0] = 0; + for (label.text.lineBreaks.items) |lineBreak| { + size[0] = @max(size[0], lineBreak.width); + } + label.size = size; + + var component = label.toComponent(); + render(&component, pos); +}