301 lines
11 KiB
Zig
301 lines
11 KiB
Zig
const std = @import("std");
|
|
const gui = @import("gui");
|
|
pub const c = @cImport({
|
|
@cInclude("SDL2/SDL.h");
|
|
});
|
|
|
|
const SDLBackend = @This();
|
|
|
|
window: *c.SDL_Window,
|
|
renderer: *c.SDL_Renderer,
|
|
cursor_last: gui.Cursor = .arrow,
|
|
cursor_backing: [@typeInfo(gui.Cursor).Enum.fields.len]?*c.SDL_Cursor = [_]?*c.SDL_Cursor{null} ** @typeInfo(gui.Cursor).Enum.fields.len,
|
|
cursor_backing_tried: [@typeInfo(gui.Cursor).Enum.fields.len]bool = [_]bool{false} ** @typeInfo(gui.Cursor).Enum.fields.len,
|
|
|
|
pub const initOptions = struct {
|
|
width: u32,
|
|
height: u32,
|
|
vsync: bool,
|
|
title: [:0]const u8,
|
|
};
|
|
|
|
pub fn init(options: initOptions) !SDLBackend {
|
|
if (c.SDL_Init(c.SDL_INIT_VIDEO) < 0) {
|
|
std.debug.print("Couldn't initialize SDL: {s}\n", .{c.SDL_GetError()});
|
|
return error.BackendError;
|
|
}
|
|
|
|
var window = c.SDL_CreateWindow(options.title, c.SDL_WINDOWPOS_UNDEFINED, c.SDL_WINDOWPOS_UNDEFINED, @intCast(c_int, options.width), @intCast(c_int, options.height), c.SDL_WINDOW_ALLOW_HIGHDPI | c.SDL_WINDOW_RESIZABLE) orelse {
|
|
std.debug.print("Failed to open window: {s}\n", .{c.SDL_GetError()});
|
|
return error.BackendError;
|
|
};
|
|
|
|
_ = c.SDL_SetHint(c.SDL_HINT_RENDER_SCALE_QUALITY, "linear");
|
|
|
|
var renderer = c.SDL_CreateRenderer(window, -1, if (options.vsync) c.SDL_RENDERER_PRESENTVSYNC else 0) orelse {
|
|
std.debug.print("Failed to create renderer: {s}\n", .{c.SDL_GetError()});
|
|
return error.BackendError;
|
|
};
|
|
|
|
_ = c.SDL_SetRenderDrawBlendMode(renderer, c.SDL_BLENDMODE_BLEND);
|
|
|
|
var back = SDLBackend{ .window = window, .renderer = renderer };
|
|
|
|
return back;
|
|
}
|
|
|
|
pub fn waitEventTimeout(_: *SDLBackend, timeout_micros: u32) void {
|
|
if (timeout_micros == std.math.maxInt(u32)) {
|
|
// wait no timeout
|
|
_ = c.SDL_WaitEvent(null);
|
|
} else if (timeout_micros > 0) {
|
|
// wait with a timeout
|
|
const timeout = std.math.min((timeout_micros + 999) / 1000, std.math.maxInt(c_int));
|
|
_ = c.SDL_WaitEventTimeout(null, @intCast(c_int, timeout));
|
|
} else {
|
|
// don't wait
|
|
}
|
|
}
|
|
|
|
pub fn refresh() void {
|
|
var ue = std.mem.zeroes(c.SDL_Event);
|
|
ue.type = c.SDL_USEREVENT;
|
|
_ = c.SDL_PushEvent(&ue);
|
|
}
|
|
|
|
pub fn addAllEvents(self: *SDLBackend, win: *gui.Window) !bool {
|
|
var event: c.SDL_Event = undefined;
|
|
while (c.SDL_PollEvent(&event) != 0) {
|
|
_ = try self.addEvent(win, event);
|
|
switch (event.type) {
|
|
c.SDL_KEYDOWN => {
|
|
if (((event.key.keysym.mod & c.KMOD_CTRL) > 0) and event.key.keysym.sym == c.SDLK_q) {
|
|
return true;
|
|
}
|
|
},
|
|
c.SDL_QUIT => {
|
|
return true;
|
|
},
|
|
else => {},
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
pub fn setCursor(self: *SDLBackend, cursor: gui.Cursor) void {
|
|
if (cursor != self.cursor_last) {
|
|
self.cursor_last = cursor;
|
|
|
|
const enum_int = @enumToInt(cursor);
|
|
const tried = self.cursor_backing_tried[enum_int];
|
|
if (!tried) {
|
|
self.cursor_backing_tried[enum_int] = true;
|
|
self.cursor_backing[enum_int] = switch (cursor) {
|
|
.arrow => c.SDL_CreateSystemCursor(c.SDL_SYSTEM_CURSOR_ARROW),
|
|
.ibeam => c.SDL_CreateSystemCursor(c.SDL_SYSTEM_CURSOR_IBEAM),
|
|
.wait => c.SDL_CreateSystemCursor(c.SDL_SYSTEM_CURSOR_WAIT),
|
|
.wait_arrow => c.SDL_CreateSystemCursor(c.SDL_SYSTEM_CURSOR_WAITARROW),
|
|
.crosshair => c.SDL_CreateSystemCursor(c.SDL_SYSTEM_CURSOR_CROSSHAIR),
|
|
.arrow_nw_se => c.SDL_CreateSystemCursor(c.SDL_SYSTEM_CURSOR_SIZENWSE),
|
|
.arrow_ne_sw => c.SDL_CreateSystemCursor(c.SDL_SYSTEM_CURSOR_SIZENESW),
|
|
.arrow_w_e => c.SDL_CreateSystemCursor(c.SDL_SYSTEM_CURSOR_SIZEWE),
|
|
.arrow_n_s => c.SDL_CreateSystemCursor(c.SDL_SYSTEM_CURSOR_SIZENS),
|
|
.arrow_all => c.SDL_CreateSystemCursor(c.SDL_SYSTEM_CURSOR_SIZEALL),
|
|
.bad => c.SDL_CreateSystemCursor(c.SDL_SYSTEM_CURSOR_NO),
|
|
.hand => c.SDL_CreateSystemCursor(c.SDL_SYSTEM_CURSOR_HAND),
|
|
};
|
|
}
|
|
|
|
if (self.cursor_backing[enum_int]) |cur| {
|
|
c.SDL_SetCursor(cur);
|
|
} else {
|
|
std.log.warn("SDL_CreateSystemCursor \"{s}\" failed\n", .{@tagName(cursor)});
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn deinit(self: *SDLBackend) void {
|
|
for (self.cursor_backing) |cursor| {
|
|
if (cursor) |cur| {
|
|
c.SDL_FreeCursor(cur);
|
|
}
|
|
}
|
|
c.SDL_DestroyRenderer(self.renderer);
|
|
c.SDL_DestroyWindow(self.window);
|
|
c.SDL_Quit();
|
|
}
|
|
|
|
pub fn renderPresent(self: *SDLBackend) void {
|
|
c.SDL_RenderPresent(self.renderer);
|
|
}
|
|
|
|
pub fn hasEvent(_: *SDLBackend) bool {
|
|
return c.SDL_PollEvent(null) == 1;
|
|
}
|
|
|
|
pub fn clear(self: *SDLBackend) void {
|
|
_ = c.SDL_SetRenderDrawColor(self.renderer, 0, 0, 0, 255);
|
|
_ = c.SDL_RenderClear(self.renderer);
|
|
}
|
|
|
|
pub fn guiBackend(self: *SDLBackend) gui.Backend {
|
|
return gui.Backend.init(self, begin, end, pixelSize, windowSize, renderGeometry, textureCreate, textureDestroy);
|
|
}
|
|
|
|
pub fn begin(_: *SDLBackend, _: std.mem.Allocator) void {}
|
|
|
|
pub fn end(_: *SDLBackend) void {}
|
|
|
|
pub fn pixelSize(self: *SDLBackend) gui.Size {
|
|
var w: i32 = undefined;
|
|
var h: i32 = undefined;
|
|
_ = c.SDL_GetRendererOutputSize(self.renderer, &w, &h);
|
|
return gui.Size{ .w = @intToFloat(f32, w), .h = @intToFloat(f32, h) };
|
|
}
|
|
|
|
pub fn windowSize(self: *SDLBackend) gui.Size {
|
|
var w: i32 = undefined;
|
|
var h: i32 = undefined;
|
|
_ = c.SDL_GetWindowSize(self.window, &w, &h);
|
|
return gui.Size{ .w = @intToFloat(f32, w), .h = @intToFloat(f32, h) };
|
|
}
|
|
|
|
pub fn renderGeometry(self: *SDLBackend, texture: ?*anyopaque, vtx: []const gui.Vertex, idx: []const u32) void {
|
|
const clipr = gui.windowRectPixels().intersect(gui.clipGet());
|
|
if (clipr.empty()) {
|
|
return;
|
|
}
|
|
|
|
//std.debug.print("renderGeometry:\n", .{});
|
|
//for (vtx) |v, i| {
|
|
// std.debug.print(" {d} vertex {}\n", .{i, v});
|
|
//}
|
|
//for (idx) |id, i| {
|
|
// std.debug.print(" {d} index {d}\n", .{i, id});
|
|
//}
|
|
|
|
// figure out how much we are losing by truncating x and y, need to add that back to w and h
|
|
const clip = c.SDL_Rect{ .x = @floatToInt(c_int, clipr.x), .y = @floatToInt(c_int, clipr.y), .w = std.math.max(0, @floatToInt(c_int, @ceil(clipr.w + clipr.x - @floor(clipr.x)))), .h = std.math.max(0, @floatToInt(c_int, @ceil(clipr.h + clipr.y - @floor(clipr.y)))) };
|
|
|
|
_ = c.SDL_RenderSetClipRect(self.renderer, &clip);
|
|
|
|
const tex = @ptrCast(?*c.SDL_Texture, texture);
|
|
|
|
_ = c.SDL_RenderGeometryRaw(self.renderer, tex, @ptrCast(*const f32, &vtx[0].pos), @sizeOf(gui.Vertex), @ptrCast(*const c.SDL_Color, @alignCast(4, &vtx[0].col)), @sizeOf(gui.Vertex), @ptrCast(*const f32, &vtx[0].uv), @sizeOf(gui.Vertex), @intCast(c_int, vtx.len), idx.ptr, @intCast(c_int, idx.len), @sizeOf(u32));
|
|
}
|
|
|
|
pub fn textureCreate(self: *SDLBackend, pixels: []u8, width: u32, height: u32) *anyopaque {
|
|
var surface = c.SDL_CreateRGBSurfaceWithFormatFrom(pixels.ptr, @intCast(c_int, width), @intCast(c_int, height), 32, @intCast(c_int, 4 * width), c.SDL_PIXELFORMAT_ABGR8888);
|
|
defer c.SDL_FreeSurface(surface);
|
|
|
|
const texture = c.SDL_CreateTextureFromSurface(self.renderer, surface) orelse unreachable;
|
|
return texture;
|
|
}
|
|
|
|
pub fn textureDestroy(_: *SDLBackend, texture: *anyopaque) void {
|
|
c.SDL_DestroyTexture(@ptrCast(*c.SDL_Texture, texture));
|
|
}
|
|
|
|
pub fn addEvent(_: *SDLBackend, win: *gui.Window, event: c.SDL_Event) !bool {
|
|
switch (event.type) {
|
|
c.SDL_KEYDOWN => {
|
|
return try win.addEventKey(.{
|
|
.code = SDL_keysym_to_gui(event.key.keysym.sym),
|
|
.action = if (event.key.repeat > 0) .repeat else .down,
|
|
.mod = SDL_keymod_to_gui(event.key.keysym.mod),
|
|
});
|
|
},
|
|
c.SDL_TEXTINPUT => {
|
|
return try win.addEventText(std.mem.sliceTo(&event.text.text, 0));
|
|
},
|
|
c.SDL_MOUSEMOTION => {
|
|
return try win.addEventMouseMotion(@intToFloat(f32, event.motion.x), @intToFloat(f32, event.motion.y));
|
|
},
|
|
c.SDL_MOUSEBUTTONDOWN => {
|
|
return try win.addEventMouseButton(.{ .press = SDL_mouse_button_to_gui(event.button.button) });
|
|
},
|
|
c.SDL_MOUSEBUTTONUP => {
|
|
return try win.addEventMouseButton(.{ .release = SDL_mouse_button_to_gui(event.button.button) });
|
|
},
|
|
c.SDL_MOUSEWHEEL => {
|
|
const ticks = @intToFloat(f32, event.wheel.y);
|
|
return try win.addEventMouseWheel(ticks);
|
|
},
|
|
else => {
|
|
//std.debug.print("unhandled SDL event type {}\n", .{event.type});
|
|
return false;
|
|
},
|
|
}
|
|
}
|
|
|
|
pub fn SDL_mouse_button_to_gui(button: u8) gui.enums.Button {
|
|
return switch (button) {
|
|
c.SDL_BUTTON_LEFT => .left,
|
|
c.SDL_BUTTON_MIDDLE => .middle,
|
|
c.SDL_BUTTON_RIGHT => .right,
|
|
c.SDL_BUTTON_X1 => .four,
|
|
c.SDL_BUTTON_X2 => .five,
|
|
else => blk: {
|
|
std.debug.print("SDL_mouse_button_to_gui unknown button {d}\n", .{button});
|
|
break :blk .six;
|
|
},
|
|
};
|
|
}
|
|
|
|
pub fn SDL_keymod_to_gui(keymod: u16) gui.enums.Mod {
|
|
if (keymod == c.KMOD_NONE) return gui.enums.Mod.none;
|
|
|
|
var m: u16 = 0;
|
|
if (keymod & c.KMOD_LSHIFT > 0) m |= @enumToInt(gui.enums.Mod.lshift);
|
|
if (keymod & c.KMOD_RSHIFT > 0) m |= @enumToInt(gui.enums.Mod.rshift);
|
|
if (keymod & c.KMOD_LCTRL > 0) m |= @enumToInt(gui.enums.Mod.lctrl);
|
|
if (keymod & c.KMOD_RCTRL > 0) m |= @enumToInt(gui.enums.Mod.rctrl);
|
|
if (keymod & c.KMOD_LALT > 0) m |= @enumToInt(gui.enums.Mod.lalt);
|
|
if (keymod & c.KMOD_RALT > 0) m |= @enumToInt(gui.enums.Mod.ralt);
|
|
if (keymod & c.KMOD_LGUI > 0) m |= @enumToInt(gui.enums.Mod.lgui);
|
|
if (keymod & c.KMOD_RGUI > 0) m |= @enumToInt(gui.enums.Mod.rgui);
|
|
|
|
return @intToEnum(gui.enums.Mod, m);
|
|
}
|
|
|
|
pub fn SDL_keysym_to_gui(keysym: i32) gui.enums.Key {
|
|
return switch (keysym) {
|
|
c.SDLK_a => .a,
|
|
c.SDLK_b => .b,
|
|
c.SDLK_c => .c,
|
|
c.SDLK_d => .d,
|
|
c.SDLK_e => .e,
|
|
c.SDLK_f => .f,
|
|
c.SDLK_g => .g,
|
|
c.SDLK_h => .h,
|
|
c.SDLK_i => .i,
|
|
c.SDLK_j => .j,
|
|
c.SDLK_k => .k,
|
|
c.SDLK_l => .l,
|
|
c.SDLK_m => .m,
|
|
c.SDLK_n => .n,
|
|
c.SDLK_o => .o,
|
|
c.SDLK_p => .p,
|
|
c.SDLK_q => .q,
|
|
c.SDLK_r => .r,
|
|
c.SDLK_s => .s,
|
|
c.SDLK_t => .t,
|
|
c.SDLK_u => .u,
|
|
c.SDLK_v => .v,
|
|
c.SDLK_w => .w,
|
|
c.SDLK_x => .x,
|
|
c.SDLK_y => .y,
|
|
c.SDLK_z => .z,
|
|
|
|
c.SDLK_SPACE => .space,
|
|
c.SDLK_BACKSPACE => .backspace,
|
|
c.SDLK_UP => .up,
|
|
c.SDLK_DOWN => .down,
|
|
c.SDLK_LEFT => .left,
|
|
c.SDLK_RIGHT => .right,
|
|
c.SDLK_TAB => .tab,
|
|
c.SDLK_ESCAPE => .escape,
|
|
else => .unknown,
|
|
};
|
|
}
|