import * as ImGui from "imgui-js"; import { ImVec2 } from "imgui-js"; import * as PIXI from "pixi.js"; import * as U from "./utils"; let clipboard_text: string = ""; const contexts: ImGui.ImGuiContext[] = []; const contextIOs: ImGui.ImGuiIO[] = []; let isFirstWindow: boolean = true; class ImGuiImplInternalRenderer extends PIXI.ObjectRenderer { constructor(r: PIXI.Renderer) { super(r); } render(owner: PIXI.DisplayObject): void { if (!(owner instanceof ImGuiWindowLayer)) return; const window: ImGuiWindow = (owner as ImGuiWindowLayer).window; const io: ImGui.ImGuiIO = window.io; const draw_data: ImGui.ImDrawData = ImGui.GetDrawData(); if (draw_data === null) { throw new Error(); } gl || console.log(draw_data); // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) const fb_width: number = io.DisplaySize.x * io.DisplayFramebufferScale.x; const fb_height: number = io.DisplaySize.y * io.DisplayFramebufferScale.y; if (fb_width === 0 || fb_height === 0) { return; } draw_data.ScaleClipRects(io.DisplayFramebufferScale); // Backup GL state const last_program: WebGLProgram | null = gl && gl.getParameter(gl.CURRENT_PROGRAM) || null; const last_active_texture: GLenum | null = gl && gl.getParameter(gl.ACTIVE_TEXTURE) || null; gl && gl.activeTexture(gl.TEXTURE0); const last_texture0: WebGLTexture | null = gl && gl.getParameter(gl.TEXTURE_BINDING_2D) || null; const last_vertex_array: WebGLVertexArrayObject | null = gl && gl.getParameter(gl.VERTEX_ARRAY_BINDING) || null; const last_array_buffer: WebGLBuffer | null = gl && gl.getParameter(gl.ARRAY_BUFFER_BINDING) || null; const last_element_array_buffer: WebGLBuffer | null = gl && gl.getParameter(gl.ELEMENT_ARRAY_BUFFER_BINDING) || null; // GLint last_polygon_mode[2]; glGetIntegerv(GL_POLYGON_MODE, last_polygon_mode); const last_viewport: Int32Array | null = gl && gl.getParameter(gl.VIEWPORT) || null; const last_scissor_box: Int32Array | null = gl && gl.getParameter(gl.SCISSOR_BOX) || null; const last_blend_src_rgb: GLenum | null = gl && gl.getParameter(gl.BLEND_SRC_RGB) || null; const last_blend_dst_rgb: GLenum | null = gl && gl.getParameter(gl.BLEND_DST_RGB) || null; const last_blend_src_alpha: GLenum | null = gl && gl.getParameter(gl.BLEND_SRC_ALPHA) || null; const last_blend_dst_alpha: GLenum | null = gl && gl.getParameter(gl.BLEND_DST_ALPHA) || null; const last_blend_equation_rgb: GLenum | null = gl && gl.getParameter(gl.BLEND_EQUATION_RGB) || null; const last_blend_equation_alpha: GLenum | null = gl && gl.getParameter(gl.BLEND_EQUATION_ALPHA) || null; const last_enable_blend: GLboolean | null = gl && gl.getParameter(gl.BLEND) || null; const last_enable_cull_face: GLboolean | null = gl && gl.getParameter(gl.CULL_FACE) || null; const last_enable_depth_test: GLboolean | null = gl && gl.getParameter(gl.DEPTH_TEST) || null; const last_enable_scissor_test: GLboolean | null = gl && gl.getParameter(gl.SCISSOR_TEST) || null; gl.clearColor(0, 0, 0, 0); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); gl && gl.bindVertexArray(g_VaoHandle); // Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled, polygon fill gl && gl.enable(gl.BLEND); gl && gl.blendEquation(gl.FUNC_ADD); gl && gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE); gl && gl.disable(gl.CULL_FACE); gl && gl.disable(gl.DEPTH_TEST); gl && gl.enable(gl.SCISSOR_TEST); // glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); // Setup viewport, orthographic projection matrix // Our visible imgui space lies from draw_data->DisplayPps (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayMin is typically (0,0) for single viewport apps. gl && gl.viewport(0, 0, fb_width, fb_height); const L: number = draw_data.DisplayPos.x; const R: number = draw_data.DisplayPos.x + draw_data.DisplaySize.x; const T: number = draw_data.DisplayPos.y; const B: number = draw_data.DisplayPos.y + draw_data.DisplaySize.y; // we actually flip the bottom and top here to match with PIXI's texture usage const ortho_projection: Float32Array = new Float32Array([ 2.0 / (R - L), 0.0, 0.0, 0.0, 0.0, 2.0 / (B - T), 0.0, 0.0, 0.0, 0.0, -1.0, 0.0, (R + L) / (L - R), (B + T) / (T - B), 0.0, 1.0, ]); gl && gl.useProgram(g_ShaderHandle); gl && gl.uniform1i(g_AttribLocationTex, 0); gl && g_AttribLocationProjMtx && gl.uniformMatrix4fv(g_AttribLocationProjMtx, false, ortho_projection); // Render command lists gl && gl.bindBuffer(gl.ARRAY_BUFFER, g_VboHandle); gl && gl.enableVertexAttribArray(g_AttribLocationPosition); gl && gl.enableVertexAttribArray(g_AttribLocationUV); gl && gl.enableVertexAttribArray(g_AttribLocationColor); gl && gl.vertexAttribPointer(g_AttribLocationPosition, 2, gl.FLOAT, false, ImGui.ImDrawVertSize, ImGui.ImDrawVertPosOffset); gl && gl.vertexAttribPointer(g_AttribLocationUV, 2, gl.FLOAT, false, ImGui.ImDrawVertSize, ImGui.ImDrawVertUVOffset); gl && gl.vertexAttribPointer(g_AttribLocationColor, 4, gl.UNSIGNED_BYTE, true, ImGui.ImDrawVertSize, ImGui.ImDrawVertColOffset); // Draw const pos = draw_data.DisplayPos; const idx_buffer_type: GLenum = gl && ((ImGui.ImDrawIdxSize === 4) ? gl.UNSIGNED_INT : gl.UNSIGNED_SHORT) || 0; draw_data.IterateDrawLists((draw_list: ImGui.ImDrawList): void => { gl || console.log(draw_list); gl || console.log("VtxBuffer.length", draw_list.VtxBuffer.length); gl || console.log("IdxBuffer.length", draw_list.IdxBuffer.length); let idx_buffer_offset: number = 0; gl && gl.bindBuffer(gl.ARRAY_BUFFER, g_VboHandle); gl && gl.bufferData(gl.ARRAY_BUFFER, draw_list.VtxBuffer, gl.STREAM_DRAW); gl && gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, g_ElementsHandle); gl && gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, draw_list.IdxBuffer, gl.STREAM_DRAW); draw_list.IterateDrawCmds((draw_cmd: ImGui.ImDrawCmd): void => { gl || console.log(draw_cmd); gl || console.log("ElemCount", draw_cmd.ElemCount); gl || console.log("ClipRect", draw_cmd.ClipRect.x, draw_cmd.ClipRect.y, draw_cmd.ClipRect.z - draw_cmd.ClipRect.x, draw_cmd.ClipRect.w - draw_cmd.ClipRect.y); gl || console.log("TextureId", draw_cmd.TextureId); if (!gl) { console.log("i: pos.x pos.y uv.x uv.y col"); for (let i = 0; i < Math.min(3, draw_cmd.ElemCount); ++i) { const view: ImGui.ImDrawVert = new ImGui.ImDrawVert(draw_list.VtxBuffer.buffer, draw_list.VtxBuffer.byteOffset + i * ImGui.ImDrawVertSize); console.log(`${i}: ${view.pos[0].toFixed(2)} ${view.pos[1].toFixed(2)} ${view.uv[0].toFixed(5)} ${view.uv[1].toFixed(5)} ${("00000000" + view.col[0].toString(16)).substr(-8)}`); } } if (draw_cmd.UserCallback !== null) { // User callback (registered via ImDrawList::AddCallback) draw_cmd.UserCallback(draw_list, draw_cmd); } else { const clip_rect = new ImGui.ImVec4(draw_cmd.ClipRect.x - pos.x, draw_cmd.ClipRect.y - pos.y, draw_cmd.ClipRect.z - pos.x, draw_cmd.ClipRect.w - pos.y); if (clip_rect.x < fb_width && clip_rect.y < fb_height && clip_rect.z >= 0.0 && clip_rect.w >= 0.0) { // Apply scissor/clipping rectangle gl && gl.scissor(clip_rect.x, clip_rect.y, clip_rect.z - clip_rect.x, clip_rect.w - clip_rect.y); // Bind texture, Draw gl && gl.bindTexture(gl.TEXTURE_2D, draw_cmd.TextureId); gl && gl.drawElements(gl.TRIANGLES, draw_cmd.ElemCount, idx_buffer_type, idx_buffer_offset); } } idx_buffer_offset += draw_cmd.ElemCount * ImGui.ImDrawIdxSize; }); }); // Restore modified GL state gl && (last_program !== null) && gl.useProgram(last_program); gl && (last_texture0 !== null) && gl.bindTexture(gl.TEXTURE_2D, last_texture0); gl && (last_active_texture !== null) && gl.activeTexture(last_active_texture); gl && gl.disableVertexAttribArray(g_AttribLocationPosition); gl && gl.disableVertexAttribArray(g_AttribLocationUV); gl && gl.disableVertexAttribArray(g_AttribLocationColor); gl && (last_vertex_array !== null) && gl.bindVertexArray(last_vertex_array); gl && (last_array_buffer !== null) && gl.bindBuffer(gl.ARRAY_BUFFER, last_array_buffer); gl && (last_element_array_buffer !== null) && gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, last_element_array_buffer); gl && (last_viewport !== null) && gl.viewport(last_viewport[0], last_viewport[1], last_viewport[2], last_viewport[3]); gl && (last_scissor_box !== null) && gl.scissor(last_scissor_box[0], last_scissor_box[1], last_scissor_box[2], last_scissor_box[3]); gl && (last_blend_equation_rgb !== null && last_blend_equation_alpha !== null) && gl.blendEquationSeparate(last_blend_equation_rgb, last_blend_equation_alpha); gl && (last_blend_src_rgb !== null && last_blend_src_alpha !== null && last_blend_dst_rgb !== null && last_blend_dst_alpha !== null) && gl.blendFuncSeparate(last_blend_src_rgb, last_blend_dst_rgb, last_blend_src_alpha, last_blend_dst_alpha); gl && (last_enable_blend ? gl.enable(gl.BLEND) : gl.disable(gl.BLEND)); gl && (last_enable_cull_face ? gl.enable(gl.CULL_FACE) : gl.disable(gl.CULL_FACE)); gl && (last_enable_depth_test ? gl.enable(gl.DEPTH_TEST) : gl.disable(gl.DEPTH_TEST)); gl && (last_enable_scissor_test ? gl.enable(gl.SCISSOR_TEST) : gl.disable(gl.SCISSOR_TEST)); // glPolygonMode(GL_FRONT_AND_BACK, (GLenum)last_polygon_mode[0]); } } let gl: WebGL2RenderingContext = null; let app: PIXI.Application = null; class ImGuiWindowLayer extends PIXI.Sprite { public readonly window: ImGuiWindow; constructor(window: ImGuiWindow, tex:PIXI.RenderTexture) { super(); this.pluginName = "imgui_renderer"; this.window = window; } } export class ImGuiWindow extends PIXI.Container { public readonly ctx: ImGui.ImGuiContext; public readonly io: ImGui.ImGuiIO; private sizeX: number; private sizeY: number; public getSizeX(): number { return this.sizeX; } public getSizeY(): number { return this.sizeY; } private baseTex: PIXI.BaseRenderTexture = new PIXI.BaseRenderTexture({scaleMode: PIXI.SCALE_MODES.LINEAR, resolution: 1, type: PIXI.TYPES.FLOAT, format: PIXI.FORMATS.RGBA}); private tex: PIXI.RenderTexture = new PIXI.RenderTexture(this.baseTex); private surface: PIXI.Sprite = new PIXI.Sprite(this.tex); private imgui: ImGuiWindowLayer = new ImGuiWindowLayer(this, this.tex); private update: (dt: number) => void; constructor(sizeX: number, sizeY: number, update: (dt: number) => void) { super(); this.ctx = ImGui.CreateContext(contextIOs.length == 0 ? null : contextIOs[0].Fonts); ImGui.SetCurrentContext(this.ctx); this.io = ImGui.GetIO(); contexts.push(this.ctx); contextIOs.push(this.io); if (contexts.length == 1) { CreateFontsTexture(); } if (typeof(window) !== "undefined") { ImGui.LoadIniSettingsFromMemory(window.localStorage.getItem("imgui.ini") || ""); } if (typeof(navigator) !== "undefined") { this.io.ConfigMacOSXBehaviors = navigator.platform.match(/Mac/) !== null; } this.io.SetClipboardTextFn = (user_data: any, text: string): void => { clipboard_text = text; // console.log(`set clipboard_text: "${clipboard_text}"`); if (typeof navigator !== "undefined" && typeof (navigator as any).clipboard !== "undefined") { // console.log(`clipboard.writeText: "${clipboard_text}"`); (navigator as any).clipboard.writeText(clipboard_text).then((): void => { // console.log(`clipboard.writeText: "${clipboard_text}" done.`); }); } }; this.io.GetClipboardTextFn = (user_data: any): string => { // if (typeof navigator !== "undefined" && typeof (navigator as any).clipboard !== "undefined") { // console.log(`clipboard.readText: "${clipboard_text}"`); // (navigator as any).clipboard.readText().then((text: string): void => { // clipboard_text = text; // console.log(`clipboard.readText: "${clipboard_text}" done.`); // }); // } // console.log(`get clipboard_text: "${clipboard_text}"`); return clipboard_text; }; this.io.ClipboardUserData = null; // Setup back-end capabilities flags this.io.BackendFlags |= ImGui.BackendFlags.HasMouseCursors; // We can honor GetMouseCursor() values (optional) // Keyboard mapping. ImGui will use those indices to peek into the io.KeyDown[] array. this.io.KeyMap[ImGui.Key.Tab] = 9; this.io.KeyMap[ImGui.Key.LeftArrow] = 37; this.io.KeyMap[ImGui.Key.RightArrow] = 39; this.io.KeyMap[ImGui.Key.UpArrow] = 38; this.io.KeyMap[ImGui.Key.DownArrow] = 40; this.io.KeyMap[ImGui.Key.PageUp] = 33; this.io.KeyMap[ImGui.Key.PageDown] = 34; this.io.KeyMap[ImGui.Key.Home] = 36; this.io.KeyMap[ImGui.Key.End] = 35; this.io.KeyMap[ImGui.Key.Insert] = 45; this.io.KeyMap[ImGui.Key.Delete] = 46; this.io.KeyMap[ImGui.Key.Backspace] = 8; this.io.KeyMap[ImGui.Key.Space] = 32; this.io.KeyMap[ImGui.Key.Enter] = 13; this.io.KeyMap[ImGui.Key.Escape] = 27; this.io.KeyMap[ImGui.Key.A] = 65; this.io.KeyMap[ImGui.Key.C] = 67; this.io.KeyMap[ImGui.Key.V] = 86; this.io.KeyMap[ImGui.Key.X] = 88; this.io.KeyMap[ImGui.Key.Y] = 89; this.io.KeyMap[ImGui.Key.Z] = 90; this.update = update; this.resize(sizeX, sizeY); this.imgui.pluginName = 'imgui_renderer'; this.addChild(this.surface); app.renderer.plugins.interaction.addListener("mousedown", this.mouseDown.bind(this)); app.renderer.plugins.interaction.addListener("mouseup", this.mouseUp.bind(this)); app.ticker.add(this.updateInternal.bind(this)); this.interactive = false; this.interactiveChildren = true; this.surface.interactive = false; } withContext(cb: () => void) { ImGui.SetCurrentContext(this.ctx); cb(); } mouseDown(e: PIXI.interaction.InteractionEvent): void { if (!this.io.WantCaptureMouse || app.renderer.plugins.interaction.hitTest(app.renderer.plugins.interaction.mouse.global) == this.surface) { this.io.MouseDown[mouse_button_map[0]] = true; } } mouseUp(e: PIXI.interaction.InteractionEvent): void { if (!this.io.WantCaptureMouse || app.renderer.plugins.interaction.hitTest(app.renderer.plugins.interaction.mouse.global) == this.surface) { this.io.MouseDown[mouse_button_map[0]] = false; } } resize(sizeX: number, sizeY: number) { this.sizeX = sizeX; this.sizeY = sizeY; this.baseTex.resize(sizeX, sizeY); this.tex.frame = new PIXI.Rectangle(0, 0, this.baseTex.width, this.baseTex.height); } updateInternal(deltaTime: number) { const dt: number = deltaTime * 1 / (1000 * PIXI.settings.TARGET_FPMS); ImGui.SetCurrentContext(this.ctx); if (this.io.WantSaveIniSettings) { this.io.WantSaveIniSettings = false; if (typeof(window) !== "undefined") { window.localStorage.setItem("imgui.ini", ImGui.SaveIniSettingsToMemory()); } } this.io.DisplaySize.x = this.sizeX; this.io.DisplaySize.y = this.sizeY; this.io.DisplayFramebufferScale.x = 1; this.io.DisplayFramebufferScale.y = 1; this.io.DeltaTime = dt; let localPos: PIXI.Point = this.toLocal(app.renderer.plugins.interaction.mouse.global); if (localPos.x < 0 || localPos.y < 0 || localPos.x >= this.sizeX || localPos.y >= this.sizeY) { localPos = new PIXI.Point(-Number.MAX_VALUE, -Number.MAX_VALUE); } this.io.MousePos.x = localPos.x; this.io.MousePos.y = localPos.y; if (this.io.WantSetMousePos) { console.log("TODO: MousePos", this.io.MousePos.x, this.io.MousePos.y); } if (typeof(document) !== "undefined") { if (this.io.MouseDrawCursor) { document.body.style.cursor = "none"; } else { switch (ImGui.GetMouseCursor()) { case ImGui.MouseCursor.None: document.body.style.cursor = "none"; break; default: case ImGui.MouseCursor.Arrow: document.body.style.cursor = "default"; break; case ImGui.MouseCursor.TextInput: document.body.style.cursor = "text"; break; // When hovering over InputText, etc. case ImGui.MouseCursor.ResizeAll: document.body.style.cursor = "move"; break; // Unused case ImGui.MouseCursor.ResizeNS: document.body.style.cursor = "ns-resize"; break; // When hovering over an horizontal border case ImGui.MouseCursor.ResizeEW: document.body.style.cursor = "ew-resize"; break; // When hovering over a vertical border or a column case ImGui.MouseCursor.ResizeNESW: document.body.style.cursor = "nesw-resize"; break; // When hovering over the bottom-left corner of a window case ImGui.MouseCursor.ResizeNWSE: document.body.style.cursor = "nwse-resize"; break; // When hovering over the bottom-right corner of a window case ImGui.MouseCursor.Hand: document.body.style.cursor = "move"; break; } } } // Gamepad navigation mapping [BETA] for (let i = 0; i < this.io.NavInputs.length; ++i) { this.io.NavInputs[i] = 0.0; } if (this.io.ConfigFlags & ImGui.ConfigFlags.NavEnableGamepad) { // Update gamepad inputs const gamepads: (Gamepad | null)[] = (typeof(navigator) !== "undefined" && typeof(navigator.getGamepads) === "function") ? navigator.getGamepads() : []; for (let i = 0; i < gamepads.length; ++i) { const gamepad: Gamepad | null = gamepads[i]; if (!gamepad) { continue; } const buttons_count: number = gamepad.buttons.length; const axes_count: number = gamepad.axes.length; const MAP_BUTTON = function(NAV_NO: number, BUTTON_NO: number): void { if (!gamepad) { return; } if (buttons_count > BUTTON_NO && gamepad.buttons[BUTTON_NO].pressed) this.io.NavInputs[NAV_NO] = 1.0; } const MAP_ANALOG = function(NAV_NO: number, AXIS_NO: number, V0: number, V1: number): void { if (!gamepad) { return; } let v: number = (axes_count > AXIS_NO) ? gamepad.axes[AXIS_NO] : V0; v = (v - V0) / (V1 - V0); if (v > 1.0) v = 1.0; if (this.io.NavInputs[NAV_NO] < v) this.io.NavInputs[NAV_NO] = v; } // TODO: map input based on vendor and product id // https://developer.mozilla.org/en-US/docs/Web/API/Gamepad/id const match: RegExpMatchArray | null = gamepad.id.match(/^([0-9a-f]{4})-([0-9a-f]{4})-.*$/); const match_chrome: RegExpMatchArray | null = gamepad.id.match(/^.*\(.*Vendor: ([0-9a-f]{4}) Product: ([0-9a-f]{4})\).*$/); const vendor: string = (match && match[1]) || (match_chrome && match_chrome[1]) || "0000"; const product: string = (match && match[2]) || (match_chrome && match_chrome[2]) || "0000"; switch (vendor + product) { case "046dc216": // Logitech Logitech Dual Action (Vendor: 046d Product: c216) MAP_BUTTON(ImGui.NavInput.Activate, 1); // Cross / A MAP_BUTTON(ImGui.NavInput.Cancel, 2); // Circle / B MAP_BUTTON(ImGui.NavInput.Menu, 0); // Square / X MAP_BUTTON(ImGui.NavInput.Input, 3); // Triangle / Y MAP_ANALOG(ImGui.NavInput.DpadLeft, 4, -0.3, -0.9); // D-Pad Left MAP_ANALOG(ImGui.NavInput.DpadRight, 4, +0.3, +0.9); // D-Pad Right MAP_ANALOG(ImGui.NavInput.DpadUp, 5, -0.3, -0.9); // D-Pad Up MAP_ANALOG(ImGui.NavInput.DpadDown, 5, +0.3, +0.9); // D-Pad Down MAP_BUTTON(ImGui.NavInput.FocusPrev, 4); // L1 / LB MAP_BUTTON(ImGui.NavInput.FocusNext, 5); // R1 / RB MAP_BUTTON(ImGui.NavInput.TweakSlow, 6); // L2 / LT MAP_BUTTON(ImGui.NavInput.TweakFast, 7); // R2 / RT MAP_ANALOG(ImGui.NavInput.LStickLeft, 0, -0.3, -0.9); MAP_ANALOG(ImGui.NavInput.LStickRight, 0, +0.3, +0.9); MAP_ANALOG(ImGui.NavInput.LStickUp, 1, -0.3, -0.9); MAP_ANALOG(ImGui.NavInput.LStickDown, 1, +0.3, +0.9); break; case "046dc21d": // Logitech Gamepad F310 (STANDARD GAMEPAD Vendor: 046d Product: c21d) MAP_BUTTON(ImGui.NavInput.Activate, 0); // Cross / A MAP_BUTTON(ImGui.NavInput.Cancel, 1); // Circle / B MAP_BUTTON(ImGui.NavInput.Menu, 2); // Square / X MAP_BUTTON(ImGui.NavInput.Input, 3); // Triangle / Y MAP_BUTTON(ImGui.NavInput.DpadLeft, 14); // D-Pad Left MAP_BUTTON(ImGui.NavInput.DpadRight, 15); // D-Pad Right MAP_BUTTON(ImGui.NavInput.DpadUp, 12); // D-Pad Up MAP_BUTTON(ImGui.NavInput.DpadDown, 13); // D-Pad Down MAP_BUTTON(ImGui.NavInput.FocusPrev, 4); // L1 / LB MAP_BUTTON(ImGui.NavInput.FocusNext, 5); // R1 / RB MAP_ANALOG(ImGui.NavInput.TweakSlow, 6, +0.3, +0.9); // L2 / LT MAP_ANALOG(ImGui.NavInput.TweakFast, 7, +0.3, +0.9); // R2 / RT MAP_ANALOG(ImGui.NavInput.LStickLeft, 0, -0.3, -0.9); MAP_ANALOG(ImGui.NavInput.LStickRight, 0, +0.3, +0.9); MAP_ANALOG(ImGui.NavInput.LStickUp, 1, -0.3, -0.9); MAP_ANALOG(ImGui.NavInput.LStickDown, 1, +0.3, +0.9); break; case "2dc86001": // 8Bitdo SN30 Pro 8Bitdo SN30 Pro (Vendor: 2dc8 Product: 6001) case "2dc86101": // 8Bitdo SN30 Pro (Vendor: 2dc8 Product: 6101) MAP_BUTTON(ImGui.NavInput.Activate, 1); // Cross / A MAP_BUTTON(ImGui.NavInput.Cancel, 0); // Circle / B MAP_BUTTON(ImGui.NavInput.Menu, 4); // Square / X MAP_BUTTON(ImGui.NavInput.Input, 3); // Triangle / Y MAP_ANALOG(ImGui.NavInput.DpadLeft, 6, -0.3, -0.9); // D-Pad Left MAP_ANALOG(ImGui.NavInput.DpadRight, 6, +0.3, +0.9); // D-Pad Right MAP_ANALOG(ImGui.NavInput.DpadUp, 7, -0.3, -0.9); // D-Pad Up MAP_ANALOG(ImGui.NavInput.DpadDown, 7, +0.3, +0.9); // D-Pad Down MAP_BUTTON(ImGui.NavInput.FocusPrev, 6); // L1 / LB MAP_BUTTON(ImGui.NavInput.FocusNext, 7); // R1 / RB MAP_BUTTON(ImGui.NavInput.TweakSlow, 8); // L2 / LT MAP_BUTTON(ImGui.NavInput.TweakFast, 9); // R2 / RT MAP_ANALOG(ImGui.NavInput.LStickLeft, 0, -0.3, -0.9); MAP_ANALOG(ImGui.NavInput.LStickRight, 0, +0.3, +0.9); MAP_ANALOG(ImGui.NavInput.LStickUp, 1, -0.3, -0.9); MAP_ANALOG(ImGui.NavInput.LStickDown, 1, +0.3, +0.9); break; default: // standard gamepad: https://w3c.github.io/gamepad/#remapping MAP_BUTTON(ImGui.NavInput.Activate, 0); // Cross / A MAP_BUTTON(ImGui.NavInput.Cancel, 1); // Circle / B MAP_BUTTON(ImGui.NavInput.Menu, 2); // Square / X MAP_BUTTON(ImGui.NavInput.Input, 3); // Triangle / Y MAP_BUTTON(ImGui.NavInput.DpadLeft, 14); // D-Pad Left MAP_BUTTON(ImGui.NavInput.DpadRight, 15); // D-Pad Right MAP_BUTTON(ImGui.NavInput.DpadUp, 12); // D-Pad Up MAP_BUTTON(ImGui.NavInput.DpadDown, 13); // D-Pad Down MAP_BUTTON(ImGui.NavInput.FocusPrev, 4); // L1 / LB MAP_BUTTON(ImGui.NavInput.FocusNext, 5); // R1 / RB MAP_BUTTON(ImGui.NavInput.TweakSlow, 6); // L2 / LT MAP_BUTTON(ImGui.NavInput.TweakFast, 7); // R2 / RT MAP_ANALOG(ImGui.NavInput.LStickLeft, 0, -0.3, -0.9); MAP_ANALOG(ImGui.NavInput.LStickRight, 0, +0.3, +0.9); MAP_ANALOG(ImGui.NavInput.LStickUp, 1, -0.3, -0.9); MAP_ANALOG(ImGui.NavInput.LStickDown, 1, +0.3, +0.9); break; } } } ImGui.NewFrame(); this.update(dt); ImGui.EndFrame(); ImGui.Render(); app.renderer.render(this.imgui, this.tex); this.surface.interactive = this.io.WantCaptureMouse; } } let canvas: HTMLCanvasElement | null = null; //export let gl: WebGL2RenderingContext | null = null; let g_ShaderHandle: WebGLProgram | null = null; let g_VertHandle: WebGLShader | null = null; let g_FragHandle: WebGLShader | null = null; let g_AttribLocationTex: WebGLUniformLocation | null = null; let g_AttribLocationProjMtx: WebGLUniformLocation | null = null; let g_AttribLocationPosition: GLint = -1; let g_AttribLocationUV: GLint = -1; let g_AttribLocationColor: GLint = -1; let g_VaoHandle: WebGLVertexArrayObject = null; let g_VboHandle: WebGLBuffer | null = null; let g_ElementsHandle: WebGLBuffer | null = null; let g_FontTexture: WebGLTexture | null = null; function document_on_copy(event: ClipboardEvent): void { if (event.clipboardData) { event.clipboardData.setData("text/plain", clipboard_text); } // console.log(`${event.type}: "${clipboard_text}"`); event.preventDefault(); } function document_on_cut(event: ClipboardEvent): void { if (event.clipboardData) { event.clipboardData.setData("text/plain", clipboard_text); } // console.log(`${event.type}: "${clipboard_text}"`); event.preventDefault(); } function document_on_paste(event: ClipboardEvent): void { if (event.clipboardData) { clipboard_text = event.clipboardData.getData("text/plain"); } // console.log(`${event.type}: "${clipboard_text}"`); event.preventDefault(); } function window_on_gamepadconnected(event: any /* GamepadEvent */): void { console.log("Gamepad connected at index %d: %s. %d buttons, %d axes.", event.gamepad.index, event.gamepad.id, event.gamepad.buttons.length, event.gamepad.axes.length); } function window_on_gamepaddisconnected(event: any /* GamepadEvent */): void { console.log("Gamepad disconnected at index %d: %s.", event.gamepad.index, event.gamepad.id); } function canvas_on_blur(event: FocusEvent): void { for (var io of contextIOs) { io.KeyCtrl = false; io.KeyShift = false; io.KeyAlt = false; io.KeySuper = false; for (let i = 0; i < io.KeysDown.length; ++i) { io.KeysDown[i] = false; } for (let i = 0; i < io.MouseDown.length; ++i) { io.MouseDown[i] = false; } } } function canvas_on_keydown(event: KeyboardEvent): void { // console.log(event.type, event.key, event.keyCode); for (var io of contextIOs) { io.KeyCtrl = event.ctrlKey; io.KeyShift = event.shiftKey; io.KeyAlt = event.altKey; io.KeySuper = event.metaKey; ImGui.IM_ASSERT(event.keyCode >= 0 && event.keyCode < ImGui.IM_ARRAYSIZE(io.KeysDown)); io.KeysDown[event.keyCode] = true; // forward to the keypress event if (/*io.WantCaptureKeyboard ||*/ event.key === "Tab") { event.preventDefault(); } } } function canvas_on_keyup(event: KeyboardEvent): void { // console.log(event.type, event.key, event.keyCode); for (var io of contextIOs) { io.KeyCtrl = event.ctrlKey; io.KeyShift = event.shiftKey; io.KeyAlt = event.altKey; io.KeySuper = event.metaKey; ImGui.IM_ASSERT(event.keyCode >= 0 && event.keyCode < ImGui.IM_ARRAYSIZE(io.KeysDown)); io.KeysDown[event.keyCode] = false; if (io.WantCaptureKeyboard) { event.preventDefault(); } } } function canvas_on_keypress(event: KeyboardEvent): void { // console.log(event.type, event.key, event.keyCode); for (var io of contextIOs) { io.AddInputCharacter(event.charCode); if (io.WantCaptureKeyboard) { event.preventDefault(); } } } // MouseEvent.button // A number representing a given button: // 0: Main button pressed, usually the left button or the un-initialized state // 1: Auxiliary button pressed, usually the wheel button or the middle button (if present) // 2: Secondary button pressed, usually the right button // 3: Fourth button, typically the Browser Back button // 4: Fifth button, typically the Browser Forward button const mouse_button_map: number[] = [ 0, 2, 1, 3, 4 ]; function canvas_on_contextmenu(event: Event): void { for (var io of contextIOs) { if (io.WantCaptureMouse) { event.preventDefault(); } } } function canvas_on_wheel(event: WheelEvent): void { for (var io of contextIOs) { let scale: number = 1.0; switch (event.deltaMode) { case event.DOM_DELTA_PIXEL: scale = 0.01; break; case event.DOM_DELTA_LINE: scale = 0.2; break; case event.DOM_DELTA_PAGE: scale = 1.0; break; } io.MouseWheelH = event.deltaX * scale; io.MouseWheel = -event.deltaY * scale; // Mouse wheel: 1 unit scrolls about 5 lines text. if (io.WantCaptureMouse) { event.preventDefault(); } } } export function PrePIXIInit(): void { PIXI.Renderer.registerPlugin("imgui_renderer", U.fromConstructor(ImGuiImplInternalRenderer)); } export function Init(_app: PIXI.Application): void { // Setup Dear ImGui binding ImGui.IMGUI_CHECKVERSION(); if (typeof(document) !== "undefined") { document.body.addEventListener("copy", document_on_copy); document.body.addEventListener("cut", document_on_cut); document.body.addEventListener("paste", document_on_paste); } if (typeof(window) !== "undefined") { window.addEventListener("gamepadconnected", window_on_gamepadconnected); window.addEventListener("gamepaddisconnected", window_on_gamepaddisconnected); } app = _app; gl = (app.renderer as any).gl; canvas = app.view; gl.getExtension("EXT_color_buffer_float"); if (canvas !== null) { canvas.style.touchAction = "none"; // Disable browser handling of all panning and zooming gestures. canvas.addEventListener("blur", canvas_on_blur); canvas.addEventListener("keydown", canvas_on_keydown); canvas.addEventListener("keyup", canvas_on_keyup); canvas.addEventListener("keypress", canvas_on_keypress); canvas.addEventListener("contextmenu", canvas_on_contextmenu); canvas.addEventListener("wheel", canvas_on_wheel); } CreateDeviceObjects(); } export function Shutdown(): void { DestroyDeviceObjects(); if (canvas !== null) { canvas.removeEventListener("blur", canvas_on_blur); canvas.removeEventListener("keydown", canvas_on_keydown); canvas.removeEventListener("keyup", canvas_on_keyup); canvas.removeEventListener("keypress", canvas_on_keypress); canvas.removeEventListener("contextmenu", canvas_on_contextmenu); canvas.removeEventListener("wheel", canvas_on_wheel); } app = null; canvas = null; if (typeof(window) !== "undefined") { window.removeEventListener("gamepadconnected", window_on_gamepadconnected); window.removeEventListener("gamepaddisconnected", window_on_gamepaddisconnected); } if (typeof(document) !== "undefined") { document.body.removeEventListener("copy", document_on_copy); document.body.removeEventListener("cut", document_on_cut); document.body.removeEventListener("paste", document_on_paste); } } function CreateFontsTexture(): void { const io = ImGui.GetIO(); // Backup GL state const last_texture: WebGLTexture | null = gl && gl.getParameter(gl.TEXTURE_BINDING_2D); // Build texture atlas // const width: number = 256; // const height: number = 256; // const pixels: Uint8Array = new Uint8Array(4 * width * height).fill(0xff); const { width, height, pixels } = io.Fonts.GetTexDataAsRGBA32(); // Load as RGBA 32-bits (75% of the memory is wasted, but default font is so small) because it is more likely to be compatible with user's existing shaders. If your ImTextureId represent a higher-level concept than just a GL texture id, consider calling GetTexDataAsAlpha8() instead to save on GPU memory. // console.log(`font texture ${width} x ${height} @ ${pixels.length}`); // Upload texture to graphics system g_FontTexture = gl && gl.createTexture(); gl && gl.bindTexture(gl.TEXTURE_2D, g_FontTexture); gl && gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); gl && gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); // gl && gl.pixelStorei(gl.UNPACK_ROW_LENGTH); // WebGL2 gl && gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, pixels); // Store our identifier io.Fonts.TexID = g_FontTexture || { foo: "bar" }; // console.log("font texture id", g_FontTexture); // Restore modified GL state gl && last_texture && gl.bindTexture(gl.TEXTURE_2D, last_texture); } function DestroyFontsTexture(): void { const io = ImGui.GetIO(); io.Fonts.TexID = null; gl && gl.deleteTexture(g_FontTexture); g_FontTexture = null; } function CreateDeviceObjects(): void { const vertex_shader: string[] = [ "#version 300 es", "uniform mat4 ProjMtx;", "in vec2 Position;", "in vec2 UV;", "in vec4 Color;", "out vec2 Frag_UV;", "out vec4 Frag_Color;", "void main() {", " Frag_UV = UV;", " Frag_Color = Color;", " gl_Position = ProjMtx * vec4(Position.xy,0,1);", "}", ]; const fragment_shader: string[] = [ "#version 300 es", "precision mediump float;", // WebGL requires precision specifiers "uniform sampler2D Texture;", "in vec2 Frag_UV;", "in vec4 Frag_Color;", "out vec4 OutColor;", "void main() {", " OutColor = Frag_Color * texture(Texture, Frag_UV);", "}", ]; g_ShaderHandle = gl && gl.createProgram(); g_VertHandle = gl && gl.createShader(gl.VERTEX_SHADER); g_FragHandle = gl && gl.createShader(gl.FRAGMENT_SHADER); gl && gl.shaderSource(g_VertHandle as WebGLShader, vertex_shader.join("\n")); gl && gl.shaderSource(g_FragHandle as WebGLShader, fragment_shader.join("\n")); gl && gl.compileShader(g_VertHandle as WebGLShader); gl && gl.compileShader(g_FragHandle as WebGLShader); gl && gl.attachShader(g_ShaderHandle as WebGLProgram, g_VertHandle as WebGLShader); gl && gl.attachShader(g_ShaderHandle as WebGLProgram, g_FragHandle as WebGLShader); gl && gl.linkProgram(g_ShaderHandle as WebGLProgram); g_AttribLocationTex = gl && gl.getUniformLocation(g_ShaderHandle as WebGLProgram, "Texture"); g_AttribLocationProjMtx = gl && gl.getUniformLocation(g_ShaderHandle as WebGLProgram, "ProjMtx"); g_AttribLocationPosition = gl && gl.getAttribLocation(g_ShaderHandle as WebGLProgram, "Position") || 0; g_AttribLocationUV = gl && gl.getAttribLocation(g_ShaderHandle as WebGLProgram, "UV") || 0; g_AttribLocationColor = gl && gl.getAttribLocation(g_ShaderHandle as WebGLProgram, "Color") || 0; g_VaoHandle = gl && gl.createVertexArray(); g_VboHandle = gl && gl.createBuffer(); g_ElementsHandle = gl && gl.createBuffer(); } function DestroyDeviceObjects(): void { DestroyFontsTexture(); gl && gl.deleteVertexArray(g_VaoHandle); g_VaoHandle = null; gl && gl.deleteBuffer(g_VboHandle); g_VboHandle = null; gl && gl.deleteBuffer(g_ElementsHandle); g_ElementsHandle = null; g_AttribLocationTex = null; g_AttribLocationProjMtx = null; g_AttribLocationPosition = -1; g_AttribLocationUV = -1; g_AttribLocationColor = -1; gl && gl.deleteProgram(g_ShaderHandle); g_ShaderHandle = null; gl && gl.deleteShader(g_VertHandle); g_VertHandle = null; gl && gl.deleteShader(g_FragHandle); g_FragHandle = null; }