From b7867dff0b5959cf87aceeed36784c9dae281b9d Mon Sep 17 00:00:00 2001 From: tul <27+tul@noreply.localhost> Date: Tue, 4 Aug 2026 15:14:58 +0000 Subject: [PATCH] mobile support --- UI/library.lua | 327 +++++++++++++++++++++++++------------------------ 1 file changed, 168 insertions(+), 159 deletions(-) diff --git a/UI/library.lua b/UI/library.lua index 63f0e44..995748f 100644 --- a/UI/library.lua +++ b/UI/library.lua @@ -80,7 +80,7 @@ labels = {}, label_gradients = {}, - is_mobile = uis.TouchEnabled and not uis.KeyboardEnabled, + is_mobile = uis.TouchEnabled, colorpicker_open = false; gui; @@ -279,57 +279,78 @@ end end - function library:resizify(frame) + -- 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 function drives(input, mouse_type) + if touch_input then return input == touch_input end + return input.UserInputType == mouse_type + end + + trigger.InputBegan:Connect(function(input) + local kind = input.UserInputType + if kind ~= Enum.UserInputType.MouseButton1 and kind ~= Enum.UserInputType.Touch then return end + if dragging then return end + + dragging = true + touch_input = (kind == Enum.UserInputType.Touch) and input or nil + on_begin(input) + end) + + library:connection(uis.InputChanged, function(input) + if not dragging then return end + if not drives(input, Enum.UserInputType.MouseMovement) then return end + + 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 + + dragging = false + touch_input = nil + if on_end then on_end(input) end + end) + end + + function library:resizify(frame) + -- a 10px corner is unhittable with a finger + local grip = library.is_mobile and 24 or 10 + local Frame = Instance.new("TextButton") - Frame.Position = dim2(1, -10, 1, -10) + Frame.Position = dim2(1, -grip, 1, -grip) Frame.BorderColor3 = rgb(0, 0, 0) - Frame.Size = dim2(0, 10, 0, 10) + Frame.Size = dim2(0, grip, 0, grip) Frame.BorderSizePixel = 0 Frame.BackgroundColor3 = rgb(255, 255, 255) Frame.Parent = frame - Frame.BackgroundTransparency = 1 + Frame.BackgroundTransparency = 1 Frame.Text = "" + Frame.AutoButtonColor = false + Frame.Active = true - local resizing = false - local start_size - local start - local og_size = frame.Size + local start_size + local start + local og_size = frame.Size - Frame.InputBegan:Connect(function(input) - if input.UserInputType == Enum.UserInputType.MouseButton1 then - resizing = true - start = input.Position - start_size = frame.Size - end - end) + library:pointer_drag(Frame, function(input) + start = input.Position + start_size = frame.Size + end, function(input) + local delta = input.Position - start - Frame.InputEnded:Connect(function(input) - if input.UserInputType == Enum.UserInputType.MouseButton1 then - resizing = false - end - end) - - library:connection(uis.InputChanged, function(input, game_event) - if resizing and input.UserInputType == Enum.UserInputType.MouseMovement then - local viewport_x = camera.ViewportSize.X - local viewport_y = camera.ViewportSize.Y - - local current_size = dim2( - start_size.X.Scale, - math.clamp( - start_size.X.Offset + (input.Position.X - start.X), - og_size.X.Offset, - viewport_x - ), - start_size.Y.Scale, - math.clamp( - start_size.Y.Offset + (input.Position.Y - start.Y), - og_size.Y.Offset, - viewport_y - ) - ) - frame.Size = current_size - end + frame.Size = dim2( + start_size.X.Scale, + clamp(start_size.X.Offset + delta.X, og_size.X.Offset, camera.ViewportSize.X), + start_size.Y.Scale, + clamp(start_size.Y.Offset + delta.Y, og_size.Y.Offset, camera.ViewportSize.Y) + ) end) end @@ -340,6 +361,16 @@ 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 + + local pos, size = uiobject.AbsolutePosition, uiobject.AbsoluteSize + + return point.X >= pos.X and point.X <= pos.X + size.X + and point.Y >= pos.Y and point.Y <= pos.Y + size.Y + end + library.lerp = function(start, finish, t) t = t or 1 / 8 @@ -347,49 +378,25 @@ end function library:draggify(frame) - local dragging = false local start_size = frame.Position - local start + local start - frame.InputBegan:Connect(function(input) - if input.UserInputType == Enum.UserInputType.MouseButton1 - or input.UserInputType == Enum.UserInputType.Touch then - dragging = true - start = input.Position - start_size = frame.Position - end - end) + -- 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.InputEnded:Connect(function(input) - if input.UserInputType == Enum.UserInputType.MouseButton1 - or input.UserInputType == Enum.UserInputType.Touch then - dragging = false - end - end) + library:pointer_drag(frame, function(input) + start = input.Position + 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 - 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 - - local current_position = dim2( - 0, - clamp( - start_size.X.Offset + (input.Position.X - start.X), - 0, - viewport_x - frame.Size.X.Offset - ), - 0, - math.clamp( - start_size.Y.Offset + (input.Position.Y - start.Y), - 0, - viewport_y - frame.Size.Y.Offset - ) - ) - - frame.Position = current_position - end + frame.Position = dim2( + 0, clamp(start_size.X.Offset + delta.X, 0, camera.ViewportSize.X - size.X), + 0, clamp(start_size.Y.Offset + delta.Y, 0, camera.ViewportSize.Y - size.Y) + ) end) end @@ -1019,6 +1026,7 @@ Text = ""; AutoButtonColor = false; BorderSizePixel = 0; + Active = true; ZIndex = 5 }); -- @@ -1053,24 +1061,16 @@ 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 + -- 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 - hitbox.InputBegan:Connect(function(input) - if input.UserInputType ~= Enum.UserInputType.MouseButton1 - and input.UserInputType ~= Enum.UserInputType.Touch then return end - - dragging = true + library:pointer_drag(hitbox, function(input) 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 - + end, function(input) local delta = input.Position - drag_start travelled = max(travelled, abs(delta.X) + abs(delta.Y)) @@ -1079,16 +1079,8 @@ 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 + end, function() + if travelled <= tap_slop and state.callback then state.callback(proxy) end end) @@ -1674,25 +1666,20 @@ -- -- Connections - outline.MouseButton1Down:Connect(function() - cfg.dragging = true - end) + local function slide_to(input) + local size_x = (input.Position.X - inline.AbsolutePosition.X) / inline.AbsoluteSize.X - library:connection(uis.InputChanged, function(input) - if cfg.dragging and input.UserInputType == Enum.UserInputType.MouseMovement then - local size_x = (input.Position.X - inline.AbsolutePosition.X) / inline.AbsoluteSize.X - local value = ((cfg.max - cfg.min) * size_x) + cfg.min - - cfg.set(value) - end - end) + cfg.set(((cfg.max - cfg.min) * size_x) + cfg.min) + end - library:connection(uis.InputEnded, function(input) - if input.UserInputType == Enum.UserInputType.MouseButton1 then - cfg.dragging = false - end + -- setting on press as well as on move, since a tap has nowhere to move to + library:pointer_drag(outline, function(input) + cfg.dragging = true + slide_to(input) + end, slide_to, function() + cfg.dragging = false end) - -- + -- cfg.set(cfg.default) @@ -1929,15 +1916,19 @@ cfg.set_visible(cfg.open) end) - uis.InputEnded:Connect(function(input) - if input.UserInputType == Enum.UserInputType.MouseButton1 then - if not (library:mouse_in_frame(accent) or library:mouse_in_frame(dropdown)) then - cfg.open = false - cfg.set_visible(false) - end + library:connection(uis.InputEnded, function(input) + local kind = input.UserInputType + if kind ~= Enum.UserInputType.MouseButton1 and kind ~= Enum.UserInputType.Touch then return end + + local at = (kind == Enum.UserInputType.Touch) + and vec2(input.Position.X, input.Position.Y) or nil + + if not (library:point_in_frame(accent, at) or library:point_in_frame(dropdown, at)) then + cfg.open = false + cfg.set_visible(false) end end) - -- + -- return setmetatable(cfg, library) end @@ -2273,9 +2264,15 @@ cfg.callback(Color, a) end - function cfg.update_color() - local mouse = uis:GetMouseLocation() - local offset = vec2(mouse.X, mouse.Y - gui_offset) + -- position is only supplied for touch, where there is no mouse to query. + -- it arrives already in AbsolutePosition space, same as the slider uses + function cfg.update_color(position) + local offset = position + + if not offset then + local mouse = uis:GetMouseLocation() + offset = vec2(mouse.X, mouse.Y - gui_offset) + end if dragging_sat then s = math.clamp((offset - saturation_value_button.AbsolutePosition).X / saturation_value_button.AbsoluteSize.X, 0, 1) @@ -2301,37 +2298,44 @@ cfg.set_visible(cfg.open) end) - uis.InputChanged:Connect(function(input) - if (dragging_sat or dragging_hue or dragging_alpha) and input.UserInputType == Enum.UserInputType.MouseMovement then - cfg.update_color() + -- mouse keeps querying GetMouseLocation, a finger has to report itself + local function pointer_at(input) + if input.UserInputType == Enum.UserInputType.Touch then + return vec2(input.Position.X, input.Position.Y) end - end) + return nil + end + + local function handle(button, set_dragging) + library:pointer_drag(button, function(input) + set_dragging(true) + cfg.update_color(pointer_at(input)) + end, function(input) + cfg.update_color(pointer_at(input)) + end, function() + set_dragging(false) + end) + end + + handle(saturation_button, function(v) dragging_sat = v end) + handle(hue_button, function(v) dragging_hue = v end) + handle(alpha_button, function(v) dragging_alpha = v end) library:connection(uis.InputEnded, function(input) - if input.UserInputType == Enum.UserInputType.MouseButton1 then - dragging_sat = false - dragging_hue = false - dragging_alpha = false + local kind = input.UserInputType + if kind ~= Enum.UserInputType.MouseButton1 and kind ~= Enum.UserInputType.Touch then return end - if not (library:mouse_in_frame(colorpicker_element) or library:mouse_in_frame(colorpicker)) then - cfg.open = false - cfg.set_visible(false) - end + dragging_sat = false + dragging_hue = false + dragging_alpha = false + + local at = pointer_at(input) + + if not (library:point_in_frame(colorpicker_element, at) or library:point_in_frame(colorpicker, at)) then + cfg.open = false + cfg.set_visible(false) end end) - - alpha_button.MouseButton1Down:Connect(function() - dragging_alpha = true - end) - - hue_button.MouseButton1Down:Connect(function() - dragging_hue = true - end) - - saturation_button.MouseButton1Down:Connect(function() - print("hiu") - dragging_sat = true - end) textbox.FocusLost:Connect(function() local r, g, b, a = library:convert(textbox.Text) @@ -2706,8 +2710,13 @@ end end - if input.UserInputType == Enum.UserInputType.MouseButton1 then - if not (library:mouse_in_frame(keybind_holder) or library:mouse_in_frame(accent)) then + local kind = input.UserInputType + + if kind == Enum.UserInputType.MouseButton1 or kind == Enum.UserInputType.Touch then + local at = (kind == Enum.UserInputType.Touch) + and vec2(input.Position.X, input.Position.Y) or nil + + if not (library:point_in_frame(keybind_holder, at) or library:point_in_frame(accent, at)) then cfg.open = false cfg.set_visible(false) end