mobile support + api
This commit is contained in:
+231
-70
@@ -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({})
|
||||
|
||||
Reference in New Issue
Block a user