|
| 1 | +-- Demonstrates masked drawing with ImGui. |
| 2 | + |
| 3 | +local mq = require 'mq' |
| 4 | +local imgui = require 'ImGui' |
| 5 | + |
| 6 | +local winTex ---@type MQTexture |
| 7 | +local logoTex ---@type MQTexture |
| 8 | +local classAnim ---@type CTextureAnimation |
| 9 | + |
| 10 | +local isOpen = true |
| 11 | + |
| 12 | +-- window_pieces01.tga: 256x256 sprite sheet |
| 13 | +-- A_RecessedBox region: pixel (180,110)-(221,151), 41×41 px |
| 14 | +local TEX_W, TEX_H = 256.0, 256.0 |
| 15 | +local NS_SRC_X1, NS_SRC_Y1 = 180, 110 |
| 16 | +local NS_SRC_X2, NS_SRC_Y2 = 221, 151 |
| 17 | + |
| 18 | +-- A_GaugeBackground region: pixel (108,7)-(208,17) — used in coverage mask Demo B |
| 19 | +local GAUGE_UV_MIN = ImVec2(108 / 256, 7 / 256) |
| 20 | +local GAUGE_UV_MAX = ImVec2(208 / 256, 17 / 256) |
| 21 | + |
| 22 | +-- Stencil Mask tab state |
| 23 | +local stencilComboIdx = AlphaMaskOp.Union + 1 |
| 24 | + |
| 25 | +local function loadTextures() |
| 26 | + if not winTex then |
| 27 | + winTex = mq.CreateTexture('uifiles/default/window_pieces01.tga') |
| 28 | + end |
| 29 | + if not logoTex then |
| 30 | + logoTex = mq.CreateTexture('uifiles/default/EQLS_background_01.tga') |
| 31 | + end |
| 32 | + if not classAnim then |
| 33 | + classAnim = mq.FindTextureAnimation('A_ClassAnim06') |
| 34 | + end |
| 35 | +end |
| 36 | + |
| 37 | + |
| 38 | +-- |
| 39 | +-- Tab 1: Stencil Mask |
| 40 | +-- |
| 41 | + |
| 42 | +local function drawStencilMaskTab() |
| 43 | + imgui.TextWrapped( |
| 44 | + 'Stencil masking composites two mask layers with a boolean operation, ' .. |
| 45 | + 'then draws content clipped to the result.\n\n' .. |
| 46 | + 'Each CreateMaskLayer call begins a new mask layer; shapes drawn ' .. |
| 47 | + 'afterward become that layer\'s mask. BeginMaskedDraw combines them ' .. |
| 48 | + 'all with the chosen operation and clips all subsequent draws until ' .. |
| 49 | + 'EndMaskedDraw.') |
| 50 | + |
| 51 | + imgui.Spacing() |
| 52 | + imgui.SetNextItemWidth(200) |
| 53 | + stencilComboIdx = imgui.Combo('AlphaMaskOp##stencil', stencilComboIdx, |
| 54 | + 'Intersect\0Union\0Subtract\0Complement\0\0') |
| 55 | + |
| 56 | + imgui.Spacing() |
| 57 | + imgui.Text('Mask Layer 0: 100x100 filled rect (+10, +10) to (+110, +110)') |
| 58 | + imgui.Text('Mask Layer 1: circle r=25 centered at (+110, +60)') |
| 59 | + imgui.Text('Content: texture drawn over 200x200 canvas') |
| 60 | + imgui.Spacing() |
| 61 | + |
| 62 | + local dl = imgui.GetWindowDrawList() |
| 63 | + local p = imgui.GetCursorScreenPosVec() |
| 64 | + |
| 65 | + -- Layer 0 — rectangular mask shape |
| 66 | + dl:CreateMaskLayer() |
| 67 | + dl:AddRectFilled( |
| 68 | + ImVec2(p.x + 10, p.y + 10), |
| 69 | + ImVec2(p.x + 110, p.y + 110), |
| 70 | + IM_COL32(255, 255, 255, 255)) |
| 71 | + |
| 72 | + -- Layer 1 — circular mask shape |
| 73 | + dl:CreateMaskLayer() |
| 74 | + dl:AddCircleFilled( |
| 75 | + ImVec2(p.x + 110, p.y + 60), |
| 76 | + 25, |
| 77 | + IM_COL32(255, 255, 255, 255)) |
| 78 | + |
| 79 | + -- Apply the chosen boolean operation and draw content through the mask |
| 80 | + dl:BeginMaskedDraw(stencilComboIdx - 1) |
| 81 | + dl:AddImage( |
| 82 | + logoTex:GetTextureID(), |
| 83 | + ImVec2(p.x, p.y), |
| 84 | + ImVec2(p.x + 200, p.y + 200)) |
| 85 | + dl:EndMaskedDraw() |
| 86 | + |
| 87 | + -- Reserve layout space so other widgets don't overlap the drawn area |
| 88 | + imgui.Dummy(200, 200) |
| 89 | + |
| 90 | + imgui.Spacing() |
| 91 | + imgui.Separator() |
| 92 | + imgui.TextWrapped( |
| 93 | + 'Union: content shows where rect OR circle exists\n' .. |
| 94 | + 'Intersect: only where both overlap\n' .. |
| 95 | + 'Subtract: rect area minus the circle\n' .. |
| 96 | + 'Complement: inverts the final combined mask') |
| 97 | +end |
| 98 | + |
| 99 | +-- Tab 2: Coverage Mask |
| 100 | +-- Demonstrates framebuffer-alpha-based soft masking. |
| 101 | +local function drawCoverageMaskTab() |
| 102 | + imgui.TextWrapped( |
| 103 | + 'Coverage masking uses the framebuffer alpha channel as a per-pixel ' .. |
| 104 | + 'opacity map.\n\n' .. |
| 105 | + 'CreateCoverageMaskLayer defines the region. Draw the mask shape ' .. |
| 106 | + 'normally — the alpha of those draws is stored as the mask. ' .. |
| 107 | + 'BeginCoverageMaskedDraw / EndCoverageMaskedDraw then multiplies ' .. |
| 108 | + 'content alpha by that stored mask, producing smooth soft edges.\n\n' .. |
| 109 | + 'Unlike stencil masking, semi-transparent mask shapes produce ' .. |
| 110 | + 'proportionally faded content rather than hard clip boundaries.') |
| 111 | + |
| 112 | + imgui.Spacing() |
| 113 | + |
| 114 | + local dl = imgui.GetWindowDrawList() |
| 115 | + |
| 116 | + -- Demo A: animated texture silhouette as a coverage mask |
| 117 | + -- The class texture's alpha channel (includes transparency in the sprite |
| 118 | + -- background) becomes the mask — content fills the texture's silhouette with |
| 119 | + -- a rainbow gradient and fades naturally at the sprite's soft edges. |
| 120 | + imgui.Separator() |
| 121 | + imgui.Text('Demo A — CTextureAnimation alpha as coverage mask') |
| 122 | + imgui.Separator() |
| 123 | + imgui.TextWrapped('Left = rainbow gradient masked to the textures\'s alpha silhouette\n' .. |
| 124 | + 'Right = unmasked texture for reference') |
| 125 | + imgui.Spacing() |
| 126 | + |
| 127 | + local size = ImVec2(64, 128) |
| 128 | + local a0 = imgui.GetCursorScreenPosVec() |
| 129 | + |
| 130 | + -- Write the texture's alpha channel into the coverage mask |
| 131 | + dl:CreateCoverageMaskLayer(a0, a0 + size) |
| 132 | + dl:AddTextureAnimation(classAnim, a0, size) |
| 133 | + |
| 134 | + -- Draw a rainbow gradient, feathered by the mask |
| 135 | + dl:BeginCoverageMaskedDraw() |
| 136 | + dl:AddRectFilledMultiColor(a0, a0 + size, |
| 137 | + IM_COL32(255, 50, 50, 255), -- top-left: red |
| 138 | + IM_COL32(255, 200, 50, 255), -- top-right: yellow |
| 139 | + IM_COL32( 50, 200, 255, 255), -- bottom-right: cyan |
| 140 | + IM_COL32(180, 50, 255, 255)) -- bottom-left: purple |
| 141 | + dl:EndCoverageMaskedDraw() |
| 142 | + |
| 143 | + imgui.Dummy(size) |
| 144 | + imgui.SameLine() |
| 145 | + imgui.DrawTextureAnimation(classAnim, size) |
| 146 | + |
| 147 | + imgui.Text('Note: Image will not appear if loaded before being ingame!') |
| 148 | + |
| 149 | + -- Demo B: Text as coverage mask |
| 150 | + imgui.Spacing() |
| 151 | + imgui.Separator() |
| 152 | + imgui.Text('Demo B — Text as coverage mask') |
| 153 | + imgui.Separator() |
| 154 | + imgui.Spacing() |
| 155 | + |
| 156 | + local bW, bH = 200, 36 |
| 157 | + local b0 = imgui.GetCursorScreenPosVec() |
| 158 | + local b1 = ImVec2(b0.x + bW, b0.y + bH) |
| 159 | + |
| 160 | + -- Write the gauge strip's alpha as the coverage mask |
| 161 | + dl:CreateCoverageMaskLayer(b0, b1) |
| 162 | + dl:AddText(nil, 36, b0, IM_COL32(255, 255, 255, 255), "Hello, World!") |
| 163 | + |
| 164 | + -- Draw a blue-to-green gradient, shaped by the gauge strip's alpha |
| 165 | + dl:BeginCoverageMaskedDraw() |
| 166 | + dl:AddRectFilledMultiColor(b0, b1, |
| 167 | + IM_COL32(255, 50, 50, 255), -- top-left: red |
| 168 | + IM_COL32(255, 200, 50, 255), -- top-right: yellow |
| 169 | + IM_COL32( 50, 200, 255, 255), -- bottom-right: cyan |
| 170 | + IM_COL32(180, 50, 255, 255)) -- bottom-left: purple |
| 171 | + dl:EndCoverageMaskedDraw() |
| 172 | + |
| 173 | + imgui.Dummy(bW, bH) |
| 174 | +end |
| 175 | + |
| 176 | +local function renderUI() |
| 177 | + loadTextures() |
| 178 | + if not isOpen then return end |
| 179 | + |
| 180 | + local show |
| 181 | + isOpen, show = imgui.Begin('ImGui Masked Drawing Demo', isOpen) |
| 182 | + if show then |
| 183 | + if imgui.BeginTabBar('##NSDemoTabs') then |
| 184 | + if imgui.BeginTabItem('Stencil Mask') then |
| 185 | + drawStencilMaskTab() |
| 186 | + imgui.EndTabItem() |
| 187 | + end |
| 188 | + if imgui.BeginTabItem('Coverage Mask') then |
| 189 | + drawCoverageMaskTab() |
| 190 | + imgui.EndTabItem() |
| 191 | + end |
| 192 | + imgui.EndTabBar() |
| 193 | + end |
| 194 | + end |
| 195 | + imgui.End() |
| 196 | +end |
| 197 | + |
| 198 | +mq.imgui.init('imgui_mask_nineslice_demo', renderUI) |
| 199 | + |
| 200 | +while isOpen do |
| 201 | + mq.delay(1000) |
| 202 | +end |
0 commit comments