// Window manipulation // Copyright (C) 2021-2023 Nguyễn Gia Phong // // This file is part of gfz. // // gfz is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published // by the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // gfz is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with gfz. If not, see . const Error = gfz.Error; const Key = input.Key; const KeyAction = input.KeyAction; const Mods = input.Mods; const Monitor = @import("Monitor.zig"); const MouseButton = input.MouseButton; const c = @import("cimport.zig"); const checkError = gfz.checkError; const getError = gfz.getError; const gfz = @import("gfz.zig"); const input = @import("input.zig"); const Self = @This(); const CursorPosFun = fn (window: Self, xpos: f64, ypos: f64) void; const KeyFun = fn (window: Self, key: Key, scancode: c_int, action: KeyAction, mods: Mods) void; const MouseButtonFun = fn (window: Self, button: MouseButton, action: MouseButton.Action, mods: Mods) void; const SizeFun = fn (window: Self, width: c_int, height: c_int) void; const Impl = c.GLFWwindow; pimpl: *Impl, /// Convert given function to GLFW callback. fn fnCast(comptime DestType: type, comptime fun: anytype) DestType { return switch (DestType) { c.GLFWcursorposfun => struct { pub fn callback(window: ?*Impl, xpos: f64, ypos: f64) callconv(.C) void { fun(@as(*const Self, @fieldParentPtr("pimpl", &window.?)).*, xpos, ypos); } }, c.GLFWkeyfun => struct { pub fn callback(window: ?*Impl, key: c_int, scancode: c_int, action: c_int, mods: c_int) callconv(.C) void { fun(@as(*const Self, @fieldParentPtr("pimpl", &window.?)).*, @enumFromInt(key), scancode, @enumFromInt(action), Mods.fromInt(mods)); } }, c.GLFWmousebuttonfun => struct { pub fn callback(window: ?*Impl, button: c_int, action: c_int, mods: c_int) callconv(.C) void { fun(@as(*const Self, @fieldParentPtr("pimpl", &window.?)).*, @enumFromInt(button), @enumFromInt(action), Mods.fromInt(mods)); } }, c.GLFWwindowsizefun => struct { pub fn callback(window: ?*Impl, width: c_int, height: c_int) callconv(.C) void { fun(@as(*const Self, @fieldParentPtr("pimpl", &window.?)).*, width, height); } }, else => unreachable, }.callback; } pub const Hints = struct { pub const dont_care = c.GLFW_DONT_CARE; resizable: bool = true, visible: bool = true, decorated: bool = true, focused: bool = true, auto_iconify: bool = true, floating: bool = false, maximized: bool = false, center_cursor: bool = true, transparent_framebuffer: bool = false, focus_on_show: bool = true, scale_to_monitor: bool = false, bits: struct { red: c_int = 8, green: c_int = 8, blue: c_int = 8, alpha: c_int = 8, depth: c_int = 24, stencil: c_int = 8, } = .{}, accum_bits: struct { red: c_int = 0, green: c_int = 0, blue: c_int = 0, alpha: c_int = 0, } = .{}, aux_buffers: c_int = 0, samples: c_int = 0, refresh_rate: c_int = dont_care, stereo: bool = false, srgb_capable: bool = false, doublebuffer: bool = true, client_api: enum(c_int) { opengl = c.GLFW_OPENGL_API, opengl_es = c.GLFW_OPENGL_ES_API, no = c.GLFW_NO_API, } = .opengl, context: struct { creation_api: enum(c_int) { native = c.GLFW_NATIVE_CONTEXT_API, egl = c.GLFW_EGL_CONTEXT_API, osmesa = c.GLFW_OSMESA_CONTEXT_API, } = .native, version: struct { major: c_int = 1, minor: c_int = 0, } = .{}, robustness: enum(c_int) { no_robustness = c.GLFW_NO_ROBUSTNESS, no_reset_notification = c.GLFW_NO_RESET_NOTIFICATION, lose_context_on_reset = c.GLFW_LOSE_CONTEXT_ON_RESET, } = .no_robustness, release_behavior: enum(c_int) { any = c.GLFW_ANY_RELEASE_BEHAVIOR, flush = c.GLFW_RELEASE_BEHAVIOR_FLUSH, none = c.GLFW_RELEASE_BEHAVIOR_NONE, } = .any, } = .{}, opengl: struct { forward_compat: bool = false, debug_context: bool = false, profile: enum(c_int) { any = c.GLFW_OPENGL_ANY_PROFILE, compat = c.GLFW_OPENGL_COMPAT_PROFILE, core = c.GLFW_OPENGL_CORE_PROFILE, } = .any, } = .{}, }; /// Set the specified window hint to the desired value. fn setHint(hint: c_int, value: anytype) Error!void { c.glfwWindowHint(hint, switch (@TypeOf(value)) { c_int, comptime_int => value, bool => if (value) c.GLFW_TRUE else c.GLFW_FALSE, else => @intFromEnum(value), }); try checkError(); } const Options = struct { fullscreen: ?Monitor = null, share: ?Self = null, }; /// Create a window and its associated context. pub fn create(width: c_int, height: c_int, title: [:0]const u8, options: Options, hints: Hints) Error!Self { try setHint(c.GLFW_RESIZABLE, hints.resizable); try setHint(c.GLFW_VISIBLE, hints.visible); try setHint(c.GLFW_DECORATED, hints.decorated); try setHint(c.GLFW_FOCUSED, hints.focused); try setHint(c.GLFW_AUTO_ICONIFY, hints.auto_iconify); try setHint(c.GLFW_FLOATING, hints.floating); try setHint(c.GLFW_MAXIMIZED, hints.maximized); try setHint(c.GLFW_CENTER_CURSOR, hints.center_cursor); try setHint(c.GLFW_TRANSPARENT_FRAMEBUFFER, hints.transparent_framebuffer); try setHint(c.GLFW_FOCUS_ON_SHOW, hints.focus_on_show); try setHint(c.GLFW_SCALE_TO_MONITOR, hints.scale_to_monitor); try setHint(c.GLFW_RED_BITS, hints.bits.red); try setHint(c.GLFW_GREEN_BITS, hints.bits.green); try setHint(c.GLFW_BLUE_BITS, hints.bits.blue); try setHint(c.GLFW_ALPHA_BITS, hints.bits.alpha); try setHint(c.GLFW_DEPTH_BITS, hints.bits.depth); try setHint(c.GLFW_STENCIL_BITS, hints.bits.stencil); try setHint(c.GLFW_ACCUM_RED_BITS, hints.accum_bits.red); try setHint(c.GLFW_ACCUM_GREEN_BITS, hints.accum_bits.green); try setHint(c.GLFW_ACCUM_BLUE_BITS, hints.accum_bits.blue); try setHint(c.GLFW_ACCUM_ALPHA_BITS, hints.accum_bits.alpha); try setHint(c.GLFW_AUX_BUFFERS, hints.aux_buffers); try setHint(c.GLFW_SAMPLES, hints.samples); try setHint(c.GLFW_REFRESH_RATE, hints.refresh_rate); try setHint(c.GLFW_STEREO, hints.stereo); try setHint(c.GLFW_SRGB_CAPABLE, hints.srgb_capable); try setHint(c.GLFW_DOUBLEBUFFER, hints.doublebuffer); try setHint(c.GLFW_CLIENT_API, hints.client_api); try setHint(c.GLFW_CONTEXT_CREATION_API, hints.context.creation_api); try setHint(c.GLFW_CONTEXT_VERSION_MAJOR, hints.context.version.major); try setHint(c.GLFW_CONTEXT_VERSION_MINOR, hints.context.version.minor); try setHint(c.GLFW_CONTEXT_ROBUSTNESS, hints.context.robustness); try setHint(c.GLFW_CONTEXT_RELEASE_BEHAVIOR, hints.context.release_behavior); try setHint(c.GLFW_OPENGL_FORWARD_COMPAT, hints.opengl.forward_compat); try setHint(c.GLFW_OPENGL_DEBUG_CONTEXT, hints.opengl.debug_context); try setHint(c.GLFW_OPENGL_PROFILE, hints.opengl.profile); const monitor = if (options.fullscreen) |box| box.pimpl else null; const share = if (options.share) |box| box.pimpl else null; const ptr = c.glfwCreateWindow(width, height, title.ptr, monitor, share); return if (ptr) |pimpl| Self{ .pimpl = pimpl } else getError(); } /// Return the window whose context is current on the calling thread. pub fn getCurrent() Error!?Self { const result = if (c.glfwGetCurrentContext()) |pimpl| Self{ .pimpl = pimpl } else null; try checkError(); return result; } /// Make the context current for the calling thread. pub fn makeCurrent(self: ?Self) Error!void { c.glfwMakeContextCurrent(if (self) |window| window.pimpl else null); try checkError(); } /// Check the close flag. pub fn shouldClose(self: Self) Error!bool { const flag = c.glfwWindowShouldClose(self.pimpl); try checkError(); return flag == c.GLFW_TRUE; } /// Set the close flag. pub fn setShouldClose(self: Self, value: bool) Error!void { c.glfwSetWindowShouldClose(self.pimpl, if (value) c.GLFW_TRUE else c.GLFW_FALSE); try checkError(); } /// Swap the front and back buffers. pub fn swapBuffers(self: Self) Error!void { c.glfwSwapBuffers(self.pimpl); try checkError(); } const InputMode = enum(c_int) { sticky_keys = c.GLFW_STICKY_KEYS, sticky_mouse_buttons = c.GLFW_STICKY_MOUSE_BUTTONS, lock_key_mods = c.GLFW_LOCK_KEY_MODS, raw_mouse_motion = c.GLFW_RAW_MOUSE_MOTION, }; /// Return the value of an input option. pub fn getInputMode(self: Self, mode: InputMode) Error!bool { const value = c.glfwGetInputMode(self.pimpl, @intFromEnum(mode)); try checkError(); return value == c.GLFW_TRUE; } /// Set an input option. pub fn setInputMode(self: Self, mode: InputMode, flag: bool) Error!void { const value = if (flag) c.GLFW_TRUE else c.GLFW_FALSE; c.glfwSetInputMode(self.pimpl, @intFromEnum(mode), value); try checkError(); } const CursorMode = enum(c_int) { normal = c.GLFW_CURSOR_NORMAL, hidden = c.GLFW_CURSOR_HIDDEN, disabled = c.GLFW_CURSOR_DISABLED, }; /// Return the cursor mode. pub fn getCursorMode(self: Self) Error!CursorMode { const value = c.glfwGetInputMode(self.pimpl, c.GLFW_CURSOR); try checkError(); return @enumFromInt(value); } /// Set the cursor mode. pub fn setCursorMode(self: Self, value: CursorMode) Error!void { c.glfwSetInputMode(self.pimpl, c.GLFW_CURSOR, @intFromEnum(value)); try checkError(); } const Pos = struct { x: f64, y: f64, }; /// Retrieve the position of the cursor relative to the window's content area. pub fn getCursorPos(self: Self) Error!Pos { var pos: Pos = undefined; c.glfwGetCursorPos(self.pimpl, &pos.x, &pos.y); try checkError(); return pos; } /// Set the position of the cursor, relative to the window's content area. pub fn setCursorPos(self: Self, xpos: f64, ypos: f64) Error!void { c.glfwSetCursorPos(self.pimpl, xpos, ypos); try checkError(); } /// Set the cursor position callback. pub fn setCursorPosCallback(self: Self, comptime fun: CursorPosFun) Error!void { _ = c.glfwSetCursorPosCallback(self.pimpl, fnCast(c.GLFWcursorposfun, fun)); try checkError(); } const State = enum(c_int) { press = c.GLFW_PRESS, release = c.GLFW_RELEASE, }; /// Return the last reported state of a mouse button. pub fn getMouseButton(self: Self, button: MouseButton) Error!State { const state = c.glfwGetMouseButton(self.pimpl, @intFromEnum(button)); try checkError(); return @enumFromInt(state); } /// Set the mouse button callback. pub fn setMouseButtonCallback(self: Self, comptime fun: MouseButtonFun) Error!void { _ = c.glfwSetMouseButtonCallback(self.pimpl, fnCast(c.GLFWmousebuttonfun, fun)); try checkError(); } /// Return the last reported state of a keyboard key. pub fn getKey(self: Self, key: Key) Error!State { const state = c.glfwGetKey(self.pimpl, @intFromEnum(key)); try checkError(); return @enumFromInt(state); } /// Set the key callback. pub fn setKeyCallback(self: Self, comptime fun: KeyFun) Error!void { _ = c.glfwSetKeyCallback(self.pimpl, fnCast(c.GLFWkeyfun, fun)); try checkError(); } /// Set the size of the content area. pub fn setSize(self: Self, width: c_int, height: c_int) Error!void { c.glfwSetWindowSize(self.pimpl, width, height); try checkError(); } /// Set the size callback. pub fn setSizeCallback(self: Self, comptime fun: SizeFun) Error!void { _ = c.glfwSetWindowSizeCallback(self.pimpl, fnCast(c.GLFWwindowsizefun, fun)); try checkError(); }