1
0

dpi scale

This commit is contained in:
tul
2026-08-04 15:56:53 +00:00
parent f2648c002b
commit f2c3449685
+36 -47
View File
@@ -282,9 +282,6 @@
end end
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) function library:apply_scale(frame)
local scale = library:create("UIScale", { local scale = library:create("UIScale", {
Parent = frame, Parent = frame,
@@ -304,12 +301,9 @@
end end
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) function library:pointer_drag(trigger, on_begin, on_move, on_end)
local dragging = false local dragging = false
local touch_input -- the finger driving this drag, nil while on mouse local touch_input
local function drives(input, mouse_type) local function drives(input, mouse_type)
if touch_input then return input == touch_input end if touch_input then return input == touch_input end
@@ -333,7 +327,6 @@
on_move(input) on_move(input)
end) end)
-- released anywhere, not just over the trigger, or a drag can get stuck on
library:connection(uis.InputEnded, function(input) library:connection(uis.InputEnded, function(input)
if not dragging then return end if not dragging then return end
if not drives(input, Enum.UserInputType.MouseButton1) then return end if not drives(input, Enum.UserInputType.MouseButton1) then return end
@@ -345,8 +338,6 @@
end end
function library:resizify(frame) 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 grip = (library.is_mobile and 24 or 10) / library.ui_scale
local Frame = Instance.new("TextButton") local Frame = Instance.new("TextButton")
@@ -369,8 +360,6 @@
start = input.Position start = input.Position
start_size = frame.Size start_size = frame.Size
end, function(input) 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 local delta = (input.Position - start) / library.ui_scale
frame.Size = dim2( frame.Size = dim2(
@@ -389,7 +378,6 @@
return (y_cond and x_cond) return (y_cond and x_cond)
end end
-- point is optional, so mouse callers keep the old behaviour and touch can pass a finger
function library:point_in_frame(uiobject, point) function library:point_in_frame(uiobject, point)
if not point then return library:mouse_in_frame(uiobject) end if not point then return library:mouse_in_frame(uiobject) end
@@ -409,8 +397,6 @@
local start_size = frame.Position local start_size = frame.Position
local start 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 frame.Active = true
library:pointer_drag(frame, function(input) library:pointer_drag(frame, function(input)
@@ -418,7 +404,6 @@
start_size = frame.Position start_size = frame.Position
end, function(input) end, function(input)
local delta = input.Position - start local delta = input.Position - start
-- measured size, so AutomaticSize frames clamp against their real width
local size = frame.AbsoluteSize local size = frame.AbsoluteSize
frame.Position = dim2( frame.Position = dim2(
@@ -614,7 +599,6 @@
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() function library:refresh_gradients()
local seq = rgbseq{ local seq = rgbseq{
rgbkey(0, themes.preset["1"]), rgbkey(0, themes.preset["1"]),
@@ -707,7 +691,18 @@
BorderSizePixel = 0; BorderSizePixel = 0;
BackgroundColor3 = rgb(255, 255, 255) 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 cfg.main_outline = window_outline
library:apply_scale(window_outline) library:apply_scale(window_outline)
@@ -773,7 +768,6 @@
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 library.main_window = cfg
return setmetatable(cfg, library) return setmetatable(cfg, library)
@@ -977,16 +971,7 @@
end) end)
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) function library.label(options)
-- tolerate library:label{} as well as library.label{}
if options == library then options = nil end if options == library then options = nil end
options = options or {} options = options or {}
@@ -1050,7 +1035,6 @@
library:apply_scale(outline) library:apply_scale(outline)
-- transparent lid so touch and mouse land on one instance
local hitbox = library:create("TextButton", { local hitbox = library:create("TextButton", {
Parent = outline; Parent = outline;
BackgroundTransparency = 1; BackgroundTransparency = 1;
@@ -1070,7 +1054,6 @@
callback = options.callback, callback = options.callback,
} }
-- declared up front so the handlers below close over the local, not a global
local proxy local proxy
local setters = {} local setters = {}
@@ -1093,8 +1076,6 @@
state.callback = value state.callback = value
end 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 drag_start, drag_origin, travelled = nil, nil, 0
local tap_slop = library.is_mobile and 12 or 6 local tap_slop = library.is_mobile and 12 or 6
@@ -1106,7 +1087,6 @@
local delta = input.Position - drag_start local delta = input.Position - drag_start
travelled = max(travelled, abs(delta.X) + abs(delta.Y)) 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( setters.position(dim_offset(
clamp(drag_origin.X.Offset + delta.X, 0, camera.ViewportSize.X - outline.AbsoluteSize.X), 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) clamp(drag_origin.Y.Offset + delta.Y, 0, camera.ViewportSize.Y - outline.AbsoluteSize.Y)
@@ -1196,10 +1176,6 @@
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({ library.mobile_toggle = library.label({
text = "toggle ui", text = "toggle ui",
position = vec2(50, 82), position = vec2(50, 82),
@@ -1215,15 +1191,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) function library:column(properties)
self.count += 1 self.count += 1
@@ -1610,6 +1577,10 @@
flag = options.flag or options.name or "Flag", 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, min = options.min or options.minimum or 0,
max = options.max or options.maximum or 100, max = options.max or options.maximum or 100,
intervals = options.interval or options.decimal or 1, intervals = options.interval or options.decimal or 1,
@@ -1710,6 +1681,7 @@
slide_to(input) slide_to(input)
end, slide_to, function() end, slide_to, function()
cfg.dragging = false cfg.dragging = false
cfg.released(cfg.value)
end) 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() 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"}) 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 = "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}) 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}) 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}) 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) section:colorpicker({name = "gradient 1", callback = function(color)
library:update_theme("1", color) library:update_theme("1", color)
library:refresh_gradients() library:refresh_gradients()