1
0

mobile support + api

This commit is contained in:
tul
2026-08-03 21:19:08 +00:00
parent fe2cef53ec
commit 67c04b5acc
+231 -70
View File
@@ -70,14 +70,20 @@
flags = {},
config_flags = {},
connections = {},
connections = {},
notifications = {},
playerlist_data = {
players = {},
player = {},
player = {},
},
colorpicker_open = false;
gui;
labels = {},
label_gradients = {},
is_mobile = uis.TouchEnabled and not uis.KeyboardEnabled,
colorpicker_open = false;
gui;
}
local themes = {
@@ -180,11 +186,9 @@
library.__index = library
-- configs live in their own folder per game, so each game keeps its own saves
library.config_folder = library.directory .. "/configs/" .. library.game_id
library.folders[#library.folders + 1] = "/configs/" .. library.game_id
-- ipairs, not next: the parent folder has to exist before the child
for _, path in ipairs(library.folders) do
local folder = library.directory .. path
if not isfolder(folder) then
@@ -348,7 +352,8 @@
local start
frame.InputBegan:Connect(function(input)
if input.UserInputType == Enum.UserInputType.MouseButton1 then
if input.UserInputType == Enum.UserInputType.MouseButton1
or input.UserInputType == Enum.UserInputType.Touch then
dragging = true
start = input.Position
start_size = frame.Position
@@ -356,13 +361,15 @@
end)
frame.InputEnded:Connect(function(input)
if input.UserInputType == Enum.UserInputType.MouseButton1 then
if input.UserInputType == Enum.UserInputType.MouseButton1
or input.UserInputType == Enum.UserInputType.Touch then
dragging = false
end
end)
library:connection(uis.InputChanged, function(input, game_event)
if dragging and input.UserInputType == Enum.UserInputType.MouseMovement then
library:connection(uis.InputChanged, function(input, game_event)
if dragging and (input.UserInputType == Enum.UserInputType.MouseMovement
or input.UserInputType == Enum.UserInputType.Touch) then
local viewport_x = camera.ViewportSize.X
local viewport_y = camera.ViewportSize.Y
@@ -572,6 +579,23 @@
themes.preset[theme] = color
end
-- repaints the window gradient plus every floating label made by library.label
function library:refresh_gradients()
local seq = rgbseq{
rgbkey(0, themes.preset["1"]),
rgbkey(0.5, themes.preset["2"]),
rgbkey(1, themes.preset["3"]),
}
if library.gradient then
library.gradient.Color = seq
end
for _, gradient in next, library.label_gradients do
gradient.Color = seq
end
end
function library:connection(signal, callback)
local connection = signal:Connect(callback)
@@ -708,12 +732,15 @@
});
--
function cfg.toggle_menu(bool)
window_outline.Visible = bool
function cfg.toggle_menu(bool)
window_outline.Visible = bool
end
-- the mobile toggle needs a handle on the menu it is toggling
library.main_window = cfg
return setmetatable(cfg, library)
end
end
function library:tab(properties)
local cfg = {
@@ -913,22 +940,32 @@
end)
end
function library:watermark(options)
local cfg = {
name = options.name or "nebulahax";
}
-- standalone floating panel -- the watermark's look, but reusable from anywhere.
-- writes go through the proxy, so `label.text = "hi"` updates the ui immediately:
--
-- local label = library.label()
-- label.text = "hi"
-- label.position = Vector2.new(500, 500)
--
-- pass a callback to make it tappable (dragging never counts as a tap).
function library.label(options)
-- tolerate library:label{} as well as library.label{}
if options == library then options = nil end
options = options or {}
local cfg = {}
-- Instances
local outline = library:create("Frame", {
Parent = library.sgui;
Position = dim2(0, 50, 0, 50);
Position = dim_offset(50, 50);
BorderColor3 = rgb(0, 0, 0);
Size = dim2(0, 0, 0, 24);
BorderSizePixel = 0;
AutomaticSize = Enum.AutomaticSize.X;
BackgroundColor3 = rgb(255, 255, 255)
}); library.watermark_outline = outline; library:draggify(outline);
});
local dark = library:create("Frame", {
Parent = outline;
BackgroundTransparency = 0.6;
@@ -951,7 +988,7 @@
FontFace = fonts["ProggyClean"];
TextColor3 = rgb(255, 255, 255);
BorderColor3 = rgb(0, 0, 0);
Text = cfg.name;
Text = "";
Parent = dark;
Size = dim2(0, 0, 1, 0);
Position = dim2(0, 1, 0, -1);
@@ -961,41 +998,198 @@
AutomaticSize = Enum.AutomaticSize.X;
TextSize = 12;
BackgroundColor3 = rgb(255, 255, 255)
});
library.watermark_gradient = library:create("UIGradient", {
});
local gradient = library:create("UIGradient", {
Color = rgbseq{
rgbkey(0, themes.preset["1"]),
rgbkey(0, themes.preset["1"]),
rgbkey(0.5, themes.preset["2"]),
rgbkey(1, themes.preset["3"]),
};
Parent = outline
});
library.label_gradients[#library.label_gradients + 1] = gradient
-- transparent lid so touch and mouse land on one instance
local hitbox = library:create("TextButton", {
Parent = outline;
BackgroundTransparency = 1;
Size = dim2(1, 0, 1, 0);
Text = "";
AutoButtonColor = false;
BorderSizePixel = 0;
ZIndex = 5
});
--
function cfg.update_text(text)
text_title.Text = text
local state = {
text = options.text or options.name or "mirrorhack",
position = options.position or vec2(50, 50),
visible = options.visible ~= false,
callback = options.callback,
}
-- declared up front so the handlers below close over the local, not a global
local proxy
local setters = {}
function setters.text(value)
state.text = tostring(value)
text_title.Text = state.text
end
cfg.update_text(cfg.name)
function setters.position(value)
state.position = value
outline.Position = typeof(value) == "UDim2" and value or dim_offset(value.X, value.Y)
end
return setmetatable(cfg, library)
end
function setters.visible(value)
state.visible = value and true or false
outline.Visible = state.visible
end
function setters.callback(value)
state.callback = value
end
-- drag, with a tap fallback -- a touch that barely moves is a click, not a drag
local dragging, drag_start, drag_origin, travelled = false, nil, nil, 0
hitbox.InputBegan:Connect(function(input)
if input.UserInputType ~= Enum.UserInputType.MouseButton1
and input.UserInputType ~= Enum.UserInputType.Touch then return end
dragging = true
travelled = 0
drag_start = input.Position
drag_origin = outline.Position
end)
library:connection(uis.InputChanged, function(input)
if not dragging then return end
if input.UserInputType ~= Enum.UserInputType.MouseMovement
and input.UserInputType ~= Enum.UserInputType.Touch then return end
local delta = input.Position - drag_start
travelled = max(travelled, abs(delta.X) + abs(delta.Y))
-- AutomaticSize leaves Size.X.Offset at 0, so clamp against the measured width
setters.position(dim_offset(
clamp(drag_origin.X.Offset + delta.X, 0, camera.ViewportSize.X - outline.AbsoluteSize.X),
clamp(drag_origin.Y.Offset + delta.Y, 0, camera.ViewportSize.Y - outline.AbsoluteSize.Y)
))
end)
library:connection(uis.InputEnded, function(input)
if not dragging then return end
if input.UserInputType ~= Enum.UserInputType.MouseButton1
and input.UserInputType ~= Enum.UserInputType.Touch then return end
dragging = false
if travelled <= 6 and state.callback then
state.callback(proxy)
end
end)
cfg.instance = outline
cfg.label = text_title
cfg.gradient = gradient
cfg.hitbox = hitbox
function cfg.update_text(text)
proxy.text = text
end
function cfg.remove()
local index = find(library.label_gradients, gradient)
if index then remove(library.label_gradients, index) end
index = find(library.labels, proxy)
if index then remove(library.labels, index) end
outline:Destroy()
end
proxy = setmetatable({}, {
__index = function(_, key)
local value = state[key]
if value ~= nil then return value end
value = cfg[key]
if value ~= nil then return value end
return library[key]
end,
__newindex = function(_, key, value)
local setter = setters[key]
if setter then
setter(value)
else
rawset(cfg, key, value)
end
end,
})
setters.text(state.text)
setters.position(state.position)
setters.visible(state.visible)
library.labels[#library.labels + 1] = proxy
return proxy
end
function library:watermark(options)
options = options or {}
local mark = library.label({
text = options.name or options.text or "nebulahax",
position = options.position,
})
library.watermark_outline = mark.instance
library.watermark_gradient = mark.gradient
return mark
end
local watermark = library:watermark({name = "mirrorhack - 100 fps - 100 ping"})
local fps = 0
local watermark_delay = tick()
local watermark_delay = tick()
run.RenderStepped:Connect(function()
fps += 1
if tick() - watermark_delay > 1 then
if tick() - watermark_delay > 1 then
watermark_delay = tick()
local ping = math.floor(stats.PerformanceStats.Ping:GetValue()) .. "ms"
local ping = math.floor(stats.PerformanceStats.Ping:GetValue()) .. "ms"
watermark.update_text(string.format("mirrorhack - fps: %s - ping: %s", fps, ping))
fps = 0
end
end)
-- mobile has no keyboard, so the menu keybind is unreachable -- hand it a tappable
-- stand-in that sits under the watermark and toggles the same thing the bind does.
-- reads the live visibility rather than tracking its own flag, so tapping stays in
-- sync with anyone who toggled the menu by keybind
library.mobile_toggle = library.label({
text = "toggle ui",
position = vec2(50, 82),
visible = library.is_mobile,
callback = function()
local window = library.main_window
if window and window.main_outline then
window.toggle_menu(not window.main_outline.Visible)
elseif library.gui then
library.gui.Enabled = not library.gui.Enabled
end
end,
})
--[[
local pingTimeSec = game.Players.LocalPlayer:GetNetworkPing()
@@ -2594,48 +2788,15 @@
section:keybind({name = "menu bind", callback = function(bool) window.toggle_menu(bool) end, default = true})
section:colorpicker({name = "gradient 1", callback = function(color)
library:update_theme("1", color)
library.gradient.Color = rgbseq{
rgbkey(0, themes.preset["1"]),
rgbkey(0.5, themes.preset["2"]),
rgbkey(1, themes.preset["3"]),
};
library.watermark_gradient.Color = rgbseq{
rgbkey(0, themes.preset["1"]),
rgbkey(0.5, themes.preset["2"]),
rgbkey(1, themes.preset["3"]),
};
library:refresh_gradients()
end, color = themes.preset["1"]})
section:colorpicker({name = "gradient 2", callback = function(color)
library:update_theme("2", color)
library.gradient.Color = rgbseq{
rgbkey(0, themes.preset["1"]),
rgbkey(0.5, themes.preset["2"]),
rgbkey(1, themes.preset["3"]),
};
library.watermark_gradient.Color = rgbseq{
rgbkey(0, themes.preset["1"]),
rgbkey(0.5, themes.preset["2"]),
rgbkey(1, themes.preset["3"]),
};
library:refresh_gradients()
end, color = themes.preset["2"]})
section:colorpicker({name = "gradient 3", callback = function(color)
library:update_theme("3", color)
library.gradient.Color = rgbseq{
rgbkey(0, themes.preset["1"]),
rgbkey(0.5, themes.preset["2"]),
rgbkey(1, themes.preset["3"]),
};
library.watermark_gradient.Color = rgbseq{
rgbkey(0, themes.preset["1"]),
rgbkey(0.5, themes.preset["2"]),
rgbkey(1, themes.preset["3"]),
};
library:refresh_gradients()
end, color = themes.preset["3"]})
main:column({})