---@diagnostic disable function onLoad(savedData) lookupInputIndexToInfo = {} lookupButtonIndexToInfo = {} lookupFieldIndices = {} lookupCheckIndices = {} lookupDecalIndices = {} lookupSelectionButtonIndices = {} lastFieldLockedMessage = 0 buttonIndex = 0 inputIndex = 0 -- load saved data if possible local loadedData = JSON.decode(savedData) or {} sheetLocked = loadedData.sheetLocked or false flip = loadedData.flip or "False" height = loadedData.height or 0.5 fields = loadedData.fields or {} checks = loadedData.checks or {} decals = loadedData.decals or {} wasCommitted = loadedData.wasCommitted or false local selfScale = self.getScale() scale = loadedData.scale or { x = math.floor(100 / selfScale.x) / 100, y = math.floor(100 / selfScale.z) / 100 } locks = loadedData.locks or { fields = false, checks = false, decals = false } if not wasCommitted then self.addContextMenuItem("Edit Layout", showEditPanel) self.addContextMenuItem("Commit Layout", showCommitPanel) nudgeDistance = self.script_state.nudgeDistance or 0.1 page = 1 editingSheet = false selectedId = 0 selectedArrayId = 1 selectedType = "" selectedMax = 5 creating = false else makeContextMenuItems() end createAll() end function makeContextMenuItems() self.clearContextMenu() if (not sheetLocked) then local getLocked = function(element) if (locks[element]) then return "◆ Lock" else return "◇ Lock" end end local allLocked = "Lock" if (locks.fields and locks.checks and locks.decals) then allLocked = "Unlock" end if (#fields > 0 and #checks > 0 and #decals > 0) then self.addContextMenuItem(allLocked .. " everything", toggleAllLocks) end for k, v in pairs(fields) do if (v.locked ~= "True") then self.addContextMenuItem(getLocked("fields") .. " texts", toggleLockFields) break end end for k, v in pairs(checks) do if (v.locked ~= "True") then self.addContextMenuItem(getLocked("checks") .. " checkboxes", toggleLockChecks) break end end for k, v in pairs(decals) do if (v.locked ~= "True") then self.addContextMenuItem(getLocked("decals") .. " images", toggleLockDecals) break end end end end function toggleAllLocks(ply, pos, obj) if (locks.fields and locks.checks and locks.decals) then locks.fields, locks.checks, locks.decals = false, false, false broadcastToColor("Unlocked everything", ply) else locks.fields, locks.checks, locks.decals = true, true, true broadcastToColor("Locked everything", ply) end updateSave() makeContextMenuItems() refresh() end function toggleLockFields(ply, pos, obj) locks.fields = not locks.fields broadcastToColor(locks.fields and "Locked texts" or "Unlocked texts", ply) updateSave() makeContextMenuItems() refresh() end function toggleLockChecks(ply, pos, obj) locks.checks = not locks.checks broadcastToColor(locks.checks and "Locked checkboxes" or "Unlocked checkboxes", ply) updateSave() makeContextMenuItems() refresh() end function toggleLockDecals(ply, pos, obj) locks.decals = not locks.decals broadcastToColor(locks.decals and "Locked images" or "Unlocked images", ply) updateSave() makeContextMenuItems() refresh() end function updateSave() self.script_state = JSON.encode({ scale = scale, height = height, fields = fields, checks = checks, decals = decals, flip = flip, sheetLocked = sheetLocked, locks = locks, nudgeDistance = not wasCommitted and nudgeDistance or nil, wasCommitted = wasCommitted }) end function null() end function refresh() if (not creating) then self.clearInputs() self.clearButtons() inputIndex = 0 buttonIndex = 0 if (editingSheet) then createSelectionHighlight() end createAll() end end function createAll() lookupInputIndexToInfo = {} lookupButtonIndexToInfo = {} lookupFieldIndices = {} lookupCheckIndices = {} lookupDecalIndices = {} startLuaCoroutine(self, "createAllCoroutine") end function createAllCoroutine() if not wasCommitted then UI.setAttribute(getPanelId("Loading"), "active", "True") end coroutine.yield(0) creating = true for fieldID, field in pairs(fields) do lookupFieldIndices[fieldID] = { inputs = {}, totals = {}, counterButtons = {}, selectionButtons = {} } local posx = field.pos.x local posy = field.pos.y local func = "edit" if (field.vsum == "True") then func = 'MarumEditableRecalculateSum_' .. fieldID _G[func] = function(obj, ply, input_value, selected) local fID = fieldID edit(obj, ply, input_value, selected) obj.call("recalculateVSums", fID) end else local iIndex = inputIndex if (field.locked == "True" or sheetLocked or locks.fields) then func = 'MarumEditableRevert_' .. fieldID _G[func] = function(obj, ply, input_value, selected) local fID = fieldID local iID = iIndex local sel = selected obj.call("revertField", { fID, ply, iID, sel }) end end end local fieldScale = { x = scale.x, y = 1, z = scale.y } local rotation = { x = 0, y = 0, z = 0 } local posMulx = 1 local flipped = 1 if (flip == "True") then rotation.y = 180 flipped = -1 end local upright = self.getTransformUp().y > 0 local shouldFlip = (field.locked == "True" or sheetLocked or locks.fields) and upright local unlockedRotation = { x = rotation.x, y = rotation.y, z = rotation.z } if (editingSheet or shouldFlip) then rotation.z = 180 posMulx = -posMulx fieldScale.x = -fieldScale.x end local vsum = 0 local fontSize = math.min(field.size.y - 24, field.font) local color = getFieldTextColor(fieldID) for x = 1, field.array.x do vsum = 0 for y = 1, field.array.y do local arrayID = x + (y - 1) * field.array.x local pos = getFieldPosition(fieldID, x, y) self.createInput({ value = field.value[arrayID], tooltip = getFieldTooltip(fieldID, arrayID), input_function = func, function_owner = self, alignment = field.align, position = pos, width = field.size.x, height = field.size.y, rotation = rotation, font_size = fontSize, scale = fieldScale, font_color = color, color = field.fieldColor, tab = 2 }) if (field.counter == "True") then local counterButtonWidth = fontSize * 0.75 _G['MarumEditableCounterIncrease_' .. fieldID .. '_' .. arrayID] = function(obj, ply, alt) local fID = fieldID local aID = arrayID obj.call("increaseCounter", { fID, aID, ply }) end buttonIndex = buttonIndex + 1 lookupButtonIndexToInfo[buttonIndex] = { type = "counter", id = fieldID } table.insert(lookupFieldIndices[fieldID].counterButtons, { index = buttonIndex, x = x, y = y, side = 1 }) self.createButton({ click_function = 'MarumEditableCounterIncrease_' .. fieldID .. '_' .. arrayID, tooltip = "↑ Increase ↑", function_owner = self, label = "[b]+[/b]", position = { x = pos.x + (field.size.x + counterButtonWidth) / 1000 * fieldScale.x * posMulx * flipped, y = pos.y, z = pos.z }, rotation = unlockedRotation, scale = fieldScale, width = counterButtonWidth, height = counterButtonWidth, font_size = fontSize / 2, font_color = color, color = field.fieldColor, }) _G['MarumEditableCounterDecrease_' .. fieldID .. '_' .. arrayID] = function(obj, ply, alt) local fID = fieldID local aID = arrayID obj.call("decreaseCounter", { fID, aID, ply }) end buttonIndex = buttonIndex + 1 table.insert(lookupFieldIndices[fieldID].counterButtons, { index = buttonIndex, x = x, y = y, side = -1 }) self.createButton({ click_function = 'MarumEditableCounterDecrease_' .. fieldID .. '_' .. arrayID, tooltip = "↓ Decrease ↓", function_owner = self, label = "[b]-[/b]", position = { x = pos.x - (field.size.x + counterButtonWidth) / 1000 * fieldScale.x * posMulx * flipped, y = pos.y, z = pos.z }, unlockedRotation = rotation, scale = fieldScale, width = counterButtonWidth, height = counterButtonWidth, font_size = fontSize / 2, font_color = color, color = field.fieldColor, }) end inputIndex = inputIndex + 1 lookupInputIndexToInfo[inputIndex] = { type = "field", id = fieldID, arrayID = arrayID } table.insert(lookupFieldIndices[fieldID].inputs, { index = inputIndex, arrayID = arrayID, x = x, y = y }) if (field.vsum == "True") then if (tonumber(field.value[arrayID])) then vsum = vsum + tonumber(field.value[arrayID]) end end if (inputIndex % 10 == 0) then coroutine.yield(0) end end if (field.vsum == "True") then local pos = getFieldPosition(fieldID, x, field.array.y + 1) local func = 'MarumEditableRevertSum_' .. fieldID _G[func] = function(obj, ply, input_value, selected) obj.call("revertFieldSum", { fieldID, ply, iIndex, selected }) end self.createInput({ value = "[u]" .. vsum .. "[/u]", tooltip = "[Sum]", input_function = func, function_owner = self, alignment = field.align, position = pos, width = field.size.x, height = field.size.y, rotation = { x = unlockedRotation.x, y = unlockedRotation.y, z = unlockedRotation.z + 180 }, font_size = fontSize, scale = { x = -scale.x, y = 1, z = scale.y }, font_color = color, color = field.fieldColor, tab = 0 }) inputIndex = inputIndex + 1 table.insert(lookupFieldIndices[fieldID].totals, { index = inputIndex, x = x }) end end end createDecals() for decalID, decal in pairs(decals) do lookupDecalIndices[decalID] = { inputs = {}, selectionButtons = {} } local pos = getDecalPosition(decalID) if (decal.locked ~= "True" and not sheetLocked and not locks.decals and not editingSheet) then local func = 'MarumEditableSetURL_' .. decalID _G[func] = function(obj, ply, alt) obj.call("showImageURLPanel", { decalID, ply }) end local tooltip = getDecalTooltip(decalID) self.createButton({ value = decal.url, tooltip = tooltip, click_function = func, function_owner = self, position = pos, width = 490, height = 490, rotation = { x = 0, y = 0, z = 0 }, font_size = 10, scale = { x = decal.scale.x * scale.x, y = decal.scale.y * scale.y, z = decal.scale.y * scale.y }, font_color = { r = 0, g = 0, b = 0, a = 0 }, color = { r = 0, g = 0, b = 0, a = 0 }, }) buttonIndex = buttonIndex + 1 lookupButtonIndexToInfo[buttonIndex] = { type = "decal", id = decalID } table.insert(lookupDecalIndices[decalID].inputs, { index = buttonIndex }) end if (inputIndex % 10 == 0) then coroutine.yield(0) end end for checkID, check in pairs(checks) do lookupCheckIndices[checkID] = { buttons = {}, selectionButtons = {} } local rotationZ = 0 local checkScale = { x = scale.x * check.size.x, y = 1, z = scale.y * check.size.y } local upright = self.getTransformUp().y > 0 local shouldFlip = (check.locked == "True" or sheetLocked or locks.checks) and upright if (editingSheet or shouldFlip) then checkScale.x = -checkScale.x rotationZ = 180 end for x = 1, check.array.x do for y = 1, check.array.y do local arrayID = x + (y - 1) * check.array.x local func = "MarumEditableClickCheckbox_" .. checkID .. "_" .. arrayID if (check.locked == "True" or sheetLocked or locks.checks) then func = "null" end local rotationY = 0 local posMul = 1 if (flip == "True") then rotationY = 180 posMul = -posMul end local pos = getCheckPosition(checkID, x, y) local bindex = buttonIndex _G['MarumEditableClickCheckbox_' .. checkID .. '_' .. arrayID] = function(obj, ply, alt) local fID = checkID local aID = arrayID local bi = bindex obj.call("clickcheck", { fID, aID, alt, bi, ply }) end local tooltip = getCheckTooltip(checkID) local label, color, alphaCorrectedColor = getCheckLabelAndColor(checkID, arrayID) buttonIndex = buttonIndex + 1 lookupButtonIndexToInfo[buttonIndex] = { type = "check", id = checkID, arrayID = arrayID } table.insert(lookupCheckIndices[checkID].buttons, { index = buttonIndex, x = x, y = y, arrayID = arrayID }) self.createButton({ label = label, tooltip = tooltip, click_function = func, function_owner = self, alignment = 3, position = pos, width = 250, height = 250, rotation = { x = 0, y = rotationY, z = rotationZ }, font_size = check.font, scale = { x = checkScale.x, y = checkScale.y, z = checkScale.z }, font_color = alphaCorrectedColor, color = check.checkColor, tab = 0 }) if (buttonIndex % 10 == 0) then coroutine.yield(0) end end end end if not wasCommitted and editingSheet then createSelectionButtons() end creating = false return 1 end function revertField(args) local fieldID = args[1] local ply = args[2] local selected = args[4] if (not selected) then Wait.frames( function() for k, v in pairs(lookupFieldIndices[fieldID].inputs) do self.editInput({ index = v.index - 1, value = fields[fieldID].value[v.arrayID] }) end end, 1 ) else if (lastFieldLockedMessage ~= fieldID) then broadcastToColor("This text is locked", ply, { r = 1, g = 0.5, b = 0 }) lastFieldLockedMessage = fieldID end end end function recalculateVSums(fieldID) local field = fields[fieldID] for k, v in pairs(lookupFieldIndices[fieldID].totals) do local vsum = 0 for y = 1, field.array.y do local arrayID = v.x + (y - 1) * field.array.x if (tonumber(field.value[arrayID])) then vsum = vsum + tonumber(field.value[arrayID]) end end self.editInput({ index = v.index - 1, value = "[u]" .. vsum .. "[/u]" }) end end function revertFieldSum(args) local fieldID = args[1] local ply = args[2] local selected = args[4] if (not selected) then Wait.frames(function() recalculateVSums(fieldID) end, 1) else if (lastFieldLockedMessage ~= fieldID) then broadcastToColor("This text is reserved for the total sum", ply, { r = 1, g = 1, b = 0 }) lastFieldLockedMessage = fieldID end end end function createDecals() local decalParameters = {} for decalID, decal in pairs(decals) do local rotationAdd = 180 if (flip == "True") then rotationAdd = 0 end local pos = getDecalPosition(decalID) table.insert(decalParameters, { url = decal.url, name = "Image #" .. decalID, position = { x = -pos.x, y = pos.y, z = pos.z }, rotation = { x = 90, y = rotationAdd + decal.rotation, z = 0 }, scale = { x = decal.scale.x * scale.x, y = decal.scale.y * scale.y, z = 1 } }) end self.setDecals(decalParameters) end function getFieldPosition(fieldID, x, y) local field = fields[fieldID] local mul = 1 if (flip == "True") then mul = -1 end return { x = (field.pos.x + (x - 1) * field.distance.x) * scale.x * mul, y = height + 0.002, z = (field.pos.y + (y - 1) * field.distance.y) * scale.y * mul } end function getFieldTooltip(fieldID, arrayID) local field = fields[fieldID] local tooltip = "" if (not field.locked) then if (field.tooltip == nil or field.tooltip:find("name")) then tooltip = field.name or "" elseif (field.tooltip:find("content")) then tooltip = field.value[arrayID] end end return tooltip end function getCheckPosition(checkID, x, y) local check = checks[checkID] local mul = 1 if (flip == "True") then mul = -1 end return { x = (check.pos.x + (x - 1) * check.distance.x) * mul * scale.x, y = height + 0.002, z = (check.pos.y + (y - 1) * check.distance.y) * mul * scale.y } end function getDecalPosition(decalID) local decal = decals[decalID] local mul = 1 if (flip == "True") then mul = -1 end return { x = decal.pos.x * mul * scale.x, y = height + 0.005, z = decal.pos.y * mul * scale.y } end function getDecalTooltip(decalID) local decal = decals[decalID] local tooltip = "" if (decal.tooltip == nil) then tooltip = decal.name or "" elseif (decal.tooltip:find("name")) then tooltip = decal.name or "" elseif (decal.tooltip:find("hint")) then tooltip = "Click to change image" end return tooltip end function increaseCounter(args) local fieldID = tonumber(args[1]) local arrayID = tonumber(args[2]) local ply = args[3] local field = fields[fieldID] if (field.value[arrayID] == nil or field.value[arrayID] == "") then field.value[arrayID] = 0 end if (tonumber(field.value[arrayID])) then field.value[arrayID] = tonumber(field.value[arrayID]) + 1 updateFieldNameContentAndTooltip(fieldID) if (field.vsum == "True") then recalculateVSums(fieldID) end updateSave() else broadcastToColor("Field does not contain a valid number", ply) end end function decreaseCounter(args) local fieldID = tonumber(args[1]) local arrayID = tonumber(args[2]) local ply = args[3] local field = fields[fieldID] if (field.value[arrayID] == nil or field.value[arrayID] == "") then field.value[arrayID] = 0 end if (tonumber(field.value[arrayID])) then field.value[arrayID] = tonumber(field.value[arrayID]) - 1 updateFieldNameContentAndTooltip(fieldID) if (field.vsum == "True") then recalculateVSums(fieldID) end updateSave() else broadcastToColor("Field does not contain a valid number", ply) end end function edit(obj, ply, value, selected) for k, v in pairs(obj.getInputs()) do if (lookupInputIndexToInfo[k] ~= nil) then if (lookupInputIndexToInfo[k].type == "field") then local field = fields[lookupInputIndexToInfo[k].id] if (v.value ~= field.value[lookupInputIndexToInfo[k].arrayID]) then field.value[lookupInputIndexToInfo[k].arrayID] = v.value if (field.tooltip ~= nil) then if (field.tooltip:find("content")) then self.editInput({ index = k - 1, tooltip = v.value }) end end if (field.role ~= nil) then if (field.role:find("name")) then self.setName(value) elseif (field.role:find("description")) then self.setDescription(value) end end updateSave() break end end end end end function editUrl(obj, ply, value, selected) for k, v in pairs(obj.getInputs()) do if (lookupInputIndexToInfo[k] ~= nil) then if (lookupInputIndexToInfo[k].type == "decal") then if (v.value ~= decals[lookupInputIndexToInfo[k].id].url) then decals[lookupInputIndexToInfo[k].id].url = v.value updateSave() createDecals() break end end end end end function clickcheck(args) local checkID = tonumber(args[1]) local arrayID = tonumber(args[2]) local alt_click = args[3] local buttonIndex = args[4] local ply = args[5] local check = checks[checkID] local value = tonumber(check.value[arrayID]) if (value == nil) then value = 1 end if (alt_click) then if (value > 0) then check.value[arrayID] = 0 else check.value[arrayID] = 1 end else if (value == 1) then check.value[arrayID] = 2 else if (value == 2) then check.value[arrayID] = 1 elseif (check.fillFromDisabled == "True") then check.value[arrayID] = 2 else broadcastToColor("This checkbox is disabled. You can enable/disable checkboxes with Right click.", ply) end end end local label, color, alphaCorrectedColor = getCheckLabelAndColor(checkID, arrayID) self.editButton({ index = buttonIndex, label = label, font_color = alphaCorrectedColor }) updateSave() end function getFieldTextColor(fieldID) local field = fields[fieldID] local textAlpha = field.textColor.a if (tonumber(field.fieldColor.a) > 0) then textAlpha = tonumber(field.textColor.a) / tonumber(field.fieldColor.a) else textAlpha = tonumber(field.textColor.a) * 100 end return { r = field.textColor.r, g = field.textColor.g, b = field.textColor.b, a = textAlpha } end function getCheckTooltip(checkID) local check = checks[checkID] local tooltip = "" if (not check.locked) then if (check.tooltip == nil or check.tooltip:find("name")) then tooltip = check.name elseif (check.tooltip:find("hint")) then tooltip = "Left click to toggle, Right click to enable/disable" end end return tooltip end function getCheckLabelAndColor(checkID, arrayID) local check = checks[checkID] local label = check.characters.empty if (check.value[arrayID] == 0) then label = check.characters.disabled elseif (check.value[arrayID] == 2) then label = check.characters.filled end local color = nil if (check.separateColors == "True") then if (check.value[arrayID] == 0) then color = check.textColorDisabled or check.textColor elseif (check.value[arrayID] == 1) then color = check.textColorOff or check.textColor elseif (check.value[arrayID] == 2) then color = check.textColorOn or check.textColor else color = check.textColorOff or check.textColor end else color = check.textColorOn or check.textColor end local alpha = 1 local checkAlpha = math.max(1 / 255, check.checkColor.a) if (tonumber(check.checkColor.a) > 0) then alpha = tonumber(color.a) / tonumber(checkAlpha) else alpha = tonumber(color.a) * 100 end return label, color, { r = color.r, g = color.g, b = color.b, a = color.a * alpha } end function split(inputstr, sep) if sep == nil then sep = "%s" end local t = {} for str in string.gmatch(inputstr, "([^" .. sep .. "]+)") do table.insert(t, str) end return t end function onRotate(spin, flip, player_color, old_spin, old_flip) updateLockedFieldOrientation(flip < 90) end function onDrop(ply) updateLockedFieldOrientation(self.getTransformUp().y >= 0) end function updateLockedFieldOrientation(upright) for k, v in pairs(fields) do if (v.locked == "True" or sheetLocked or locks.fields or v.vsum == "True") then local field = v local fieldScale = { x = scale.x, y = 1, z = scale.y } local rotation = { x = 0, y = 0, z = 0 } if (flip == "True") then rotation.y = 180 end if (upright) then rotation.z = 180 fieldScale.x = -scale.x end if (v.locked == "True" or sheetLocked or locks.fields) then for arrayID, inp in pairs(lookupFieldIndices[k].inputs) do self.editInput({ index = inp.index - 1, rotation = rotation, scale = fieldScale }) end end for arrayID, inp in pairs(lookupFieldIndices[k].totals) do self.editInput({ index = inp.index - 1, rotation = rotation, scale = fieldScale }) end end end for k, v in pairs(checks) do if (v.locked == "True" or sheetLocked or locks.fields) then local check = v local rotationZ = 0 local checkScale = { x = scale.x * check.size.x, y = 1, z = scale.y * check.size.y } if (upright) then checkScale.x = -checkScale.x rotationZ = 180 end local rotationY = 0 if (flip == "True") then rotationY = 180 end for arrayID, inp in pairs(lookupCheckIndices[k].buttons) do self.editButton({ index = inp.index - 1, rotation = { x = 0, y = rotationY, z = rotationZ }, scale = checkScale }) end end end end function showImageURLPanel(args) local decalID = args[1] local ply = args[2] Player[ply].showInputDialog("Set image URL", function(text, player_color) decals[decalID].url = text createDecals() end ) end function updateFieldNameContentAndTooltip(fieldID) local field = fields[fieldID] for k, v in pairs(lookupFieldIndices[fieldID].inputs) do self.editInput({ index = v.index - 1, value = field.value[v.arrayID], tooltip = getFieldTooltip(fieldID, v.arrayID) }) end local name = "T" .. fieldID local tooltip = "Select " .. (field.name or name) for k, v in pairs(lookupFieldIndices[fieldID].selectionButtons) do self.editButton({ index = v.index - 1, tooltip = tooltip }) end end --$ function get_line_count(str) local lines = 1 for i = 1, #str do local c = str:sub(i, i) if c == 'n' then lines = lines + 1 end end return lines end function createSelectionButtons() coroutine.yield(0) local rotation = { x = 0, y = 0, z = 0 } if (flip == "True") then rotation.y = 180 end for fieldID, field in pairs(fields) do local posx = field.pos.x local posy = field.pos.y local fieldScale = { x = scale.x, y = 1, z = scale.y } local posMul = 1 if (flip == "True") then posMul = -1 end local fontSize = field.font fontSize = math.min(field.size.y - 23, fontSize) for x = 1, field.array.x do for y = 1, field.array.y do local name = "T" .. fieldID local arrayID = x + (y - 1) * field.array.x local tooltip = "Select " .. (field.name or name) local pos = { x = (posx + (x - 1) * field.distance.x) * fieldScale.x * posMul, y = height + 0.005, z = (posy + (y - 1) * field.distance.y) * fieldScale.z * posMul } buttonIndex = buttonIndex + 1 table.insert(lookupFieldIndices[fieldID].selectionButtons, { index = buttonIndex, x = x, y = y }) self.createButton({ click_function = 'MarumEditableSheet_SelectField/' .. fieldID .. '_' .. arrayID, tooltip = tooltip, function_owner = self, label = "", position = pos, rotation = rotation, scale = { x = fieldScale.x * 0.25, y = 1, z = fieldScale.z * 0.25 }, width = field.size.x * 4 + 200, height = field.size.y * 4 + 200, font_size = field.font, font_color = { r = 1, g = 1, b = 1, a = 1 }, color = { r = 0, g = 0.5, b = 1, a = 0.5 }, hover_color = { r = 0, g = 0.5, b = 1, a = 0.8 } }) _G['MarumEditableSheet_SelectField/' .. fieldID .. '_' .. arrayID] = function(obj, ply, alt) obj.call("selectField", { id = fieldID, arrayId = arrayID }) end end end if (buttonIndex % 10 == 0) then coroutine.yield(0) end end for decalID, decal in pairs(decals) do local name = "D" .. decalID local tooltip = "Select " .. (decal.name or name) local pos = getDecalPosition(decalID) buttonIndex = buttonIndex + 1 table.insert(lookupDecalIndices[decalID].selectionButtons, { index = buttonIndex, x = x, y = y }) self.createButton({ click_function = 'MarumEditableSheet_SelectDecal_' .. decalID, tooltip = tooltip, function_owner = self, label = "", position = pos, rotation = rotation, scale = { x = decal.scale.x * scale.x * 0.25, y = decal.scale.y * scale.y * 0.25, z = decal.scale.y * scale.y * 0.25 }, width = 2000, height = 2000, font_size = 100, font_color = { r = 1, g = 1, b = 1, a = 1 }, color = { r = 0, g = 0.5, b = 1, a = 0.5 }, hover_color = { r = 0, g = 0.5, b = 1, a = 0.8 } }) _G['MarumEditableSheet_SelectDecal_' .. decalID] = function(obj, ply, alt) obj.call("selectDecal", decalID) end if (buttonIndex % 10 == 0) then coroutine.yield(0) end end for checkID, check in pairs(checks) do local posx = check.pos.x local posy = check.pos.y local checkScale = { x = scale.x * check.size.x, y = 1, z = scale.y * check.size.y } local name = "C" .. checkID local tooltip = "Select " .. (check.name or name) for x = 1, check.array.x do for y = 1, check.array.y do local arrayID = x + (y - 1) * check.array.x local func = "MarumEditableClickCheckbox_" .. checkID .. '_' .. arrayID local rotationAdd = 0 local posMul = 1 if (flip == "True") then rotationAdd = 180 posMul = -1 end local pos = { x = (posx + (x - 1) * check.distance.x) * posMul * scale.x, y = height + 0.002, z = (posy + (y - 1) * check.distance.y) * posMul * scale.y } buttonIndex = buttonIndex + 1 table.insert(lookupCheckIndices[checkID].selectionButtons, { index = buttonIndex, x = x, y = y, arrayID = arrayID }) self.createButton({ label = "", tooltip = tooltip, click_function = func, function_owner = self, alignment = 3, position = pos, width = 1000, height = 1000, rotation = { x = 0, y = rotationAdd, z = 0 }, font_size = check.font / 3, scale = { x = checkScale.x * 0.25, y = checkScale.y, z = checkScale.z * 0.25 }, font_color = { r = 1, g = 1, b = 1, a = 1 }, color = { r = 0, g = 0.5, b = 1, a = 0.5 }, hover_color = { r = 0, g = 0.5, b = 1, a = 0.8 }, tab = 0 }) _G['MarumEditableClickCheckbox_' .. checkID .. '_' .. arrayID] = function(obj, ply, alt) obj.call("selectCheck", { id = checkID, arrayId = arrayID }) end end end if (buttonIndex % 10 == 0) then coroutine.yield(0) end end UI.setAttribute(getPanelId("Loading"), "active", "False") end function createSelectionHighlight(edit) local topLeft = { x = 0, y = 0, z = 0 } local topRight = { x = 0, y = 0, z = 0 } local bottomLeft = { x = 0, y = 0, z = 0 } local bottomRight = { x = 0, y = 0, z = 0 } local scale = { x = scale.x, y = 1, z = scale.y } local rotation = { x = 0, y = 0, z = 0 } if (flip == "True") then rotation.y = 180 end if (selectedType == "field") then local field = fields[selectedId] local posx = field.pos.x local posy = field.pos.y local posMul = 1 if (flip == "True") then posMul = -1 posy = posy - 0.1 end local x = (selectedArrayId - 1) % field.array.x local y = math.floor((selectedArrayId - 1) / field.array.x) local gridpos = { x = x * field.distance.x, y = y * field.distance.y } bottomLeft = { x = (posx - field.size.x / 1000 - 0.1 + gridpos.x) * scale.x * posMul, y = height + 0.005, z = (posy + field.size.y / 1000 + 0.1 + gridpos.y) * scale.z * posMul } bottomRight = { x = (posx + field.size.x / 1000 + 0.1 + gridpos.x) * scale.x * posMul, y = height + 0.005, z = (posy + field.size.y / 1000 + 0.1 + gridpos.y) * scale.z * posMul } topLeft = { x = (posx - field.size.x / 1000 - 0.1 + gridpos.x) * scale.x * posMul, y = height + 0.005, z = (posy - field.size.y / 1000 + gridpos.y) * scale.z * posMul } topRight = { x = (posx + field.size.x / 1000 + 0.1 + gridpos.x) * scale.x * posMul, y = height + 0.005, z = (posy - field.size.y / 1000 + gridpos.y) * scale.z * posMul } elseif (selectedType == "decal") then local decal = decals[selectedId] local posx = decal.pos.x local posy = decal.pos.y local posMul = 1 if (flip == "True") then posMul = -1 posy = posy - 0.1 end bottomLeft = { x = (posx - decal.scale.x / 2 - 0.1) * scale.x * posMul, y = height + 0.005, z = (posy + decal.scale.y / 2 + 0.1) * scale.z * posMul } bottomRight = { x = (posx + decal.scale.x / 2 + 0.1) * scale.x * posMul, y = height + 0.005, z = (posy + decal.scale.y / 2 + 0.1) * scale.z * posMul } topLeft = { x = (posx - decal.scale.x / 2 - 0.1) * scale.x * posMul, y = height + 0.005, z = (posy - decal.scale.y / 2) * scale.z * posMul } topRight = { x = (posx + decal.scale.x / 2 + 0.1) * scale.x * posMul, y = height + 0.005, z = (posy - decal.scale.y / 2) * scale.z * posMul } elseif (selectedType == "check") then local check = checks[selectedId] local posx = check.pos.x local posy = check.pos.y local posMul = 1 if (flip == "True") then posMul = -1 posy = posy - 0.1 end local x = (selectedArrayId - 1) % check.array.x local y = math.floor((selectedArrayId - 1) / check.array.x) local gridpos = { x = x * check.distance.x, y = y * check.distance.y } bottomLeft = { x = (posx - check.size.x * 0.25 - 0.1 + gridpos.x) * scale.x * posMul, y = height + 0.005, z = (posy + check.size.y * 0.25 + 0.1 + gridpos.y) * scale.z * posMul } bottomRight = { x = (posx + check.size.x * 0.25 + 0.1 + gridpos.x) * scale.x * posMul, y = height + 0.005, z = (posy + check.size.y * 0.25 + 0.1 + gridpos.y) * scale.z * posMul } topLeft = { x = (posx - check.size.x * 0.25 - 0.1 + gridpos.x) * scale.x * posMul, y = height + 0.005, z = (posy - check.size.y * 0.25 + gridpos.y) * scale.z * posMul } topRight = { x = (posx + check.size.x * 0.25 + 0.1 + gridpos.x) * scale.x * posMul, y = height + 0.005, z = (posy - check.size.y * 0.25 + gridpos.y) * scale.z * posMul } end if (edit) then self.editButton({ index = 0, scale = scale, position = bottomLeft }) self.editButton({ index = 1, scale = scale, position = topLeft }) self.editButton({ index = 2, scale = scale, position = bottomRight }) self.editButton({ index = 3, scale = scale, position = topRight }) else local label = "┗" if (flip == "True") then label = "┓" end self.createButton({ click_function = 'null', function_owner = self, label = label, position = bottomLeft, rotation = rotation, scale = scale, width = 0, height = 0, font_size = 200, font_color = { r = 0, g = 0.5, b = 1, a = 100 }, color = { r = 0, g = 0, b = 0, a = 0.01 }, }) label = "┏" if (flip == "True") then label = "┛" end self.createButton({ click_function = 'null', function_owner = self, label = label, position = topLeft, rotation = rotation, scale = scale, width = 0, height = 0, font_size = 200, font_color = { r = 0, g = 0.5, b = 1, a = 100 }, color = { r = 0, g = 0, b = 0, a = 0.01 }, }) label = "┛" if (flip == "True") then label = "┏" end self.createButton({ click_function = 'null', function_owner = self, label = label, position = bottomRight, rotation = rotation, scale = scale, width = 0, height = 0, font_size = 200, font_color = { r = 0, g = 0.5, b = 1, a = 100 }, color = { r = 0, g = 0, b = 0, a = 0.01 }, }) label = "┓" if (flip == "True") then label = "┗" end self.createButton({ click_function = 'null', function_owner = self, label = label, position = topRight, rotation = rotation, scale = scale, width = 0, height = 0, font_size = 200, font_color = { r = 0, g = 0.5, b = 1, a = 100 }, color = { r = 0, g = 0, b = 0, a = 0.01 }, }) buttonIndex = buttonIndex + 4 end end function selectField(args) selectedId = args.id selectedArrayId = args.arrayId selectedType = "field" createSelectionHighlight(true) UI.hide(attrId("EmptyPrompt")) UI.show(attrId("SelectionPanel")) refreshEditPanel() end function selectDecal(id) selectedId = id selectedType = "decal" createSelectionHighlight(true) UI.hide(attrId("EmptyPrompt")) UI.show(attrId("SelectionPanel")) refreshEditPanel() end function selectCheck(args) selectedId = args.id selectedArrayId = args.arrayId selectedType = "check" createSelectionHighlight(true) UI.hide(attrId("EmptyPrompt")) UI.show(attrId("SelectionPanel")) refreshEditPanel() end function refreshEditPanel() local page = selectedId local subpages = 0 if (selectedType == "field") then local field = fields[selectedId] local name = "" if (field.name ~= "" and field.name ~= nil) then name = " - " .. field.name end local value = field.value[selectedArrayId] subpages = field.array.x * field.array.y local sub = "" if (subpages > 1) then sub = ";" .. selectedArrayId end UI.setAttribute(attrId("Field/ID"), "text", "Text #" .. tostring(selectedId) .. sub .. name .. "") UI.setAttribute(attrId("Field/name"), "text", field.name) UI.setAttribute(attrId("Field/content"), "text", value or "") UI.setAttribute(attrId("Field/font"), "text", field.font) UI.setAttribute(attrId("Field/pos/x"), "text", field.pos.x) UI.setAttribute(attrId("Field/pos/y"), "text", field.pos.y) UI.setAttribute(attrId("Field/size/x"), "text", field.size.x) UI.setAttribute(attrId("Field/size/y"), "text", field.size.y) UI.setAttribute(attrId("Field/array/x"), "text", field.array.x) UI.setAttribute(attrId("Field/array/y"), "text", field.array.y) UI.setAttribute(attrId("Field/distance/x"), "text", field.distance.x) UI.setAttribute(attrId("Field/distance/y"), "text", field.distance.y) local textColor = "rgba(" .. field.textColor.r .. "," .. field.textColor.g .. "," .. field.textColor.b .. ",1)" local textColor2 = "rgba(" .. (field.textColor.r * 0.5 + 0.2) .. "," .. (field.textColor.g * 0.5 + 0.2) .. "," .. (field.textColor.b * 0.5 + 0.2) .. ",1)" UI.setAttribute(attrId("Field/textColor"), "colors", textColor .. "|" .. textColor2 .. "|" .. textColor2 .. "|" .. textColor) UI.setAttribute(attrId("Field/textColor/a"), "percentage", field.textColor.a * 100) local fieldColor = "rgba(" .. field.fieldColor.r .. "," .. field.fieldColor.g .. "," .. field.fieldColor.b .. ",1)" local fieldColor2 = "rgba(" .. (field.fieldColor.r * 0.5 + 0.2) .. "," .. (field.fieldColor.g * 0.5 + 0.2) .. "," .. (field.fieldColor.b * 0.5 + 0.2) .. ",1)" UI.setAttribute(attrId("Field/fieldColor"), "colors", fieldColor .. "|" .. fieldColor2 .. "|" .. fieldColor2 .. "|" .. fieldColor) UI.setAttribute(attrId("Field/fieldColor/a"), "percentage", field.fieldColor.a * 100) UI.setAttribute(attrId("Field/counter"), "isOn", field.counter == "True") UI.setAttribute(attrId("Field/vsum"), "isOn", field.vsum == "True") UI.setAttribute(attrId("Field/locked"), "isOn", field.locked == "True") local role = 0 if (field.role ~= nil) then if (field.role:find("name")) then role = 1 elseif (field.role:find("desc")) then role = 2 end end UI.setAttribute(attrId("Field/role"), "value", role) local tooltip = 0 if (field.tooltip ~= nil) then if (field.tooltip:find("name")) then tooltip = 1 elseif (field.tooltip:find("content")) then tooltip = 2 end end UI.setAttribute(attrId("Field/tooltip"), "value", tooltip) local align = 0 if (field.align == 3) then align = 1 elseif (field.align == 4) then align = 2 end UI.setAttribute(attrId("Field/align"), "value", align) UI.hide(attrId("CheckPanel")) UI.hide(attrId("DecalPanel")) UI.show(attrId("FieldPanel")) elseif (selectedType == "check") then local check = checks[selectedId] local name = "" if (check.name ~= "" and check.name ~= "") then name = " - " .. check.name end subpages = check.array.x * check.array.y local sub = "" if (subpages > 1) then sub = ";" .. selectedArrayId end UI.setAttribute(attrId("Check/ID"), "text", "Checkbox #" .. tostring(selectedId) .. sub .. name .. "") UI.setAttribute(attrId("Check/name"), "text", check.name) UI.setAttribute(attrId("Check/font"), "text", check.font) UI.setAttribute(attrId("Check/pos/x"), "text", check.pos.x) UI.setAttribute(attrId("Check/pos/y"), "text", check.pos.y) UI.setAttribute(attrId("Check/size/x"), "text", check.size.x) UI.setAttribute(attrId("Check/size/y"), "text", check.size.y) UI.setAttribute(attrId("Check/array/x"), "text", check.array.x) UI.setAttribute(attrId("Check/array/y"), "text", check.array.y) UI.setAttribute(attrId("Check/distance/x"), "text", check.distance.x) UI.setAttribute(attrId("Check/distance/y"), "text", check.distance.y) local tc = check.textColorOn or check.textColor local textColor = "rgba(" .. tc.r .. "," .. tc.g .. "," .. tc.b .. ",1)" local textColor2 = "rgba(" .. (tc.r * 0.5 + 0.2) .. "," .. (tc.g * 0.5 + 0.2) .. "," .. (tc.b * 0.5 + 0.2) .. ",1)" UI.setAttribute(attrId("Check/textColorOn"), "colors", textColor .. "|" .. textColor2 .. "|" .. textColor2 .. "|" .. textColor) UI.setAttribute(attrId("Check/textColorOn/a"), "percentage", tc.a * 100) local tc = check.textColorOff or check.textColor local textColor = "rgba(" .. tc.r .. "," .. tc.g .. "," .. tc.b .. ",1)" local textColor2 = "rgba(" .. (tc.r * 0.5 + 0.2) .. "," .. (tc.g * 0.5 + 0.2) .. "," .. (tc.b * 0.5 + 0.2) .. ",1)" UI.setAttribute(attrId("Check/textColorOff"), "colors", textColor .. "|" .. textColor2 .. "|" .. textColor2 .. "|" .. textColor) UI.setAttribute(attrId("Check/textColorOff/a"), "percentage", tc.a * 100) local tc = check.textColorDisabled or check.textColor local textColor = "rgba(" .. tc.r .. "," .. tc.g .. "," .. tc.b .. ",1)" local textColor2 = "rgba(" .. (tc.r * 0.5 + 0.2) .. "," .. (tc.g * 0.5 + 0.2) .. "," .. (tc.b * 0.5 + 0.2) .. ",1)" UI.setAttribute(attrId("Check/textColorDisabled"), "colors", textColor .. "|" .. textColor2 .. "|" .. textColor2 .. "|" .. textColor) UI.setAttribute(attrId("Check/textColorDisabled/a"), "percentage", tc.a * 100) local checkColor = "rgba(" .. check.checkColor.r .. "," .. check.checkColor.g .. "," .. check.checkColor.b .. ",1)" local checkColor2 = "rgba(" .. (check.checkColor.r * 0.5 + 0.2) .. "," .. (check.checkColor.g * 0.5 + 0.2) .. "," .. (check.checkColor.b * 0.5 + 0.2) .. ",1)" UI.setAttribute(attrId("Check/checkColor"), "colors", checkColor .. "|" .. checkColor2 .. "|" .. checkColor2 .. "|" .. checkColor) UI.setAttribute(attrId("Check/checkColor/a"), "percentage", check.checkColor.a * 100) UI.setAttribute(attrId("Check/characters/empty"), "text", check.characters.empty) UI.setAttribute(attrId("Check/characters/filled"), "text", check.characters.filled) UI.setAttribute(attrId("Check/characters/disabled"), "text", check.characters.disabled) if (check.separateColors == "True") then UI.setAttribute(attrId("CheckOnColorLabel"), "text", "On color") UI.setAttribute(attrId("CheckOffColorRow"), "active", "True") UI.setAttribute(attrId("CheckDisabledColorRow"), "active", "True") UI.setAttribute(attrId("CheckSeparateColorsSpacer1"), "active", "False") UI.setAttribute(attrId("CheckSeparateColorsSpacer2"), "active", "False") else UI.setAttribute(attrId("CheckOnColorLabel"), "text", "Color") UI.setAttribute(attrId("CheckOffColorRow"), "active", "False") UI.setAttribute(attrId("CheckDisabledColorRow"), "active", "False") UI.setAttribute(attrId("CheckSeparateColorsSpacer1"), "active", "True") UI.setAttribute(attrId("CheckSeparateColorsSpacer2"), "active", "True") end UI.setAttribute(attrId("Check/separateColors"), "isOn", check.separateColors == "True") UI.setAttribute(attrId("Check/fillFromDisabled"), "isOn", check.fillFromDisabled == "True") local option = 0 if (check.value[selectedArrayId] == 0) then option = 2 end if (check.value[selectedArrayId] == 1) then option = 0 end if (check.value[selectedArrayId] == 2) then option = 1 end UI.setAttribute(attrId("Check/value"), "value", option) local tooltip = 0 if (check.tooltip ~= nil) then if (check.tooltip:find("name")) then tooltip = 1 elseif (check.tooltip:find("hint")) then tooltip = 2 end end UI.setAttribute(attrId("Check/tooltip"), "value", tooltip) UI.setAttribute(attrId("Check/locked"), "isOn", check.locked == "True") UI.hide(attrId("DecalPanel")) UI.hide(attrId("FieldPanel")) UI.show(attrId("CheckPanel")) page = selectedId + #fields elseif (selectedType == "decal") then local decal = decals[selectedId] local slot = 1 UI.setAttribute(attrId("Decal/ID"), "text", "Decal #" .. tostring(selectedId) .. "") UI.setAttribute(attrId("Decal/name"), "text", decal.name) UI.setAttribute(attrId("Decal/url"), "text", decal.url) UI.setAttribute(attrId("Decal/pos/x"), "text", decal.pos.x) UI.setAttribute(attrId("Decal/pos/y"), "text", decal.pos.y) UI.setAttribute(attrId("Decal/scale/x"), "text", decal.scale.x) UI.setAttribute(attrId("Decal/scale/y"), "text", decal.scale.y) UI.setAttribute(attrId("Decal/rotation"), "text", decal.rotation) UI.setAttribute(attrId("Decal/locked"), "isOn", decal.locked == "True") local tooltip = 0 if (decal.tooltip ~= nil) then if (decal.tooltip:find("name")) then tooltip = 1 elseif (decal.tooltip:find("hint")) then tooltip = 2 end end UI.setAttribute(attrId("Decal/tooltip"), "value", tooltip) UI.hide(attrId("CheckPanel")) UI.hide(attrId("FieldPanel")) UI.show(attrId("DecalPanel")) page = selectedId + #fields + #checks else UI.show(attrId("EmptyPrompt")) UI.hide(attrId("SelectionPanel")) end -- Pages local pageCount = getPageCount() UI.setAttribute(attrId("LastPage"), "interactable", tostring(page < pageCount)) UI.setAttribute(attrId("NextPage"), "interactable", tostring(page < pageCount)) UI.setAttribute(attrId("PreviousPage"), "interactable", tostring(page > 1)) UI.setAttribute(attrId("FirstPage"), "interactable", tostring(page > 1)) if (subpages > 1) then UI.setAttribute(attrId("Pages"), "text", page .. " (" .. selectedArrayId .. "/" .. subpages .. ") / " .. pageCount) else UI.setAttribute(attrId("Pages"), "text", page .. " / " .. pageCount) end end function commit(ply) local codeSplit = split(self.script_code, "$") local uncutLines = get_line_count(codeSplit[1]) local cutLines = get_line_count(codeSplit[2] .. codeSplit[3]) broadcastToColor("Layout commited. Code reduced from " .. (uncutLines + cutLines + 1) .. " lines to " .. uncutLines, ply) if (Global.getVar("MarumEditableSheetGUID", self.guid)) then Global.setVar("MarumEditableSheetGUID", nil) end wasCommitted = true updateSave() self.script_code = codeSplit[1] self.reload() end function onToggleSheetLocked(obj, ply, value, selected) sheetLocked = not sheetLocked updateSave() end function closePanel(ply, value, id) if (editingSheet) then editingSheet = false refresh() end UI.setAttribute(getPanelId("main"), "active", "False") end function forceClosePanel() if (editingSheet) then editingSheet = false refresh() end end function getPageCount() return #fields + #checks + #decals end function getPanelId(panel) return "MarumEditableSheet/" .. panel end function showEditPanel(ply, value, id) createPanelIfNecessary(ply) waitForUiLoaded(function() UI.setAttribute(getPanelId("main"), "active", "True") UI.setAttribute(getPanelId("main"), "visibility", ply) if (selectedId == 0) then UI.show(attrId("EmptyPrompt")) UI.hide(attrId("SelectionPanel")) else UI.hide(attrId("EmptyPrompt")) UI.show(attrId("SelectionPanel")) end editingSheet = true local forcedSelectFirst = false if (selectedId == 0) then if (#fields > 0) then selectedId = 1 selectedArrayId = 1 selectedType = "field" forcedSelectFirst = true elseif (#checks > 0) then selectedId = 1 selectedArrayId = 1 selectedType = "check" forcedSelectFirst = true elseif (#decals > 0) then selectedId = 1 selectedType = "decal" forcedSelectFirst = true end end refresh() if (forcedSelectFirst) then UI.hide(attrId("EmptyPrompt")) UI.show(attrId("SelectionPanel")) end refreshEditPanel() end) end function showCommitPanel(ply, value, id) local previousGUID = Global.getVar("MarumEditableSheetGUID") if (previousGUID) then local obj = getObjectFromGUID(previousGUID) if (obj ~= nil) then getObjectFromGUID(previousGUID).call("closePanel") end end Player[ply].showConfirmDialog("Do you want to lock the current layout and remove editing related code? " .. "You will still be able to change the contents of unlocked fields.", commit) end function removeMESPanel() local tbl = UI.getXmlTable() local removing = false local newTable = {} for k, v in pairs(tbl) do local isTag = false if (v.attributes ~= nil) then if (v.attributes.id == "MESStart") then removing = true isTag = true elseif (v.attributes.id == "MESEnd") then removing = false isTag = true end end if (not isTag and not removing) then table.insert(newTable, v) end end UI.setXmlTable({ newTable }) end function createPanelIfNecessary(color) local guid = self.getGUID() local previousGUID = Global.getVar("MarumEditableSheetGUID") if (guid ~= previousGUID) then if (previousGUID ~= nil) then local obj = getObjectFromGUID(previousGUID) if (obj) then getObjectFromGUID(previousGUID).call("forceClosePanel") end removeMESPanel() end waitForUiLoaded(createMainPanel) end end function createMainPanel() local guid = self.getGUID() Global.setVar("MarumEditableSheetGUID", guid) local xml = [[ Editable Sheet - by Marum Document settings: Height ]] .. height .. [[ Scale ]] .. scale.x .. [[ ]] .. scale.y .. [[ Lock everything Flip ]] .. selectedId .. " / " .. getPageCount() .. [[ Add a new element using the buttons above. You can then select elements by clicking on them on the sheet, or with the navigation buttons above. ]] .. getFieldPanel() .. [[ ]] .. getCheckPanel() .. [[ ]] .. getDecalPanel() .. [[ Loading... ]] UI.setXml(UI.getXml() .. xml) end function onScaleReset() scale = { x = math.floor(100 / self.getScale().x) / 100, y = math.floor(100 / self.getScale().z) / 100 } UI.setAttribute(attrId("ScaleX"), "text", scale.x) UI.setAttribute(attrId("ScaleY"), "text", scale.y) refreshAllPositionsAndSize() refreshEditPanel() end function firstPage() if (#fields > 0) then selectField({ id = 1, arrayId = 1 }) elseif (#checks > 0) then selectCheck({ id = 1, arrayId = 1 }) elseif (#decals > 0) then selectDecal(1) end end function previousPage() if (selectedType == "decal") then if (selectedId > 1) then selectedId = selectedId - 1 selectDecal(selectedId) else if (#checks > 0) then selectedType = "check" selectedId = #checks selectCheck({ id = selectedId, arrayId = checks[selectedId].array.x * checks[selectedId].array.y }) elseif (#fields > 0) then selectedType = "field" selectedId = #fields selectField({ id = selectedId, arrayId = fields[selectedId].array.x * fields[selectedId].array.y }) end end elseif (selectedType == "check") then if (selectedArrayId > 1) then selectedArrayId = selectedArrayId - 1 selectCheck({ id = selectedId, arrayId = selectedArrayId }) elseif (selectedId > 1) then selectedId = selectedId - 1 selectCheck({ id = selectedId, arrayId = checks[selectedId].array.x * checks[selectedId].array.y }) else if (#fields > 0) then selectedType = "field" selectedId = #fields selectField({ id = selectedId, arrayId = fields[selectedId].array.x * fields[selectedId].array.y }) end end elseif (selectedType == "field") then if (selectedArrayId > 1) then selectedArrayId = selectedArrayId - 1 selectField({ id = selectedId, arrayId = selectedArrayId }) elseif (selectedId > 1) then selectedId = selectedId - 1 selectField({ id = selectedId, arrayId = fields[selectedId].array.x * fields[selectedId].array.y }) end end end function nextPage() if (selectedType == "field") then if (fields[selectedId].array.x * fields[selectedId].array.y > selectedArrayId) then selectedArrayId = selectedArrayId + 1 selectField({ id = selectedId, arrayId = selectedArrayId }) elseif (#fields > selectedId) then selectedId = selectedId + 1 selectField({ id = selectedId, arrayId = 1 }) else if (#checks > 0) then selectedType = "check" selectedId = 1 selectCheck({ id = selectedId, arrayId = 1 }) elseif (#decals > 0) then selectedType = "decal" selectedId = 1 selectDecal(1) end end elseif (selectedType == "check") then if (checks[selectedId].array.x * checks[selectedId].array.y > selectedArrayId) then selectedArrayId = selectedArrayId + 1 selectCheck({ id = selectedId, arrayId = selectedArrayId }) elseif (#checks > selectedId) then selectedId = selectedId + 1 selectCheck({ id = selectedId, arrayId = 1 }) else if (#decals > 0) then selectedType = "decal" selectedId = 1 selectDecal(1) end end elseif (selectedType == "decal") then if (#decals > selectedId) then selectedId = selectedId + 1 selectDecal(selectedId) end end end function lastPage() if (#decals > 0) then selectDecal(#decals) elseif (#checks > 0) then local last = checks[#checks] selectCheck({ id = #checks, arrayId = last.array.x * last.array.y }) elseif (#fields > 0) then local last = fields[#fields] selectField({ id = #fields, arrayId = last.array.x * last.array.y }) end end function onNudgeChanged(ply, value, id) nudgeDistance = value UI.setAttribute(attrId("Field_nudge"), "text", nudgeDistance) UI.setAttribute(attrId("Check_nudge"), "text", nudgeDistance) UI.setAttribute(attrId("Decal_nudge"), "text", nudgeDistance) updateSave() end function compareString(str, list) for k, v in pairs(list) do if (str == v) then return true end end end function onValueEdited(ply, value, id) local spl = split(id, "/") local parameter = spl[2] local tbl = nil if (selectedType == "field") then tbl = fields end if (selectedType == "check") then tbl = checks end if (selectedType == "decal") then tbl = decals end if (parameter == "content") then tbl[selectedId].value[selectedArrayId] = value else if (#spl > 2) then tbl[selectedId][parameter][spl[3]] = value else tbl[selectedId][parameter] = value end end updateSave() if (selectedType == "field") then if (compareString(parameter, { "pos", "distance", "size" })) then updateFieldPositionAndSize(selectedId) elseif (compareString(parameter, { "name", "content", "tooltip" })) then updateFieldNameContentAndTooltip(selectedId) elseif (parameter == "font") then updateFieldFontAndColor(selectedId) else refresh() end elseif (selectedType == "check") then if (compareString(parameter, { "pos", "distance", "size" })) then updateCheckPositionAndSize(selectedId) elseif (parameter == "name") then updateCheckNameContentAndTooltip(selectedId) elseif (parameter == "font") then updateCheckFontAndColor(selectedId) else refresh() end elseif (selectedType == "decal") then if (compareString(parameter, { "pos", "scale", "rotation" })) then updateDecalPositionAndSize(selectedId) elseif (parameter == "url") then createDecals() else refresh() end end end function updateFieldPositionAndSize(fieldID) local field = fields[fieldID] local lookup = lookupFieldIndices[fieldID] local fieldScale = { x = -scale.x, y = 1, z = scale.y } local flipped = 1 local rotation = { x = 0, y = 0, z = 180 } if (flip == "True") then rotation.y = 180 flipped = -1 end local fontSize = math.min(field.size.y - 24, field.font) for k, v in pairs(lookup.inputs) do local pos = getFieldPosition(fieldID, v.x, v.y) self.editInput({ index = v.index - 1, position = pos, scale = fieldScale, width = field.size.x, height = field.size.y, font_size = fontSize, rotation = rotation }) end for k, v in pairs(lookup.totals) do local pos = getFieldPosition(fieldID, v.x, field.array.y + 1) self.editInput({ index = v.index - 1, position = pos, scale = fieldScale, width = field.size.x, height = field.size.y, rotation = rotation, font_size = fontSize }) end for k, v in pairs(lookup.counterButtons) do local pos = getFieldPosition(fieldID, v.x, v.y) local offset = (field.size.x + fontSize * 0.75) / 1000 * scale.x self.editButton({ index = v.index - 1, position = { x = pos.x + offset * v.side * flipped, y = pos.y, z = pos.z }, scale = fieldScale, rotation = rotation }) end for k, v in pairs(lookup.selectionButtons) do local pos = getFieldPosition(fieldID, v.x, v.y) self.editButton({ index = v.index - 1, position = pos, scale = { x = scale.x * 0.25, y = 1, z = scale.y * 0.25 }, width = field.size.x * 4 + 200, height = field.size.y * 4 + 200 }) end createSelectionHighlight(true) end function updateFieldFontAndColor(fieldID) local field = fields[fieldID] local fontColor = getFieldTextColor(fieldID) local fontSize = math.min(field.size.y - 24, field.font) local lookup = lookupFieldIndices[fieldID] for k, v in pairs(lookup.inputs) do -- Do not question the ways of the tabletop, as if it requests for font_color to be assigned twice, then it shall be so self.editInput({ index = v.index - 1, font_color = fontColor }) self.editInput({ index = v.index - 1, font_size = fontSize, color = field.fieldColor, font_color = fontColor }) self.editInput({ index = v.index - 1 }) end for k, v in pairs(lookup.counterButtons) do self.editButton({ index = v.index - 1, color = field.fieldColor, font_color = fontColor, font_size = fontSize / 2 }) end for k, v in pairs(lookup.totals) do self.editInput({ index = v.index - 1, font_size = fontSize, color = field.fieldColor, font_color = fontColor }) end end function updateCheckNameContentAndTooltip(checkID) local check = checks[checkID] for k, v in pairs(lookupCheckIndices[checkID].buttons) do local label = getCheckLabelAndColor(checkID, v.arrayID) self.editButton({ index = v.index - 1, label = label, tooltip = getCheckTooltip(checkID) }) end local name = "C" .. checkID local tooltip = "Select " .. (check.name or name) for k, v in pairs(lookupCheckIndices[checkID].selectionButtons) do self.editButton({ index = v.index - 1, tooltip = tooltip }) end end function updateCheckPositionAndSize(checkID) local check = checks[checkID] local lookup = lookupCheckIndices[checkID] local rotation = { x = 0, y = 0, z = 0 } if (flip == "True") then rotation.y = 180 end for k, v in pairs(lookup.buttons) do local pos = getCheckPosition(checkID, v.x, v.y) local checkScale = { x = scale.x * check.size.x, y = 1, z = scale.y * check.size.y } self.editButton({ index = v.index - 1, position = pos, scale = checkScale, rotation = rotation }) end for k, v in pairs(lookup.selectionButtons) do local pos = getCheckPosition(checkID, v.x, v.y) local checkScale = { x = scale.x * check.size.x * 0.25, y = 1, z = scale.y * check.size.y * 0.25 } self.editButton({ index = v.index - 1, position = pos, scale = checkScale }) end createSelectionHighlight(true) end function updateCheckFontAndColor(checkID) local check = checks[checkID] for k, v in pairs(lookupCheckIndices[checkID].buttons) do local label, color, alphaCorrectedColor = getCheckLabelAndColor(checkID, v.arrayID) -- Do not question the ways of the tabletop, as if it requests for font_color to be assigned twice, then it shall be so self.editButton({ index = v.index - 1, font_size = check.font, color = check.checkColor, font_color = alphaCorrectedColor }) end end function updateDecalPositionAndSize(decalID) local decal = decals[decalID] local lookup = lookupDecalIndices[decalID] local decalScale = { x = decal.scale.x * scale.x * 0.25, y = decal.scale.y * scale.y * 0.25, z = decal.scale.y * scale.y * 0.25 } for k, v in pairs(lookup.inputs) do local pos = getDecalPosition(decalID, v.x, v.y) self.editInput({ index = v.index - 1, position = pos, scale = decalScale, rotation = decal.rotation }) end for k, v in pairs(lookup.selectionButtons) do local pos = getDecalPosition(decalID, v.x, v.y) self.editButton({ index = v.index - 1, position = pos, scale = decalScale }) end createSelectionHighlight(true) createDecals() end function onCheckValueEdited(ply, value, id) local spl = split(id, "/") local checkParameter = spl[2] if (#spl > 2) then checks[selectedId][checkParameter][spl[3]] = value else checks[selectedId][checkParameter] = value end if (compareString(checkParameter, { "pos", "size" })) then updateCheckPositionAndSize(selectedId) elseif (checkParameter == "characters") then updateCheckNameContentAndTooltip(selectedId) else refresh() end updateSave() end function onDecalValueEdited(ply, value, id) local spl = split(id, "/") local decalID = tonumber(spl[1]) local decalParameter = spl[2] if (#spl > 2) then decals[decalID][decalParameter][spl[3]] = value else decals[decalID][decalParameter] = value end updateSave() refresh() end function onToggleChanged(ply, value, id) local spl = split(id, "/") local tbl = nil if (selectedType == "field") then tbl = fields end if (selectedType == "check") then tbl = checks end if (selectedType == "decal") then tbl = decals end tbl[selectedId][spl[2]] = value if (spl[2] == "separateColors") then updateCheckFontAndColor(selectedId) refreshEditPanel() else if (not compareString(spl[2], { "locked", "fillFromDisabled" })) then refresh() end end updateSave() end function onFieldDropdownSelected(ply, option, id) local spl = split(id, "/") local parameterID = spl[2] local shouldRefresh = false if (parameterID == "align") then shouldRefresh = true if (option == "Auto") then option = 1 elseif (option == "Left") then option = 2 elseif (option == "Center") then option = 3 elseif (option == "Right") then option = 4 elseif (option == "Justified") then option = 5 end end fields[selectedId][parameterID] = option updateFieldFontAndColor(selectedId) updateSave() if (shouldRefresh) then -- I did my best to not refresh here, but it seems like editInput doesn't care about alignment refresh() end end function onCheckDropdownSelected(ply, option, id) local spl = split(id, "/") local parameterID = spl[2] if (parameterID == "value") then local value = 0 if (option == "Off") then value = 1 elseif (option == "On") then value = 2 end local check = checks[selectedId] check.value[selectedArrayId] = value updateCheckNameContentAndTooltip(selectedId) updateCheckFontAndColor(selectedId) updateSave() elseif (parameterID == "tooltip") then checks[selectedId].tooltip = option updateSave() else checks[selectedId][parameterID] = option updateSave() refresh() end end function onDecalDropdownSelected(ply, option, id) local spl = split(id, "/") local parameterID = spl[2] if (parameterID == "tooltip") then decals[selectedId].tooltip = option updateSave() else decals[selectedId][parameterID] = option updateSave() refresh() end end function onColorButtonPressed(ply, value, id) local spl = split(id, "/") local parameterID = spl[2] local tbl = nil if (selectedType == "field") then tbl = fields end if (selectedType == "check") then tbl = checks end local startingColor = tbl[selectedId][parameterID] if (startingColor == nil) then if (parameterID:find("textColor")) then startingColor = tbl[selectedId].textColor end end ply.showColorDialog(startingColor, function(color, player_color) tbl[selectedId][parameterID] = color if (selectedType == "field") then updateFieldFontAndColor(selectedId) elseif (selectedType == "check") then updateCheckFontAndColor(selectedId) end local c = "rgba(" .. color.r .. "," .. color.g .. "," .. color.b .. ",1)" local c2 = "rgba(" .. (color.r * 0.5 + 0.2) .. "," .. (color.g * 0.5 + 0.2) .. "," .. (color.b * 0.5 + 0.2) .. ",1)" UI.setAttribute(id, "colors", c .. "|" .. c2 .. "|" .. c2 .. "|" .. c) UI.setAttribute(id .. "/a", "percentage", color.a * 100) updateSave() end ) end function refreshAllPositionsAndSize() for k, v in pairs(fields) do updateFieldPositionAndSize(k) end for k, v in pairs(checks) do updateCheckPositionAndSize(k) end for k, v in pairs(decals) do updateDecalPositionAndSize(k) end end function onScaleChangedX(ply, value) scale.x = value updateSave() refreshAllPositionsAndSize() end function onScaleChangedY(ply, value) scale.y = value updateSave() refreshAllPositionsAndSize() end function onHeightChanged(ply, value) height = value updateSave() refreshAllPositionsAndSize() end function onFlip(ply, value) flip = value updateSave() refreshAllPositionsAndSize() local label = "┗" if (flip == "True") then label = "┓" end self.editButton({ index = 0, label = label }) label = "┏" if (flip == "True") then label = "┛" end self.editButton({ index = 1, label = label }) label = "┛" if (flip == "True") then label = "┏" end self.editButton({ index = 2, label = label }) label = "┓" if (flip == "True") then label = "┗" end self.editButton({ index = 3, label = label }) end function onCheckToggleChanged(ply, value, id) local spl = split(id, "/") local checkID = tonumber(spl[1]) checks[checkID][spl[2]] = value updateSave() refresh() end function onDecalToggleChanged(ply, value, id) local spl = split(id, "/") local decalID = tonumber(spl[1]) decals[decalID][spl[2]] = value updateSave() refresh() end function getDeselectButton(panelID) if (selectedId > 0) then return [[]] end return "" end function deselect() selectedId = 0 selectedType = "" refresh() end function correctCheckboxesAndDecals() for decalID, decal in pairs(decals) do decal.pos.x = decal.pos.x / scale.x decal.pos.y = decal.pos.y / scale.y end for checkID, check in pairs(checks) do check.pos.x = check.pos.x / scale.x check.pos.y = check.pos.y / scale.y check.distance.x = check.distance.x / scale.x check.distance.y = check.distance.y / scale.y end refresh() end function attrId(str) return "MarumEditorSheetAttribute_" .. str end function getFieldPanel() local guid = self.getGUID() return [[ Text ? Text Name Content Tooltip On edit Font and color Font size Align Text color Background Color Position and Size Nudge ]] .. nudgeDistance .. [[ Pos Size Array Columns/Rows Spacing Toggles Counter Total sum Lock ]] end function getCheckPanel() local guid = self.getGUID() return [[ Checkbox ? Checkbox Name State Tooltip Off On Disabled Font size Separate colors On color Off color Disabled color Background Color Position and Scale Nudge ]] .. nudgeDistance .. [[ Pos Scale Array Columns/Rows Spacing Toggles Can fill even if disabled Lock ]] end function getDecalPanel() local guid = self.getGUID() return [[ Image ? Image Name URL Tooltip Position and Scale Nudge ]] .. nudgeDistance .. [[ Pos Scale Rotation Toggles Lock ]] end function onCheckPresetButton(ply, value, id) local pres = { "◌ ○ ●", " ◇ ◆", " □ ■", "/ △ ▴", " ◎ ◉", "- x", " ◾ ✦", " x ♥" } ply.showOptionsDialog("Select checkbox preset", pres, 1, function(text, index, player_color) local disabled = pres[index]:sub(1, 1) local empty = pres[index]:sub(3, 3) local filled = pres[index]:sub(5, 5) checks[selectedId].characters.disabled = disabled checks[selectedId].characters.empty = empty checks[selectedId].characters.filled = filled updateCheckNameContentAndTooltip(selectedId) refreshEditPanel() updateSave() end) end function nudgeSet1() updateNudgeDistance(1) end function nudgeSet01() updateNudgeDistance(0.1) end function nudgeSet001() updateNudgeDistance(0.01) end function nudgeSet0001() updateNudgeDistance(0.001) end function updateNudgeDistance(value) nudgeDistance = value UI.setAttribute(attrId("Field_nudge"), "text", nudgeDistance) UI.setAttribute(attrId("Check_nudge"), "text", nudgeDistance) UI.setAttribute(attrId("Decal_nudge"), "text", nudgeDistance) updateSave() end function nudgeLeft(obj, value, id) local newX = fields[selectedId].pos.x - tonumber(nudgeDistance) UI.setAttribute(attrId("Field/pos/x"), "text", newX) fields[selectedId].pos.x = newX updateFieldPositionAndSize(selectedId) updateSave() end function nudgeUp(obj, value, id) local newY = fields[selectedId].pos.y - tonumber(nudgeDistance) UI.setAttribute(attrId("Field/pos/y"), "text", newY) fields[selectedId].pos.y = newY updateFieldPositionAndSize(selectedId) updateSave() end function nudgeRight(obj, value, id) local newX = fields[selectedId].pos.x + tonumber(nudgeDistance) UI.setAttribute(attrId("Field/pos/x"), "text", newX) fields[selectedId].pos.x = newX updateFieldPositionAndSize(selectedId) updateSave() end function nudgeDown(obj, value, id) local newY = fields[selectedId].pos.y + tonumber(nudgeDistance) UI.setAttribute(attrId("Field/pos/y"), "text", newY) fields[selectedId].pos.y = newY updateFieldPositionAndSize(selectedId) updateSave() end function nudgeDecalLeft(obj, value, id) local newX = decals[selectedId].pos.x - tonumber(nudgeDistance) UI.setAttribute(attrId("Decal/pos/x"), "text", newX) decals[selectedId].pos.x = newX updateDecalPositionAndSize(selectedId) updateSave() end function nudgeDecalUp(obj, value, id) local newY = decals[tonumber(selectedId)].pos.y - tonumber(nudgeDistance) UI.setAttribute(attrId("Decal/pos/y"), "text", newY) decals[tonumber(selectedId)].pos.y = newY updateDecalPositionAndSize(selectedId) updateSave() end function nudgeDecalRight(obj, value, id) local newX = decals[tonumber(selectedId)].pos.x + tonumber(nudgeDistance) UI.setAttribute(attrId("Decal/pos/x"), "text", newX) decals[tonumber(selectedId)].pos.x = newX updateDecalPositionAndSize(selectedId) updateSave() end function nudgeDecalDown(obj, value, id) local newY = decals[tonumber(selectedId)].pos.y + tonumber(nudgeDistance) UI.setAttribute(attrId("Decal/pos/y"), "text", newY) decals[tonumber(selectedId)].pos.y = newY updateDecalPositionAndSize(selectedId) updateSave() end function nudgeCheckLeft(obj, value, id) local newX = checks[tonumber(selectedId)].pos.x - tonumber(nudgeDistance) UI.setAttribute(attrId("Check/pos/x"), "text", newX) checks[tonumber(selectedId)].pos.x = newX updateCheckPositionAndSize(selectedId) updateSave() end function nudgeCheckUp(obj, value, id) local newY = checks[tonumber(selectedId)].pos.y - tonumber(nudgeDistance) UI.setAttribute(attrId("Check/pos/y"), "text", newY) checks[tonumber(selectedId)].pos.y = newY updateCheckPositionAndSize(selectedId) updateSave() end function nudgeCheckRight(obj, value, id) local newX = checks[tonumber(selectedId)].pos.x + tonumber(nudgeDistance) UI.setAttribute(attrId("Check/pos/x"), "text", newX) checks[tonumber(selectedId)].pos.x = newX updateCheckPositionAndSize(selectedId) updateSave() end function nudgeCheckDown(obj, value, id) local newY = checks[tonumber(selectedId)].pos.y + tonumber(nudgeDistance) UI.setAttribute(attrId("Check/pos/y"), "text", newY) checks[tonumber(selectedId)].pos.y = newY updateCheckPositionAndSize(selectedId) updateSave() end function showEmptyPrompt() selectedId = 0 selectedType = "" refreshEditPanel() end function duplicateField(ply, value, id) broadcastToColor("Text duplicated", ply.color) local newField = JSON.decode(JSON.encode(fields[tonumber(selectedId)])) table.insert(fields, newField) updateSave() selectField({ id = #fields, arrayId = 1 }) refresh() end function deleteField(ply, value, id) broadcastToColor("Text deleted", ply.color) table.remove(fields, tonumber(selectedId)) updateSave() if (#fields > 0) then selectField({ id = math.max(selectedId - 1, 1), arrayId = 1 }) else if (#checks > 0) then selectCheck({ id = 1, arrayId = 1 }) elseif (#decals > 0) then selectDecal(1) else showEmptyPrompt() end end refresh() end function duplicateCheck(ply, value, id) broadcastToColor("Check duplicated", ply.color) local newCheck = JSON.decode(JSON.encode(checks[tonumber(selectedId)])) table.insert(checks, newCheck) updateSave() selectCheck({ id = #checks, arrayId = 1 }) refresh() end function deleteCheck(ply, value, id) broadcastToColor("Check deleted", ply.color) table.remove(checks, tonumber(selectedId)) updateSave() if (#checks > 0) then selectCheck({ id = math.max(selectedId - 1, 1), arrayId = 1 }) else if (#fields > 0) then selectField({ id = 1, arrayId = 1 }) elseif (#decals > 0) then selectDecal(1) else showEmptyPrompt() end end refresh() end function duplicateDecal(ply, value, id) broadcastToColor("Image duplicated", ply.color) local newDecal = JSON.decode(JSON.encode(decals[tonumber(selectedId)])) table.insert(decals, newDecal) updateSave() selectDecal(#decals) refresh() end function deleteDecal(ply, value, id) broadcastToColor("Image deleted", ply.color) table.remove(decals, tonumber(selectedId)) updateSave() if (#decals > 0) then selectDecal(math.max(selectedId - 1, 1)) else if (#fields > 0) then selectField({ id = 1, arrayId = 1 }) elseif (#checks > 0) then selectCheck({ id = 1, arrayId = 1 }) else showEmptyPrompt() end end refresh() end function addField(obj, player_clicker_color, alt_click) local newField = { value = { "?" }, name = "", tooltip = "name", role = "Normal Field", textColor = { r = 0, g = 0, b = 0, a = 1 }, fieldColor = { r = 1, g = 1, b = 1, a = 1 }, font = 150, align = 3, pos = { x = 0, y = 0 }, size = { x = 250, y = 250 }, array = { x = 1, y = 1 }, distance = { x = 1, y = 1 }, locked = false } table.insert(fields, newField) selectField({ id = #fields, arrayId = 1 }) updateSave() refresh() end function addCheck(obj, player_clicker_color, alt_click) local newCheck = { name = "", tooltip = "hint", value = { 1 }, characters = { disabled = "◌", empty = "○", filled = "●" }, textColor = { r = 0, g = 0, b = 0, a = 1 }, textColorOff = { r = 0, g = 0, b = 0, a = 1 }, textColorDisabled = { r = 0.5, g = 0.5, b = 0.5, a = 1 }, separateColors = false, fillFromDisabled = false, checkColor = { r = 1, g = 1, b = 1, a = 1 }, pos = { x = 0, y = 0 }, size = { x = 1, y = 1 }, array = { x = 1, y = 1 }, distance = { x = 1, y = 1 }, font = 500, locked = false } table.insert(checks, newCheck) selectCheck({ id = #checks, arrayId = 1 }) updateSave() refresh() end function addDecal(obj, player_clicker_color, alt_click) local newDecal = { name = "", url = "https://api.tabletopsimulator.com/img/TSIcon.png", tooltip = "name", pos = { x = 0, y = 0 }, rotation = 0, scale = { x = 1, y = 1 }, locked = false } table.insert(decals, newDecal) selectDecal(#decals) updateSave() refresh() end function onDestroy() closePanel() end function cutAtWord(inputStr, delimiter) local t = {} local pattern = "(.-)" .. delimiter for str in inputStr:gmatch(pattern) do table.insert(t, str) end if (#t > 0) then return t[1] else return false end end function waitForUiLoaded(callback) if UI.loading == false then callback() return nil end return Wait.condition(callback, function() return UI.loading == false end) end