mobile support + api
This commit is contained in:
+214
-53
@@ -76,6 +76,12 @@
|
|||||||
players = {},
|
players = {},
|
||||||
player = {},
|
player = {},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
labels = {},
|
||||||
|
label_gradients = {},
|
||||||
|
|
||||||
|
is_mobile = uis.TouchEnabled and not uis.KeyboardEnabled,
|
||||||
|
|
||||||
colorpicker_open = false;
|
colorpicker_open = false;
|
||||||
gui;
|
gui;
|
||||||
}
|
}
|
||||||
@@ -180,11 +186,9 @@
|
|||||||
|
|
||||||
library.__index = library
|
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.config_folder = library.directory .. "/configs/" .. library.game_id
|
||||||
library.folders[#library.folders + 1] = "/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
|
for _, path in ipairs(library.folders) do
|
||||||
local folder = library.directory .. path
|
local folder = library.directory .. path
|
||||||
if not isfolder(folder) then
|
if not isfolder(folder) then
|
||||||
@@ -348,7 +352,8 @@
|
|||||||
local start
|
local start
|
||||||
|
|
||||||
frame.InputBegan:Connect(function(input)
|
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
|
dragging = true
|
||||||
start = input.Position
|
start = input.Position
|
||||||
start_size = frame.Position
|
start_size = frame.Position
|
||||||
@@ -356,13 +361,15 @@
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
frame.InputEnded:Connect(function(input)
|
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
|
dragging = false
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
library:connection(uis.InputChanged, function(input, game_event)
|
library:connection(uis.InputChanged, function(input, game_event)
|
||||||
if dragging and input.UserInputType == Enum.UserInputType.MouseMovement then
|
if dragging and (input.UserInputType == Enum.UserInputType.MouseMovement
|
||||||
|
or input.UserInputType == Enum.UserInputType.Touch) then
|
||||||
local viewport_x = camera.ViewportSize.X
|
local viewport_x = camera.ViewportSize.X
|
||||||
local viewport_y = camera.ViewportSize.Y
|
local viewport_y = camera.ViewportSize.Y
|
||||||
|
|
||||||
@@ -572,6 +579,23 @@
|
|||||||
themes.preset[theme] = color
|
themes.preset[theme] = color
|
||||||
end
|
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)
|
function library:connection(signal, callback)
|
||||||
local connection = signal:Connect(callback)
|
local connection = signal:Connect(callback)
|
||||||
|
|
||||||
@@ -712,6 +736,9 @@
|
|||||||
window_outline.Visible = bool
|
window_outline.Visible = bool
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- the mobile toggle needs a handle on the menu it is toggling
|
||||||
|
library.main_window = cfg
|
||||||
|
|
||||||
return setmetatable(cfg, library)
|
return setmetatable(cfg, library)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -913,21 +940,31 @@
|
|||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
function library:watermark(options)
|
-- standalone floating panel -- the watermark's look, but reusable from anywhere.
|
||||||
local cfg = {
|
-- writes go through the proxy, so `label.text = "hi"` updates the ui immediately:
|
||||||
name = options.name or "nebulahax";
|
--
|
||||||
}
|
-- 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
|
-- Instances
|
||||||
local outline = library:create("Frame", {
|
local outline = library:create("Frame", {
|
||||||
Parent = library.sgui;
|
Parent = library.sgui;
|
||||||
Position = dim2(0, 50, 0, 50);
|
Position = dim_offset(50, 50);
|
||||||
BorderColor3 = rgb(0, 0, 0);
|
BorderColor3 = rgb(0, 0, 0);
|
||||||
Size = dim2(0, 0, 0, 24);
|
Size = dim2(0, 0, 0, 24);
|
||||||
BorderSizePixel = 0;
|
BorderSizePixel = 0;
|
||||||
AutomaticSize = Enum.AutomaticSize.X;
|
AutomaticSize = Enum.AutomaticSize.X;
|
||||||
BackgroundColor3 = rgb(255, 255, 255)
|
BackgroundColor3 = rgb(255, 255, 255)
|
||||||
}); library.watermark_outline = outline; library:draggify(outline);
|
});
|
||||||
|
|
||||||
local dark = library:create("Frame", {
|
local dark = library:create("Frame", {
|
||||||
Parent = outline;
|
Parent = outline;
|
||||||
@@ -951,7 +988,7 @@
|
|||||||
FontFace = fonts["ProggyClean"];
|
FontFace = fonts["ProggyClean"];
|
||||||
TextColor3 = rgb(255, 255, 255);
|
TextColor3 = rgb(255, 255, 255);
|
||||||
BorderColor3 = rgb(0, 0, 0);
|
BorderColor3 = rgb(0, 0, 0);
|
||||||
Text = cfg.name;
|
Text = "";
|
||||||
Parent = dark;
|
Parent = dark;
|
||||||
Size = dim2(0, 0, 1, 0);
|
Size = dim2(0, 0, 1, 0);
|
||||||
Position = dim2(0, 1, 0, -1);
|
Position = dim2(0, 1, 0, -1);
|
||||||
@@ -963,7 +1000,7 @@
|
|||||||
BackgroundColor3 = rgb(255, 255, 255)
|
BackgroundColor3 = rgb(255, 255, 255)
|
||||||
});
|
});
|
||||||
|
|
||||||
library.watermark_gradient = library:create("UIGradient", {
|
local gradient = library:create("UIGradient", {
|
||||||
Color = rgbseq{
|
Color = rgbseq{
|
||||||
rgbkey(0, themes.preset["1"]),
|
rgbkey(0, themes.preset["1"]),
|
||||||
rgbkey(0.5, themes.preset["2"]),
|
rgbkey(0.5, themes.preset["2"]),
|
||||||
@@ -971,15 +1008,153 @@
|
|||||||
};
|
};
|
||||||
Parent = outline
|
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)
|
local state = {
|
||||||
text_title.Text = text
|
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
|
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)
|
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
|
end
|
||||||
|
|
||||||
local watermark = library:watermark({name = "mirrorhack - 100 fps - 100 ping"})
|
local watermark = library:watermark({name = "mirrorhack - 100 fps - 100 ping"})
|
||||||
@@ -997,6 +1172,25 @@
|
|||||||
end
|
end
|
||||||
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()
|
local pingTimeSec = game.Players.LocalPlayer:GetNetworkPing()
|
||||||
local pingTimeMs = pingTimeSec * 1000
|
local pingTimeMs = pingTimeSec * 1000
|
||||||
@@ -2594,48 +2788,15 @@
|
|||||||
section:keybind({name = "menu bind", callback = function(bool) window.toggle_menu(bool) end, default = true})
|
section:keybind({name = "menu bind", callback = function(bool) window.toggle_menu(bool) end, default = true})
|
||||||
section:colorpicker({name = "gradient 1", callback = function(color)
|
section:colorpicker({name = "gradient 1", callback = function(color)
|
||||||
library:update_theme("1", color)
|
library:update_theme("1", color)
|
||||||
|
library:refresh_gradients()
|
||||||
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"]),
|
|
||||||
};
|
|
||||||
end, color = themes.preset["1"]})
|
end, color = themes.preset["1"]})
|
||||||
section:colorpicker({name = "gradient 2", callback = function(color)
|
section:colorpicker({name = "gradient 2", callback = function(color)
|
||||||
library:update_theme("2", color)
|
library:update_theme("2", color)
|
||||||
|
library:refresh_gradients()
|
||||||
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"]),
|
|
||||||
};
|
|
||||||
end, color = themes.preset["2"]})
|
end, color = themes.preset["2"]})
|
||||||
section:colorpicker({name = "gradient 3", callback = function(color)
|
section:colorpicker({name = "gradient 3", callback = function(color)
|
||||||
library:update_theme("3", color)
|
library:update_theme("3", color)
|
||||||
|
library:refresh_gradients()
|
||||||
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"]),
|
|
||||||
};
|
|
||||||
end, color = themes.preset["3"]})
|
end, color = themes.preset["3"]})
|
||||||
|
|
||||||
main:column({})
|
main:column({})
|
||||||
|
|||||||
Reference in New Issue
Block a user