diff --git a/UI/library.lua b/UI/library.lua index b4539ab..7ba2198 100644 --- a/UI/library.lua +++ b/UI/library.lua @@ -282,9 +282,6 @@ end end - -- scales a floating root and its whole subtree. UIScale grows from the anchor - -- point, so with the default (0, 0) anchor AbsolutePosition is untouched and only - -- AbsoluteSize shrinks -- which keeps every drag and ratio calculation honest function library:apply_scale(frame) local scale = library:create("UIScale", { Parent = frame, @@ -304,12 +301,9 @@ end end - -- one pointer-drag helper behind the window, the resize grip and every label. - -- a touch is tracked by its input object rather than its type, so a second finger - -- (moving, shooting, another panel) can never hijack a drag that is already running. function library:pointer_drag(trigger, on_begin, on_move, on_end) local dragging = false - local touch_input -- the finger driving this drag, nil while on mouse + local touch_input local function drives(input, mouse_type) if touch_input then return input == touch_input end @@ -333,7 +327,6 @@ on_move(input) end) - -- released anywhere, not just over the trigger, or a drag can get stuck on library:connection(uis.InputEnded, function(input) if not dragging then return end if not drives(input, Enum.UserInputType.MouseButton1) then return end @@ -345,8 +338,6 @@ end function library:resizify(frame) - -- a 10px corner is unhittable with a finger. divided by the scale so the grip - -- stays that size on screen instead of shrinking along with the menu local grip = (library.is_mobile and 24 or 10) / library.ui_scale local Frame = Instance.new("TextButton") @@ -369,8 +360,6 @@ start = input.Position start_size = frame.Size end, function(input) - -- Size is in unscaled units while the pointer moves in screen pixels, so - -- the delta has to be converted or the corner drifts away from the finger local delta = (input.Position - start) / library.ui_scale frame.Size = dim2( @@ -389,7 +378,6 @@ return (y_cond and x_cond) end - -- point is optional, so mouse callers keep the old behaviour and touch can pass a finger function library:point_in_frame(uiobject, point) if not point then return library:mouse_in_frame(uiobject) end @@ -409,8 +397,6 @@ local start_size = frame.Position local start - -- without this a touch falls straight through to the camera controls and the - -- frame never sees the drag, which is why mobile could not move anything frame.Active = true library:pointer_drag(frame, function(input) @@ -418,7 +404,6 @@ start_size = frame.Position end, function(input) local delta = input.Position - start - -- measured size, so AutomaticSize frames clamp against their real width local size = frame.AbsoluteSize frame.Position = dim2( @@ -614,7 +599,6 @@ 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"]), @@ -707,7 +691,18 @@ BorderSizePixel = 0; BackgroundColor3 = rgb(255, 255, 255) }); - window_outline.Position = dim2(0, window_outline.AbsolutePosition.Y, 0, window_outline.AbsolutePosition.Y) + + do + local viewport = camera.ViewportSize + local rendered_x = cfg.size.X.Offset * library.ui_scale + local rendered_y = cfg.size.Y.Offset * library.ui_scale + + window_outline.Position = dim2( + 0, max(floor((viewport.X - rendered_x) / 2), 0), + 0, max(floor((viewport.Y - rendered_y) / 2), 0) + ) + end + cfg.main_outline = window_outline library:apply_scale(window_outline) @@ -773,7 +768,6 @@ 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) @@ -977,16 +971,7 @@ end) end - -- 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 {} @@ -1050,7 +1035,6 @@ library:apply_scale(outline) - -- transparent lid so touch and mouse land on one instance local hitbox = library:create("TextButton", { Parent = outline; BackgroundTransparency = 1; @@ -1070,7 +1054,6 @@ callback = options.callback, } - -- declared up front so the handlers below close over the local, not a global local proxy local setters = {} @@ -1093,8 +1076,6 @@ state.callback = value end - -- drag, with a tap fallback -- a pointer that barely moves is a click, not a drag. - -- fingers wobble far more than a mouse, so touch gets a looser threshold local drag_start, drag_origin, travelled = nil, nil, 0 local tap_slop = library.is_mobile and 12 or 6 @@ -1106,7 +1087,6 @@ 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) @@ -1196,10 +1176,6 @@ 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), @@ -1214,15 +1190,6 @@ end end, }) - - --[[ - local pingTimeSec = game.Players.LocalPlayer:GetNetworkPing() - local pingTimeMs = pingTimeSec * 1000 - pingLabel.Text = "Ping: " .. tostring(math.floor(pingTimeMs)) .. "ms" - - local realFPS = workspace:GetRealPhysicsFPS() - fpsLabel.Text = "FPS: " .. tostring(math.floor(realFPS)) - ]] function library:column(properties) self.count += 1 @@ -1608,8 +1575,12 @@ name = options.name or nil, suffix = options.suffix or "", flag = options.flag or options.name or "Flag", - callback = options.callback or function() end, - + callback = options.callback or function() end, + + -- fires once on release instead of every step, for work too heavy to + -- run mid-drag. never fires for a programmatic set, only real input + released = options.released or options.on_release or function() end, + min = options.min or options.minimum or 0, max = options.max or options.maximum or 100, intervals = options.interval or options.decimal or 1, @@ -1710,6 +1681,7 @@ slide_to(input) end, slide_to, function() cfg.dragging = false + cfg.released(cfg.value) end) -- @@ -2847,11 +2819,28 @@ config_holder = section:dropdown({name = "configs", options = {"report", "this", "error", "to", "dev"}, callback = function(option) if textbox then textbox.set(option) end end, flag = "config_name_list"}); library:update_config_list() textbox = section:textbox({name = "config name:", flag = "config_name_text"}) section:button({name = "save", callback = function() writefile(library:config_path(flags["config_name_text"]), library:get_config()) library:update_config_list() end}) - section:button({name = "load", callback = function() library:load_config(readfile(library:config_path(flags["config_name_text"]))) library:update_config_list() end}) + section:button({name = "load", callback = function() + library:load_config(readfile(library:config_path(flags["config_name_text"]))) + library:update_config_list() + + -- sliders restore through set, which skips the release hook on purpose, + -- so the saved scale has to be pushed through by hand + local dpi = tonumber(flags["dpi_scale"]) + if dpi then library:set_ui_scale(dpi / 100) end + end}) section:button({name = "delete", callback = function() delfile(library:config_path(flags["config_name_text"])) library:update_config_list() end}) local section = main:column({}):section({name = "other", size = 1, default = true}) section:keybind({name = "menu bind", callback = function(bool) window.toggle_menu(bool) end, default = true}) + + -- rescaling relays out every element, so it waits for release rather than + -- thrashing the whole menu on each step of the drag + section:slider({ + name = "dpi scale", min = 50, max = 150, default = library.ui_scale * 100, + interval = 1, suffix = "%", flag = "dpi_scale", + released = function(value) library:set_ui_scale(value / 100) end + }) + section:colorpicker({name = "gradient 1", callback = function(color) library:update_theme("1", color) library:refresh_gradients()