diff --git a/src/imgui_impl.ts b/src/imgui_impl.ts index a6d069b..9655604 100644 --- a/src/imgui_impl.ts +++ b/src/imgui_impl.ts @@ -1,24 +1,377 @@ 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 = ""; -export class ImGuiImplPIXI +class ImGuiImplInternalRenderer extends PIXI.ObjectRenderer { - private readonly app: PIXI.Application; - private readonly owner: PIXI.DisplayObject; - private readonly ctx: ImGui.ImGuiContext; - constructor(app:PIXI.Application, owner:PIXI.DisplayObject) + constructor(r: PIXI.Renderer) { - this.app = app; - this.owner = owner; + super(r); + } + render(owner: PIXI.DisplayObject): void + { + + const io: ImGui.ImGuiIO = ImGui.GetIO(); + 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_active_texture: GLenum | null = gl && gl.getParameter(gl.ACTIVE_TEXTURE) || null; + const last_program: WebGLProgram | null = gl && gl.getParameter(gl.CURRENT_PROGRAM) || null; + const last_texture: WebGLTexture | null = gl && gl.getParameter(gl.TEXTURE_BINDING_2D) || 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; + + // 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.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); + 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; + const ortho_projection: Float32Array = new Float32Array([ + 2.0 / (R - L), 0.0, 0.0, 0.0, + 0.0, 2.0 / (T - B), 0.0, 0.0, + 0.0, 0.0, -1.0, 0.0, + (R + L) / (L - R), (T + B) / (B - T), 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, fb_height - draw_cmd.ClipRect.w, 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, fb_height - clip_rect.w, clip_rect.z - clip_rect.x, clip_rect.w - clip_rect.y); + + // Bind texture, Draw + gl && gl.activeTexture(gl.TEXTURE0); + 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_texture !== null) && gl.bindTexture(gl.TEXTURE_2D, last_texture); + 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_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_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_src_alpha, last_blend_dst_rgb, 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]); + 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]); + } +} + +let gl: WebGL2RenderingContext = null; +let app: PIXI.Application = null; + +export class ImGuiWindow extends PIXI.Container +{ + public readonly ctx: ImGui.ImGuiContext; + 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}); + private tex: PIXI.RenderTexture = new PIXI.RenderTexture(this.baseTex); + + private surface: PIXI.Sprite = new PIXI.Sprite(this.tex); + private imgui: PIXI.Sprite = new PIXI.Sprite(); + + private update: (dt: number) => void; + constructor(sizeX: number, sizeY: number, update: (dt: number) => void) + { + super(); this.ctx = ImGui.CreateContext(); + this.update = update; + this.resize(sizeX, sizeY); + this.imgui.pluginName = 'imgui_renderer'; + this.addChild(this.surface); + this.surface.addListener("mousedown", this.mouseDown.bind(this)); + this.surface.addListener("mouseup", this.mouseUp.bind(this)); + app.ticker.add(this.updateInternal.bind(this)); + } + mouseDown(e: PIXI.interaction.InteractionEvent): void + { + const io: ImGui.ImGuiIO = ImGui.GetIO(); + io.MouseDown[mouse_button_map[0]] = true; + } + mouseUp(e: PIXI.interaction.InteractionEvent): void + { + const io: ImGui.ImGuiIO = ImGui.GetIO(); + 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); + this.surface.hitArea = this.tex.frame; + } + updateInternal(deltaTime: number) + { + const dt: number = deltaTime * 1 / (1000 * PIXI.settings.TARGET_FPMS); + + ImGui.SetCurrentContext(this.ctx); + + const io: ImGui.ImGuiIO = ImGui.GetIO(); + + if (io.WantSaveIniSettings) { + io.WantSaveIniSettings = false; + if (typeof(window) !== "undefined") { + window.localStorage.setItem("imgui.ini", ImGui.SaveIniSettingsToMemory()); + } + } + + io.DisplaySize.x = this.sizeX; + io.DisplaySize.y = this.sizeY; + io.DisplayFramebufferScale.x = 1; + io.DisplayFramebufferScale.y = 1; + + io.DeltaTime = dt; + + // TODO global mouse pos to local + const localPos: PIXI.Point = this.getGlobalPosition(new PIXI.Point()); + io.MousePos.x = localPos.x; + io.MousePos.y = localPos.y; + + if (io.WantSetMousePos) { + console.log("TODO: MousePos", io.MousePos.x, io.MousePos.y); + } + + if (typeof(document) !== "undefined") { + if (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 < io.NavInputs.length; ++i) { + io.NavInputs[i] = 0.0; + } + if (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) + 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 (io.NavInputs[NAV_NO] < v) 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); } } let canvas: HTMLCanvasElement | null = null; -export let gl: WebGL2RenderingContext | 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; @@ -31,8 +384,6 @@ let g_ElementsHandle: WebGLBuffer | null = null; let g_FontTexture: WebGLTexture | null = null; -export let ctx: CanvasRenderingContext2D | null = null; - let prev_time: number = 0; function document_on_copy(event: ClipboardEvent): void { @@ -122,16 +473,6 @@ } } -function canvas_on_pointermove(event: PointerEvent): void { - const io = ImGui.GetIO(); - const devicePixelRatio: number = window.devicePixelRatio || 1; - io.MousePos.x = event.offsetX * devicePixelRatio; - io.MousePos.y = event.offsetY * devicePixelRatio; - if (io.WantCaptureMouse) { - event.preventDefault(); - } -} - // MouseEvent.button // A number representing a given button: // 0: Main button pressed, usually the left button or the un-initialized state @@ -141,16 +482,6 @@ // 4: Fifth button, typically the Browser Forward button const mouse_button_map: number[] = [ 0, 2, 1, 3, 4 ]; -function canvas_on_pointerdown(event: PointerEvent): void { - const io = ImGui.GetIO(); - const devicePixelRatio: number = window.devicePixelRatio || 1; - io.MousePos.x = event.offsetX * devicePixelRatio; - io.MousePos.y = event.offsetY * devicePixelRatio; - io.MouseDown[mouse_button_map[event.button]] = true; - // if (io.WantCaptureMouse) { - // event.preventDefault(); - // } -} function canvas_on_contextmenu(event: Event): void { const io = ImGui.GetIO(); if (io.WantCaptureMouse) { @@ -158,14 +489,6 @@ } } -function canvas_on_pointerup(event: PointerEvent): void { - const io = ImGui.GetIO(); - io.MouseDown[mouse_button_map[event.button]] = false; - if (io.WantCaptureMouse) { - event.preventDefault(); - } -} - function canvas_on_wheel(event: WheelEvent): void { const io = ImGui.GetIO(); let scale: number = 1.0; @@ -181,7 +504,11 @@ } } -export function Init(value: HTMLCanvasElement | WebGL2RenderingContext | CanvasRenderingContext2D | null): void { +export function Init(_app: PIXI.Application): void { + // Setup Dear ImGui binding + ImGui.IMGUI_CHECKVERSION(); + + PIXI.Renderer.registerPlugin("imgui_renderer", U.fromConstructor(ImGuiImplInternalRenderer)); const io = ImGui.GetIO(); if (typeof(window) !== "undefined") { @@ -226,19 +553,9 @@ window.addEventListener("gamepaddisconnected", window_on_gamepaddisconnected); } - if (typeof(window) !== "undefined") { - if (value instanceof(HTMLCanvasElement)) { - value = value.getContext("webgl2", { alpha: false }) || value.getContext("2d"); - } - if (value instanceof(WebGL2RenderingContext)) { - canvas = value.canvas; - gl = value; - } - if (value instanceof(CanvasRenderingContext2D)) { - canvas = value.canvas; - ctx = value; - } - } + app = _app; + gl = (app.renderer as any).gl; + canvas = app.view; if (canvas !== null) { canvas.style.touchAction = "none"; // Disable browser handling of all panning and zooming gestures. @@ -246,10 +563,7 @@ canvas.addEventListener("keydown", canvas_on_keydown); canvas.addEventListener("keyup", canvas_on_keyup); canvas.addEventListener("keypress", canvas_on_keypress); - canvas.addEventListener("pointermove", canvas_on_pointermove); - canvas.addEventListener("pointerdown", canvas_on_pointerdown); canvas.addEventListener("contextmenu", canvas_on_contextmenu); - canvas.addEventListener("pointerup", canvas_on_pointerup); canvas.addEventListener("wheel", canvas_on_wheel); } @@ -290,15 +604,11 @@ canvas.removeEventListener("keydown", canvas_on_keydown); canvas.removeEventListener("keyup", canvas_on_keyup); canvas.removeEventListener("keypress", canvas_on_keypress); - canvas.removeEventListener("pointermove", canvas_on_pointermove); - canvas.removeEventListener("pointerdown", canvas_on_pointerdown); canvas.removeEventListener("contextmenu", canvas_on_contextmenu); - canvas.removeEventListener("pointerup", canvas_on_pointerup); canvas.removeEventListener("wheel", canvas_on_wheel); } - gl = null; - ctx = null; + app = null; canvas = null; if (typeof(window) !== "undefined") { @@ -314,368 +624,9 @@ } export function NewFrame(time: number): void { - const io = ImGui.GetIO(); - - if (io.WantSaveIniSettings) { - io.WantSaveIniSettings = false; - if (typeof(window) !== "undefined") { - window.localStorage.setItem("imgui.ini", ImGui.SaveIniSettingsToMemory()); - } - } - - const w: number = canvas && canvas.width || 640; - const h: number = canvas && canvas.height || 480; - const display_w: number = gl && gl.drawingBufferWidth || w; - const display_h: number = gl && gl.drawingBufferHeight || h; - io.DisplaySize.x = w; - io.DisplaySize.y = h; - io.DisplayFramebufferScale.x = w > 0 ? (display_w / w) : 0; - io.DisplayFramebufferScale.y = h > 0 ? (display_h / h) : 0; - - const dt: number = time - prev_time; - prev_time = time; - io.DeltaTime = dt / 1000; - - if (io.WantSetMousePos) { - console.log("TODO: MousePos", io.MousePos.x, io.MousePos.y); - } - - if (typeof(document) !== "undefined") { - if (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 < io.NavInputs.length; ++i) { - io.NavInputs[i] = 0.0; - } - if (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) - 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 (io.NavInputs[NAV_NO] < v) 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; - } - } - } } export function RenderDrawData(draw_data: ImGui.ImDrawData | null = ImGui.GetDrawData()): void { - const io = ImGui.GetIO(); - if (draw_data === null) { throw new Error(); } - - gl || ctx || 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_active_texture: GLenum | null = gl && gl.getParameter(gl.ACTIVE_TEXTURE) || null; - const last_program: WebGLProgram | null = gl && gl.getParameter(gl.CURRENT_PROGRAM) || null; - const last_texture: WebGLTexture | null = gl && gl.getParameter(gl.TEXTURE_BINDING_2D) || 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; - - // 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.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); - 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; - const ortho_projection: Float32Array = new Float32Array([ - 2.0 / (R - L), 0.0, 0.0, 0.0, - 0.0, 2.0 / (T - B), 0.0, 0.0, - 0.0, 0.0, -1.0, 0.0, - (R + L) / (L - R), (T + B) / (B - T), 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 || ctx || console.log(draw_list); - gl || ctx || console.log("VtxBuffer.length", draw_list.VtxBuffer.length); - gl || ctx || 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 || ctx || console.log(draw_cmd); - gl || ctx || console.log("ElemCount", draw_cmd.ElemCount); - gl || ctx || console.log("ClipRect", draw_cmd.ClipRect.x, fb_height - draw_cmd.ClipRect.w, draw_cmd.ClipRect.z - draw_cmd.ClipRect.x, draw_cmd.ClipRect.w - draw_cmd.ClipRect.y); - gl || ctx || console.log("TextureId", draw_cmd.TextureId); - if (!gl && !ctx) { - 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, fb_height - clip_rect.w, clip_rect.z - clip_rect.x, clip_rect.w - clip_rect.y); - - // Bind texture, Draw - gl && gl.activeTexture(gl.TEXTURE0); - gl && gl.bindTexture(gl.TEXTURE_2D, draw_cmd.TextureId); - gl && gl.drawElements(gl.TRIANGLES, draw_cmd.ElemCount, idx_buffer_type, idx_buffer_offset); - - if (ctx) { - ctx.save(); - ctx.beginPath(); - ctx.rect(clip_rect.x, clip_rect.y, clip_rect.z - clip_rect.x, clip_rect.w - clip_rect.y); - ctx.clip(); - const idx = ImGui.ImDrawIdxSize === 4 ? - new Uint32Array(draw_list.IdxBuffer.buffer, draw_list.IdxBuffer.byteOffset + idx_buffer_offset) : - new Uint16Array(draw_list.IdxBuffer.buffer, draw_list.IdxBuffer.byteOffset + idx_buffer_offset); - for (let i = 0; i < draw_cmd.ElemCount; i += 3) { - const i0: number = idx[i + 0]; - const i1: number = idx[i + 1]; - const i2: number = idx[i + 2]; - const v0: ImGui.ImDrawVert = new ImGui.ImDrawVert(draw_list.VtxBuffer.buffer, draw_list.VtxBuffer.byteOffset + i0 * ImGui.ImDrawVertSize); - const v1: ImGui.ImDrawVert = new ImGui.ImDrawVert(draw_list.VtxBuffer.buffer, draw_list.VtxBuffer.byteOffset + i1 * ImGui.ImDrawVertSize); - const v2: ImGui.ImDrawVert = new ImGui.ImDrawVert(draw_list.VtxBuffer.buffer, draw_list.VtxBuffer.byteOffset + i2 * ImGui.ImDrawVertSize); - const i3: number = idx[i + 3]; - const i4: number = idx[i + 4]; - const i5: number = idx[i + 5]; - const v3: ImGui.ImDrawVert = new ImGui.ImDrawVert(draw_list.VtxBuffer.buffer, draw_list.VtxBuffer.byteOffset + i3 * ImGui.ImDrawVertSize); - const v4: ImGui.ImDrawVert = new ImGui.ImDrawVert(draw_list.VtxBuffer.buffer, draw_list.VtxBuffer.byteOffset + i4 * ImGui.ImDrawVertSize); - const v5: ImGui.ImDrawVert = new ImGui.ImDrawVert(draw_list.VtxBuffer.buffer, draw_list.VtxBuffer.byteOffset + i5 * ImGui.ImDrawVertSize); - let quad = true; - let minmin: ImGui.ImDrawVert = v0; - let minmax: ImGui.ImDrawVert = v0; - let maxmin: ImGui.ImDrawVert = v0; - let maxmax: ImGui.ImDrawVert = v0; - for (const v of [ v1, v2, v3, v4, v5 ]) { - let found = false; - if (v.pos[0] <= minmin.pos[0] && v.pos[1] <= minmin.pos[1]) { minmin = v; found = true; } - if (v.pos[0] <= minmax.pos[0] && v.pos[1] >= minmax.pos[1]) { minmax = v; found = true; } - if (v.pos[0] >= maxmin.pos[0] && v.pos[1] <= maxmin.pos[1]) { maxmin = v; found = true; } - if (v.pos[0] >= maxmax.pos[0] && v.pos[1] >= maxmax.pos[1]) { maxmax = v; found = true; } - if (!found) { quad = false; } - } - quad = quad && (minmin.pos[0] === minmax.pos[0]); - quad = quad && (maxmin.pos[0] === maxmax.pos[0]); - quad = quad && (minmin.pos[1] === maxmin.pos[1]); - quad = quad && (minmax.pos[1] === maxmax.pos[1]); - if (quad) { - if (minmin.uv[0] < 0.01 && minmin.uv[1] < 0.01) { - // one vertex color - ctx.beginPath(); - ctx.rect(minmin.pos[0], minmin.pos[1], maxmax.pos[0] - minmin.pos[0], maxmax.pos[1] - minmin.pos[1]); - ctx.fillStyle = `rgba(${v0.col[0] >> 0 & 0xff}, ${v0.col[0] >> 8 & 0xff}, ${v0.col[0] >> 16 & 0xff}, ${(v0.col[0] >> 24 & 0xff) / 0xff})`; - ctx.fill(); - } else { - // no vertex color - const image = draw_cmd.TextureId as HTMLCanvasElement; - ctx.drawImage(image, - minmin.uv[0] * image.width, minmin.uv[1] * image.height, - (maxmax.uv[0] - minmin.uv[0]) * image.width, (maxmax.uv[1] - minmin.uv[1]) * image.height, - minmin.pos[0], minmin.pos[1], - maxmax.pos[0] - minmin.pos[0], maxmax.pos[1] - minmin.pos[1]); - // ctx.beginPath(); - // ctx.rect(minmin.pos[0], minmin.pos[1], maxmax.pos[0] - minmin.pos[0], maxmax.pos[1] - minmin.pos[1]); - // ctx.strokeStyle = "yellow"; - // ctx.stroke(); - } - i += 3; - } else { - // one vertex color, no texture - ctx.beginPath(); - ctx.moveTo(v0.pos[0], v0.pos[1]); - ctx.lineTo(v1.pos[0], v1.pos[1]); - ctx.lineTo(v2.pos[0], v2.pos[1]); - ctx.closePath(); - ctx.fillStyle = `rgba(${v0.col[0] >> 0 & 0xff}, ${v0.col[0] >> 8 & 0xff}, ${v0.col[0] >> 16 & 0xff}, ${(v0.col[0] >> 24 & 0xff) / 0xff})`; - ctx.fill(); - } - } - ctx.restore(); - } - } - } - - idx_buffer_offset += draw_cmd.ElemCount * ImGui.ImDrawIdxSize; - }); - }); - - // Restore modified GL state - gl && (last_program !== null) && gl.useProgram(last_program); - gl && (last_texture !== null) && gl.bindTexture(gl.TEXTURE_2D, last_texture); - 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_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_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_src_alpha, last_blend_dst_rgb, 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]); - 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]); } export function CreateFontsTexture(): void { @@ -703,18 +654,6 @@ io.Fonts.TexID = g_FontTexture || { foo: "bar" }; // console.log("font texture id", g_FontTexture); - if (ctx) { - const image_canvas: HTMLCanvasElement = document.createElement("canvas"); - image_canvas.width = width; - image_canvas.height = height; - const image_ctx = image_canvas.getContext("2d"); - if (image_ctx === null) { throw new Error(); } - const image_data = image_ctx.getImageData(0, 0, width, height); - image_data.data.set(pixels); - image_ctx.putImageData(image_data, 0, 0); - io.Fonts.TexID = image_canvas; - } - // Restore modified GL state gl && last_texture && gl.bindTexture(gl.TEXTURE_2D, last_texture); } diff --git a/src/imgui_impl.ts b/src/imgui_impl.ts index a6d069b..9655604 100644 --- a/src/imgui_impl.ts +++ b/src/imgui_impl.ts @@ -1,24 +1,377 @@ 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 = ""; -export class ImGuiImplPIXI +class ImGuiImplInternalRenderer extends PIXI.ObjectRenderer { - private readonly app: PIXI.Application; - private readonly owner: PIXI.DisplayObject; - private readonly ctx: ImGui.ImGuiContext; - constructor(app:PIXI.Application, owner:PIXI.DisplayObject) + constructor(r: PIXI.Renderer) { - this.app = app; - this.owner = owner; + super(r); + } + render(owner: PIXI.DisplayObject): void + { + + const io: ImGui.ImGuiIO = ImGui.GetIO(); + 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_active_texture: GLenum | null = gl && gl.getParameter(gl.ACTIVE_TEXTURE) || null; + const last_program: WebGLProgram | null = gl && gl.getParameter(gl.CURRENT_PROGRAM) || null; + const last_texture: WebGLTexture | null = gl && gl.getParameter(gl.TEXTURE_BINDING_2D) || 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; + + // 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.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); + 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; + const ortho_projection: Float32Array = new Float32Array([ + 2.0 / (R - L), 0.0, 0.0, 0.0, + 0.0, 2.0 / (T - B), 0.0, 0.0, + 0.0, 0.0, -1.0, 0.0, + (R + L) / (L - R), (T + B) / (B - T), 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, fb_height - draw_cmd.ClipRect.w, 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, fb_height - clip_rect.w, clip_rect.z - clip_rect.x, clip_rect.w - clip_rect.y); + + // Bind texture, Draw + gl && gl.activeTexture(gl.TEXTURE0); + 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_texture !== null) && gl.bindTexture(gl.TEXTURE_2D, last_texture); + 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_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_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_src_alpha, last_blend_dst_rgb, 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]); + 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]); + } +} + +let gl: WebGL2RenderingContext = null; +let app: PIXI.Application = null; + +export class ImGuiWindow extends PIXI.Container +{ + public readonly ctx: ImGui.ImGuiContext; + 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}); + private tex: PIXI.RenderTexture = new PIXI.RenderTexture(this.baseTex); + + private surface: PIXI.Sprite = new PIXI.Sprite(this.tex); + private imgui: PIXI.Sprite = new PIXI.Sprite(); + + private update: (dt: number) => void; + constructor(sizeX: number, sizeY: number, update: (dt: number) => void) + { + super(); this.ctx = ImGui.CreateContext(); + this.update = update; + this.resize(sizeX, sizeY); + this.imgui.pluginName = 'imgui_renderer'; + this.addChild(this.surface); + this.surface.addListener("mousedown", this.mouseDown.bind(this)); + this.surface.addListener("mouseup", this.mouseUp.bind(this)); + app.ticker.add(this.updateInternal.bind(this)); + } + mouseDown(e: PIXI.interaction.InteractionEvent): void + { + const io: ImGui.ImGuiIO = ImGui.GetIO(); + io.MouseDown[mouse_button_map[0]] = true; + } + mouseUp(e: PIXI.interaction.InteractionEvent): void + { + const io: ImGui.ImGuiIO = ImGui.GetIO(); + 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); + this.surface.hitArea = this.tex.frame; + } + updateInternal(deltaTime: number) + { + const dt: number = deltaTime * 1 / (1000 * PIXI.settings.TARGET_FPMS); + + ImGui.SetCurrentContext(this.ctx); + + const io: ImGui.ImGuiIO = ImGui.GetIO(); + + if (io.WantSaveIniSettings) { + io.WantSaveIniSettings = false; + if (typeof(window) !== "undefined") { + window.localStorage.setItem("imgui.ini", ImGui.SaveIniSettingsToMemory()); + } + } + + io.DisplaySize.x = this.sizeX; + io.DisplaySize.y = this.sizeY; + io.DisplayFramebufferScale.x = 1; + io.DisplayFramebufferScale.y = 1; + + io.DeltaTime = dt; + + // TODO global mouse pos to local + const localPos: PIXI.Point = this.getGlobalPosition(new PIXI.Point()); + io.MousePos.x = localPos.x; + io.MousePos.y = localPos.y; + + if (io.WantSetMousePos) { + console.log("TODO: MousePos", io.MousePos.x, io.MousePos.y); + } + + if (typeof(document) !== "undefined") { + if (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 < io.NavInputs.length; ++i) { + io.NavInputs[i] = 0.0; + } + if (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) + 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 (io.NavInputs[NAV_NO] < v) 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); } } let canvas: HTMLCanvasElement | null = null; -export let gl: WebGL2RenderingContext | 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; @@ -31,8 +384,6 @@ let g_ElementsHandle: WebGLBuffer | null = null; let g_FontTexture: WebGLTexture | null = null; -export let ctx: CanvasRenderingContext2D | null = null; - let prev_time: number = 0; function document_on_copy(event: ClipboardEvent): void { @@ -122,16 +473,6 @@ } } -function canvas_on_pointermove(event: PointerEvent): void { - const io = ImGui.GetIO(); - const devicePixelRatio: number = window.devicePixelRatio || 1; - io.MousePos.x = event.offsetX * devicePixelRatio; - io.MousePos.y = event.offsetY * devicePixelRatio; - if (io.WantCaptureMouse) { - event.preventDefault(); - } -} - // MouseEvent.button // A number representing a given button: // 0: Main button pressed, usually the left button or the un-initialized state @@ -141,16 +482,6 @@ // 4: Fifth button, typically the Browser Forward button const mouse_button_map: number[] = [ 0, 2, 1, 3, 4 ]; -function canvas_on_pointerdown(event: PointerEvent): void { - const io = ImGui.GetIO(); - const devicePixelRatio: number = window.devicePixelRatio || 1; - io.MousePos.x = event.offsetX * devicePixelRatio; - io.MousePos.y = event.offsetY * devicePixelRatio; - io.MouseDown[mouse_button_map[event.button]] = true; - // if (io.WantCaptureMouse) { - // event.preventDefault(); - // } -} function canvas_on_contextmenu(event: Event): void { const io = ImGui.GetIO(); if (io.WantCaptureMouse) { @@ -158,14 +489,6 @@ } } -function canvas_on_pointerup(event: PointerEvent): void { - const io = ImGui.GetIO(); - io.MouseDown[mouse_button_map[event.button]] = false; - if (io.WantCaptureMouse) { - event.preventDefault(); - } -} - function canvas_on_wheel(event: WheelEvent): void { const io = ImGui.GetIO(); let scale: number = 1.0; @@ -181,7 +504,11 @@ } } -export function Init(value: HTMLCanvasElement | WebGL2RenderingContext | CanvasRenderingContext2D | null): void { +export function Init(_app: PIXI.Application): void { + // Setup Dear ImGui binding + ImGui.IMGUI_CHECKVERSION(); + + PIXI.Renderer.registerPlugin("imgui_renderer", U.fromConstructor(ImGuiImplInternalRenderer)); const io = ImGui.GetIO(); if (typeof(window) !== "undefined") { @@ -226,19 +553,9 @@ window.addEventListener("gamepaddisconnected", window_on_gamepaddisconnected); } - if (typeof(window) !== "undefined") { - if (value instanceof(HTMLCanvasElement)) { - value = value.getContext("webgl2", { alpha: false }) || value.getContext("2d"); - } - if (value instanceof(WebGL2RenderingContext)) { - canvas = value.canvas; - gl = value; - } - if (value instanceof(CanvasRenderingContext2D)) { - canvas = value.canvas; - ctx = value; - } - } + app = _app; + gl = (app.renderer as any).gl; + canvas = app.view; if (canvas !== null) { canvas.style.touchAction = "none"; // Disable browser handling of all panning and zooming gestures. @@ -246,10 +563,7 @@ canvas.addEventListener("keydown", canvas_on_keydown); canvas.addEventListener("keyup", canvas_on_keyup); canvas.addEventListener("keypress", canvas_on_keypress); - canvas.addEventListener("pointermove", canvas_on_pointermove); - canvas.addEventListener("pointerdown", canvas_on_pointerdown); canvas.addEventListener("contextmenu", canvas_on_contextmenu); - canvas.addEventListener("pointerup", canvas_on_pointerup); canvas.addEventListener("wheel", canvas_on_wheel); } @@ -290,15 +604,11 @@ canvas.removeEventListener("keydown", canvas_on_keydown); canvas.removeEventListener("keyup", canvas_on_keyup); canvas.removeEventListener("keypress", canvas_on_keypress); - canvas.removeEventListener("pointermove", canvas_on_pointermove); - canvas.removeEventListener("pointerdown", canvas_on_pointerdown); canvas.removeEventListener("contextmenu", canvas_on_contextmenu); - canvas.removeEventListener("pointerup", canvas_on_pointerup); canvas.removeEventListener("wheel", canvas_on_wheel); } - gl = null; - ctx = null; + app = null; canvas = null; if (typeof(window) !== "undefined") { @@ -314,368 +624,9 @@ } export function NewFrame(time: number): void { - const io = ImGui.GetIO(); - - if (io.WantSaveIniSettings) { - io.WantSaveIniSettings = false; - if (typeof(window) !== "undefined") { - window.localStorage.setItem("imgui.ini", ImGui.SaveIniSettingsToMemory()); - } - } - - const w: number = canvas && canvas.width || 640; - const h: number = canvas && canvas.height || 480; - const display_w: number = gl && gl.drawingBufferWidth || w; - const display_h: number = gl && gl.drawingBufferHeight || h; - io.DisplaySize.x = w; - io.DisplaySize.y = h; - io.DisplayFramebufferScale.x = w > 0 ? (display_w / w) : 0; - io.DisplayFramebufferScale.y = h > 0 ? (display_h / h) : 0; - - const dt: number = time - prev_time; - prev_time = time; - io.DeltaTime = dt / 1000; - - if (io.WantSetMousePos) { - console.log("TODO: MousePos", io.MousePos.x, io.MousePos.y); - } - - if (typeof(document) !== "undefined") { - if (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 < io.NavInputs.length; ++i) { - io.NavInputs[i] = 0.0; - } - if (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) - 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 (io.NavInputs[NAV_NO] < v) 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; - } - } - } } export function RenderDrawData(draw_data: ImGui.ImDrawData | null = ImGui.GetDrawData()): void { - const io = ImGui.GetIO(); - if (draw_data === null) { throw new Error(); } - - gl || ctx || 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_active_texture: GLenum | null = gl && gl.getParameter(gl.ACTIVE_TEXTURE) || null; - const last_program: WebGLProgram | null = gl && gl.getParameter(gl.CURRENT_PROGRAM) || null; - const last_texture: WebGLTexture | null = gl && gl.getParameter(gl.TEXTURE_BINDING_2D) || 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; - - // 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.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); - 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; - const ortho_projection: Float32Array = new Float32Array([ - 2.0 / (R - L), 0.0, 0.0, 0.0, - 0.0, 2.0 / (T - B), 0.0, 0.0, - 0.0, 0.0, -1.0, 0.0, - (R + L) / (L - R), (T + B) / (B - T), 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 || ctx || console.log(draw_list); - gl || ctx || console.log("VtxBuffer.length", draw_list.VtxBuffer.length); - gl || ctx || 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 || ctx || console.log(draw_cmd); - gl || ctx || console.log("ElemCount", draw_cmd.ElemCount); - gl || ctx || console.log("ClipRect", draw_cmd.ClipRect.x, fb_height - draw_cmd.ClipRect.w, draw_cmd.ClipRect.z - draw_cmd.ClipRect.x, draw_cmd.ClipRect.w - draw_cmd.ClipRect.y); - gl || ctx || console.log("TextureId", draw_cmd.TextureId); - if (!gl && !ctx) { - 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, fb_height - clip_rect.w, clip_rect.z - clip_rect.x, clip_rect.w - clip_rect.y); - - // Bind texture, Draw - gl && gl.activeTexture(gl.TEXTURE0); - gl && gl.bindTexture(gl.TEXTURE_2D, draw_cmd.TextureId); - gl && gl.drawElements(gl.TRIANGLES, draw_cmd.ElemCount, idx_buffer_type, idx_buffer_offset); - - if (ctx) { - ctx.save(); - ctx.beginPath(); - ctx.rect(clip_rect.x, clip_rect.y, clip_rect.z - clip_rect.x, clip_rect.w - clip_rect.y); - ctx.clip(); - const idx = ImGui.ImDrawIdxSize === 4 ? - new Uint32Array(draw_list.IdxBuffer.buffer, draw_list.IdxBuffer.byteOffset + idx_buffer_offset) : - new Uint16Array(draw_list.IdxBuffer.buffer, draw_list.IdxBuffer.byteOffset + idx_buffer_offset); - for (let i = 0; i < draw_cmd.ElemCount; i += 3) { - const i0: number = idx[i + 0]; - const i1: number = idx[i + 1]; - const i2: number = idx[i + 2]; - const v0: ImGui.ImDrawVert = new ImGui.ImDrawVert(draw_list.VtxBuffer.buffer, draw_list.VtxBuffer.byteOffset + i0 * ImGui.ImDrawVertSize); - const v1: ImGui.ImDrawVert = new ImGui.ImDrawVert(draw_list.VtxBuffer.buffer, draw_list.VtxBuffer.byteOffset + i1 * ImGui.ImDrawVertSize); - const v2: ImGui.ImDrawVert = new ImGui.ImDrawVert(draw_list.VtxBuffer.buffer, draw_list.VtxBuffer.byteOffset + i2 * ImGui.ImDrawVertSize); - const i3: number = idx[i + 3]; - const i4: number = idx[i + 4]; - const i5: number = idx[i + 5]; - const v3: ImGui.ImDrawVert = new ImGui.ImDrawVert(draw_list.VtxBuffer.buffer, draw_list.VtxBuffer.byteOffset + i3 * ImGui.ImDrawVertSize); - const v4: ImGui.ImDrawVert = new ImGui.ImDrawVert(draw_list.VtxBuffer.buffer, draw_list.VtxBuffer.byteOffset + i4 * ImGui.ImDrawVertSize); - const v5: ImGui.ImDrawVert = new ImGui.ImDrawVert(draw_list.VtxBuffer.buffer, draw_list.VtxBuffer.byteOffset + i5 * ImGui.ImDrawVertSize); - let quad = true; - let minmin: ImGui.ImDrawVert = v0; - let minmax: ImGui.ImDrawVert = v0; - let maxmin: ImGui.ImDrawVert = v0; - let maxmax: ImGui.ImDrawVert = v0; - for (const v of [ v1, v2, v3, v4, v5 ]) { - let found = false; - if (v.pos[0] <= minmin.pos[0] && v.pos[1] <= minmin.pos[1]) { minmin = v; found = true; } - if (v.pos[0] <= minmax.pos[0] && v.pos[1] >= minmax.pos[1]) { minmax = v; found = true; } - if (v.pos[0] >= maxmin.pos[0] && v.pos[1] <= maxmin.pos[1]) { maxmin = v; found = true; } - if (v.pos[0] >= maxmax.pos[0] && v.pos[1] >= maxmax.pos[1]) { maxmax = v; found = true; } - if (!found) { quad = false; } - } - quad = quad && (minmin.pos[0] === minmax.pos[0]); - quad = quad && (maxmin.pos[0] === maxmax.pos[0]); - quad = quad && (minmin.pos[1] === maxmin.pos[1]); - quad = quad && (minmax.pos[1] === maxmax.pos[1]); - if (quad) { - if (minmin.uv[0] < 0.01 && minmin.uv[1] < 0.01) { - // one vertex color - ctx.beginPath(); - ctx.rect(minmin.pos[0], minmin.pos[1], maxmax.pos[0] - minmin.pos[0], maxmax.pos[1] - minmin.pos[1]); - ctx.fillStyle = `rgba(${v0.col[0] >> 0 & 0xff}, ${v0.col[0] >> 8 & 0xff}, ${v0.col[0] >> 16 & 0xff}, ${(v0.col[0] >> 24 & 0xff) / 0xff})`; - ctx.fill(); - } else { - // no vertex color - const image = draw_cmd.TextureId as HTMLCanvasElement; - ctx.drawImage(image, - minmin.uv[0] * image.width, minmin.uv[1] * image.height, - (maxmax.uv[0] - minmin.uv[0]) * image.width, (maxmax.uv[1] - minmin.uv[1]) * image.height, - minmin.pos[0], minmin.pos[1], - maxmax.pos[0] - minmin.pos[0], maxmax.pos[1] - minmin.pos[1]); - // ctx.beginPath(); - // ctx.rect(minmin.pos[0], minmin.pos[1], maxmax.pos[0] - minmin.pos[0], maxmax.pos[1] - minmin.pos[1]); - // ctx.strokeStyle = "yellow"; - // ctx.stroke(); - } - i += 3; - } else { - // one vertex color, no texture - ctx.beginPath(); - ctx.moveTo(v0.pos[0], v0.pos[1]); - ctx.lineTo(v1.pos[0], v1.pos[1]); - ctx.lineTo(v2.pos[0], v2.pos[1]); - ctx.closePath(); - ctx.fillStyle = `rgba(${v0.col[0] >> 0 & 0xff}, ${v0.col[0] >> 8 & 0xff}, ${v0.col[0] >> 16 & 0xff}, ${(v0.col[0] >> 24 & 0xff) / 0xff})`; - ctx.fill(); - } - } - ctx.restore(); - } - } - } - - idx_buffer_offset += draw_cmd.ElemCount * ImGui.ImDrawIdxSize; - }); - }); - - // Restore modified GL state - gl && (last_program !== null) && gl.useProgram(last_program); - gl && (last_texture !== null) && gl.bindTexture(gl.TEXTURE_2D, last_texture); - 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_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_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_src_alpha, last_blend_dst_rgb, 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]); - 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]); } export function CreateFontsTexture(): void { @@ -703,18 +654,6 @@ io.Fonts.TexID = g_FontTexture || { foo: "bar" }; // console.log("font texture id", g_FontTexture); - if (ctx) { - const image_canvas: HTMLCanvasElement = document.createElement("canvas"); - image_canvas.width = width; - image_canvas.height = height; - const image_ctx = image_canvas.getContext("2d"); - if (image_ctx === null) { throw new Error(); } - const image_data = image_ctx.getImageData(0, 0, width, height); - image_data.data.set(pixels); - image_ctx.putImageData(image_data, 0, 0); - io.Fonts.TexID = image_canvas; - } - // Restore modified GL state gl && last_texture && gl.bindTexture(gl.TEXTURE_2D, last_texture); } diff --git a/src/main.ts b/src/main.ts index 98b52be..0d51084 100644 --- a/src/main.ts +++ b/src/main.ts @@ -8,16 +8,11 @@ async function main(): Promise { - PIXI.Renderer.registerPlugin("imgui_renderer", U.fromConstructor(ImGuiRenderer)); - console.log("main()"); await ImGui.default(); window.requestAnimationFrame(init); } -let ctx: ImGui.ImGuiContext[] = []; -let currentCtx = 0; - const clearColor:ImVec4 = new ImVec4(0.7, 0.9, 1, 1); function init(): void { @@ -39,8 +34,6 @@ app.view.style.width = "100%"; app.view.style.height = "100%"; - // Setup Dear ImGui binding - ImGui.IMGUI_CHECKVERSION(); for (var i = 0; i < 2; i++) { let newCtx: ImGui.ImGuiContext = ImGui.CreateContext(); @@ -52,13 +45,9 @@ // Setup style ImGui.StyleColorsLight(); - ImGui_Impl.Init(app.view); - - ctx.push(newCtx); + ImGui_Impl.Init(this); } - ImGui.SetCurrentContext(ctx[0]); - app.ticker.add( (deltaTime: number) => {