Use a 9-slice texture to draw a button outline.
fixes #379 Also using separate texture for the selection/pressed state of buttons and checkboxes to allow more artistic freedom.
@ -9,6 +9,8 @@ flat out vec4 fColor;
|
||||
uniform vec2 start;
|
||||
uniform vec2 size;
|
||||
uniform vec2 screen;
|
||||
uniform vec2 uvOffset;
|
||||
uniform vec2 uvDim;
|
||||
|
||||
uniform int color;
|
||||
|
||||
@ -22,5 +24,5 @@ void main() {
|
||||
gl_Position = vec4(position, 0, 1);
|
||||
|
||||
fColor = vec4((color & 0xff0000)>>16, (color & 0xff00)>>8, color & 0xff, (color>>24) & 255)/255.0;
|
||||
uv = vertex_pos;
|
||||
uv = uvOffset + vertex_pos*uvDim;
|
||||
}
|
9
assets/cubyz/shaders/graphics/RectBorder.fs
Normal file
@ -0,0 +1,9 @@
|
||||
#version 330
|
||||
|
||||
layout (location=0) out vec4 frag_color;
|
||||
|
||||
flat in vec4 color;
|
||||
|
||||
void main(){
|
||||
frag_color = color;
|
||||
}
|
27
assets/cubyz/shaders/graphics/RectBorder.vs
Normal file
@ -0,0 +1,27 @@
|
||||
#version 330 core
|
||||
|
||||
layout (location=0) in vec4 vertex_pos;
|
||||
|
||||
flat out vec4 color;
|
||||
|
||||
|
||||
//in pixel
|
||||
uniform vec2 start;
|
||||
uniform vec2 size;
|
||||
uniform vec2 screen;
|
||||
uniform float lineWidth;
|
||||
|
||||
uniform int rectColor;
|
||||
|
||||
|
||||
void main() {
|
||||
|
||||
// Convert to opengl coordinates:
|
||||
vec2 position_percentage = (start + vertex_pos.xy*size + vertex_pos.zw*lineWidth)/screen;
|
||||
|
||||
vec2 position = vec2(position_percentage.x, -position_percentage.y)*2+vec2(-1, 1);
|
||||
|
||||
gl_Position = vec4(position, 0, 1);
|
||||
|
||||
color = vec4((rectColor & 0xff0000)>>16, (rectColor & 0xff00)>>8, rectColor & 0xff, (rectColor>>24) & 255)/255.0;;
|
||||
}
|
@ -6,14 +6,10 @@ uniform sampler2D image;
|
||||
flat in vec4 fColor;
|
||||
in vec2 startCoord;
|
||||
|
||||
uniform bool pressed;
|
||||
uniform float scale;
|
||||
|
||||
void main() {
|
||||
frag_color = texture(image, (gl_FragCoord.xy - startCoord)/(2*scale)/textureSize(image, 0));
|
||||
frag_color.a *= fColor.a;
|
||||
frag_color.rgb += fColor.rgb;
|
||||
if(pressed) {
|
||||
frag_color.rgb *= 0.8;
|
||||
}
|
||||
}
|
BIN
assets/cubyz/ui/box_hovered.png
Normal file
After Width: | Height: | Size: 5.0 KiB |
BIN
assets/cubyz/ui/box_pressed.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
Before Width: | Height: | Size: 263 B After Width: | Height: | Size: 8.2 KiB |
BIN
assets/cubyz/ui/button_hovered.png
Normal file
After Width: | Height: | Size: 8.5 KiB |
BIN
assets/cubyz/ui/button_hovered_outline.png
Normal file
After Width: | Height: | Size: 7.1 KiB |
BIN
assets/cubyz/ui/button_outline.png
Normal file
After Width: | Height: | Size: 7.3 KiB |
BIN
assets/cubyz/ui/button_pressed.png
Normal file
After Width: | Height: | Size: 7.9 KiB |
BIN
assets/cubyz/ui/button_pressed_outline.png
Normal file
After Width: | Height: | Size: 7.1 KiB |
BIN
assets/cubyz/ui/checked_box_hovered.png
Normal file
After Width: | Height: | Size: 5.2 KiB |
BIN
assets/cubyz/ui/checked_box_pressed.png
Normal file
After Width: | Height: | Size: 5.1 KiB |
@ -396,6 +396,8 @@ pub const draw = struct {
|
||||
size: c_int,
|
||||
image: c_int,
|
||||
color: c_int,
|
||||
uvOffset: c_int,
|
||||
uvDim: c_int,
|
||||
} = undefined;
|
||||
var imageShader: Shader = undefined;
|
||||
|
||||
@ -422,6 +424,30 @@ pub const draw = struct {
|
||||
c.glUniform2f(imageUniforms.start, pos[0], pos[1]);
|
||||
c.glUniform2f(imageUniforms.size, dim[0], dim[1]);
|
||||
c.glUniform1i(imageUniforms.color, @bitCast(color));
|
||||
c.glUniform2f(imageUniforms.uvOffset, 0, 0);
|
||||
c.glUniform2f(imageUniforms.uvDim, 1, 1);
|
||||
|
||||
c.glBindVertexArray(rectVAO);
|
||||
c.glDrawArrays(c.GL_TRIANGLE_STRIP, 0, 4);
|
||||
}
|
||||
|
||||
pub fn boundSubImage(_pos: Vec2f, _dim: Vec2f, uvOffset: Vec2f, uvDim: Vec2f) void {
|
||||
var pos = _pos;
|
||||
var dim = _dim;
|
||||
pos *= @splat(scale);
|
||||
pos += translation;
|
||||
dim *= @splat(scale);
|
||||
pos = @floor(pos);
|
||||
dim = @ceil(dim);
|
||||
|
||||
imageShader.bind();
|
||||
|
||||
c.glUniform2f(imageUniforms.screen, @floatFromInt(Window.width), @floatFromInt(Window.height));
|
||||
c.glUniform2f(imageUniforms.start, pos[0], pos[1]);
|
||||
c.glUniform2f(imageUniforms.size, dim[0], dim[1]);
|
||||
c.glUniform1i(imageUniforms.color, @bitCast(color));
|
||||
c.glUniform2f(imageUniforms.uvOffset, uvOffset[0], 1 - uvOffset[1] - uvDim[1]);
|
||||
c.glUniform2f(imageUniforms.uvDim, uvDim[0], uvDim[1]);
|
||||
|
||||
c.glBindVertexArray(rectVAO);
|
||||
c.glDrawArrays(c.GL_TRIANGLE_STRIP, 0, 4);
|
||||
@ -1697,6 +1723,14 @@ pub const Texture = struct {
|
||||
self.bindTo(0);
|
||||
draw.boundImage(pos, dim);
|
||||
}
|
||||
|
||||
pub fn size(self: Texture) Vec2i {
|
||||
self.bind();
|
||||
var result: Vec2i = undefined;
|
||||
c.glGetTexLevelParameteriv(c.GL_TEXTURE_2D, 0, c.GL_TEXTURE_WIDTH, &result[0]);
|
||||
c.glGetTexLevelParameteriv(c.GL_TEXTURE_2D, 0, c.GL_TEXTURE_HEIGHT, &result[1]);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
pub const CubeMapTexture = struct {
|
||||
|
@ -19,7 +19,31 @@ const Button = @This();
|
||||
const border: f32 = 3;
|
||||
const fontSize: f32 = 16;
|
||||
|
||||
var texture: Texture = undefined;
|
||||
const Textures = struct {
|
||||
texture: Texture,
|
||||
outlineTexture: Texture,
|
||||
outlineTextureSize: Vec2f,
|
||||
|
||||
pub fn init(basePath: []const u8) Textures {
|
||||
var self: Textures = undefined;
|
||||
const buttonPath = std.fmt.allocPrint(main.stackAllocator.allocator, "{s}.png", .{basePath}) catch unreachable;
|
||||
defer main.stackAllocator.free(buttonPath);
|
||||
self.texture = Texture.initFromFile(buttonPath);
|
||||
const outlinePath = std.fmt.allocPrint(main.stackAllocator.allocator, "{s}_outline.png", .{basePath}) catch unreachable;
|
||||
defer main.stackAllocator.free(outlinePath);
|
||||
self.outlineTexture = Texture.initFromFile(outlinePath);
|
||||
self.outlineTextureSize = @floatFromInt(self.outlineTexture.size());
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn deinit(self: Textures) void {
|
||||
self.texture.deinit();
|
||||
self.outlineTexture.deinit();
|
||||
}
|
||||
};
|
||||
var normalTextures: Textures = undefined;
|
||||
var hoveredTextures: Textures = undefined;
|
||||
var pressedTextures: Textures = undefined;
|
||||
pub var shader: Shader = undefined;
|
||||
pub var buttonUniforms: struct {
|
||||
screen: c_int,
|
||||
@ -29,7 +53,6 @@ pub var buttonUniforms: struct {
|
||||
scale: c_int,
|
||||
|
||||
image: c_int,
|
||||
pressed: c_int,
|
||||
} = undefined;
|
||||
|
||||
pos: Vec2f,
|
||||
@ -43,12 +66,16 @@ pub fn __init() void {
|
||||
shader = Shader.initAndGetUniforms("assets/cubyz/shaders/ui/button.vs", "assets/cubyz/shaders/ui/button.fs", &buttonUniforms);
|
||||
shader.bind();
|
||||
graphics.c.glUniform1i(buttonUniforms.image, 0);
|
||||
texture = Texture.initFromFile("assets/cubyz/ui/button.png");
|
||||
normalTextures = Textures.init("assets/cubyz/ui/button");
|
||||
hoveredTextures = Textures.init("assets/cubyz/ui/button_hovered");
|
||||
pressedTextures = Textures.init("assets/cubyz/ui/button_pressed");
|
||||
}
|
||||
|
||||
pub fn __deinit() void {
|
||||
shader.deinit();
|
||||
texture.deinit();
|
||||
normalTextures.deinit();
|
||||
hoveredTextures.deinit();
|
||||
pressedTextures.deinit();
|
||||
}
|
||||
|
||||
fn defaultOnAction(_: usize) void {}
|
||||
@ -106,20 +133,33 @@ pub fn mainButtonReleased(self: *Button, mousePosition: Vec2f) void {
|
||||
}
|
||||
|
||||
pub fn render(self: *Button, mousePosition: Vec2f) void {
|
||||
texture.bindTo(0);
|
||||
const textures = if(self.pressed)
|
||||
pressedTextures
|
||||
else if(GuiComponent.contains(self.pos, self.size, mousePosition) and self.hovered)
|
||||
hoveredTextures
|
||||
else normalTextures;
|
||||
textures.texture.bindTo(0);
|
||||
shader.bind();
|
||||
graphics.c.glUniform1i(buttonUniforms.pressed, 0);
|
||||
if(self.pressed) {
|
||||
draw.setColor(0xff000000);
|
||||
graphics.c.glUniform1i(buttonUniforms.pressed, 1);
|
||||
} else if(GuiComponent.contains(self.pos, self.size, mousePosition) and self.hovered) {
|
||||
draw.setColor(0xff000040);
|
||||
} else {
|
||||
draw.setColor(0xff000000);
|
||||
}
|
||||
self.hovered = false;
|
||||
draw.customShadedRect(buttonUniforms, self.pos, self.size);
|
||||
graphics.c.glUniform1i(buttonUniforms.pressed, 0);
|
||||
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);
|
||||
draw.setColor(0xffffffff);
|
||||
// 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 textPos = self.pos + self.size/@as(Vec2f, @splat(2.0)) - self.child.size()/@as(Vec2f, @splat(2.0));
|
||||
self.child.mutPos().* = textPos;
|
||||
self.child.render(mousePosition - self.pos);
|
||||
|
@ -21,8 +21,12 @@ const border: f32 = 3;
|
||||
const fontSize: f32 = 16;
|
||||
const boxSize: f32 = 16;
|
||||
|
||||
var textureChecked: Texture = undefined;
|
||||
var textureEmpty: Texture = undefined;
|
||||
var textureCheckedNormal: Texture = undefined;
|
||||
var textureCheckedHovered: Texture = undefined;
|
||||
var textureCheckedPressed: Texture = undefined;
|
||||
var textureEmptyNormal: Texture = undefined;
|
||||
var textureEmptyHovered: Texture = undefined;
|
||||
var textureEmptyPressed: Texture = undefined;
|
||||
|
||||
pos: Vec2f,
|
||||
size: Vec2f,
|
||||
@ -33,13 +37,21 @@ onAction: *const fn(bool) void,
|
||||
label: *Label,
|
||||
|
||||
pub fn __init() void {
|
||||
textureChecked = Texture.initFromFile("assets/cubyz/ui/checked_box.png");
|
||||
textureEmpty = Texture.initFromFile("assets/cubyz/ui/box.png");
|
||||
textureCheckedNormal = Texture.initFromFile("assets/cubyz/ui/checked_box.png");
|
||||
textureCheckedHovered = Texture.initFromFile("assets/cubyz/ui/checked_box_hovered.png");
|
||||
textureCheckedPressed = Texture.initFromFile("assets/cubyz/ui/checked_box_pressed.png");
|
||||
textureEmptyNormal = Texture.initFromFile("assets/cubyz/ui/box.png");
|
||||
textureEmptyHovered = Texture.initFromFile("assets/cubyz/ui/box_hovered.png");
|
||||
textureEmptyPressed = Texture.initFromFile("assets/cubyz/ui/box_pressed.png");
|
||||
}
|
||||
|
||||
pub fn __deinit() void {
|
||||
textureChecked.deinit();
|
||||
textureEmpty.deinit();
|
||||
textureCheckedNormal.deinit();
|
||||
textureCheckedHovered.deinit();
|
||||
textureCheckedPressed.deinit();
|
||||
textureEmptyNormal.deinit();
|
||||
textureEmptyHovered.deinit();
|
||||
textureEmptyPressed.deinit();
|
||||
}
|
||||
|
||||
pub fn init(pos: Vec2f, width: f32, text: []const u8, initialValue: bool, onAction: *const fn(bool) void) *CheckBox {
|
||||
@ -86,23 +98,25 @@ pub fn mainButtonReleased(self: *CheckBox, mousePosition: Vec2f) void {
|
||||
|
||||
pub fn render(self: *CheckBox, mousePosition: Vec2f) void {
|
||||
if(self.state) {
|
||||
textureChecked.bindTo(0);
|
||||
if(self.pressed) {
|
||||
textureCheckedPressed.bindTo(0);
|
||||
} else if(GuiComponent.contains(self.pos, self.size, mousePosition) and self.hovered) {
|
||||
textureCheckedHovered.bindTo(0);
|
||||
} else {
|
||||
textureCheckedNormal.bindTo(0);
|
||||
}
|
||||
} else {
|
||||
textureEmpty.bindTo(0);
|
||||
if(self.pressed) {
|
||||
textureEmptyPressed.bindTo(0);
|
||||
} else if(GuiComponent.contains(self.pos, self.size, mousePosition) and self.hovered) {
|
||||
textureEmptyHovered.bindTo(0);
|
||||
} else {
|
||||
textureEmptyNormal.bindTo(0);
|
||||
}
|
||||
}
|
||||
Button.shader.bind();
|
||||
graphics.c.glUniform1i(Button.buttonUniforms.pressed, 0);
|
||||
if(self.pressed) {
|
||||
draw.setColor(0xff000000);
|
||||
graphics.c.glUniform1i(Button.buttonUniforms.pressed, 1);
|
||||
} else if(GuiComponent.contains(self.pos, self.size, mousePosition) and self.hovered) {
|
||||
draw.setColor(0xff000040);
|
||||
} else {
|
||||
draw.setColor(0xff000000);
|
||||
}
|
||||
self.hovered = false;
|
||||
draw.customShadedRect(Button.buttonUniforms, self.pos + Vec2f{0, self.size[1]/2 - boxSize/2}, @as(Vec2f, @splat(boxSize)));
|
||||
graphics.c.glUniform1i(Button.buttonUniforms.pressed, 0);
|
||||
const textPos = self.pos + Vec2f{boxSize/2, 0} + self.size/@as(Vec2f, @splat(2.0)) - self.label.size/@as(Vec2f, @splat(2.0));
|
||||
self.label.pos = textPos;
|
||||
self.label.render(mousePosition - textPos);
|
||||
|