diff --git a/exemple/elasticScrollFrame.lua b/exemple/elasticScrollFrame.lua new file mode 100644 index 0000000..05db8c2 --- /dev/null +++ b/exemple/elasticScrollFrame.lua @@ -0,0 +1,221 @@ +local Quad = require(game.ReplicatedStorage:WaitForChild "Quad"); ---@module Quad.src +local _ = Quad.init "IosUI"; local round,class,mount,store,event,advancedTween = _.round,_.class,_.mount,_.store,_.event,_.advancedTween; + +-- init quad +local scrollFrame = class.extend(); +local connection = event.new; + +-- const +local white = Color3.fromRGB(255,255,255); +local scrollMut = 100; +local elasticMut = 20; +local fitTime = 0.22; +local tweenTime = 0.63; +local mouseUpMut = 42; +local max = math.max; +local min = math.min; +local abs = math.abs; + +-- base objects +local frame = class "Frame"; +frame.BorderSizePixel = 0; +local button = class "TextButton"; +button.BorderSizePixel = 0; +button.Text = nil; + +-- tween for moving +local out = advancedTween.EasingDirections.Out; +local exp2 = advancedTween.EasingFunctions.Exp2; +local tweenData = { + Time = tweenTime; + Direction = out; + EasingFunction = exp2; +}; +local runTween = advancedTween.RunTween; + +-- user input service +local userInputService = game:GetService "UserInputService"; +local inputEnded = userInputService.InputEnded; +local inputChanged = userInputService.InputChanged; +local mouseMovement = Enum.UserInputType.MouseMovement; +local mouseWheel = Enum.UserInputType.MouseWheel; +local shiftModifier = Enum.ModifierKey.Shift; +local mouseButton1 = Enum.UserInputType.MouseButton1; +local touch = Enum.UserInputType.Touch; + +-- Calc Elastic +local calcElastic do + local maxLogX = 32; + local mlog = math.log; + local maxlog = mlog(maxLogX+1,2); + local function log(x) + return min(mlog(x*maxLogX+1,2),maxlog)/maxlog; + end + function calcElastic (elastic,pos,max) + if pos < 0 then + return - (log(abs(pos)/scrollMut*elasticMut/elastic)*elastic); + elseif pos > max then + return max + (log((pos-max)/scrollMut*elasticMut/elastic)*elastic); + end + return pos; + end +end + +-- Init class +function scrollFrame:init(props) + local CanvasPosition = props.CanvasPosition or Vector2.new(0,0); + self._targetX = CanvasPosition.X; + self._targetY = CanvasPosition.Y; + props.CanvasPosition = CanvasPosition; + self._inputIndex = -20000000; + self._mouseConnection = connection(); + props:default("CanvasPosition",CanvasPosition); + props:default("CanvasSize",UDim2.fromScale(1,2)); + props:default("Size",UDim2.new(1,0,1,0)); + props:default("Position",UDim2.new(0,0,0,0)); + props:default("Elastic",122); + props:default("OverscrollX",false); + props:default("OverscrollY",true); + props:default("BackgroundTransparency",1); + props:default("ZIndex",1); + props:default("BackgroundColor3",white); + props:default("ScrollbarSizeX",2); + props:default("ScrollbarPadding",2); + props:default("ScrollbarVisible",true); +end + +-- Render objects +function scrollFrame:render(props) + return button { + Size = props "Size"; + Position = props "Position"; + BackgroundTransparency = props "BackgroundTransparency"; + BackgroundColor3 = props "BackgroundColor3"; + ZIndex = props "ZIndex"; + ClipsDescendants = true; + [event.createdSync] = function (this) + this.InputChanged:Connect(function (input) + if self._mouseDown then + return; + end + local inputType= input.UserInputType; + if inputType == mouseWheel then + local x,y = 0,1; + if input:IsModifierKeyDown(shiftModifier) then + x,y = 1,0; + end + local scroll = input.Position.Z * scrollMut; + local inputIndex = self:update(self._targetX + x*scroll,self._targetY - y*scroll); + delay(fitTime,function () + if inputIndex == self._inputIndex then + self:fit(); + end + end); + end + end); + end; + [event "MouseButton1Down"] = function (this,downX,downY) + self._mouseDown = true; + local mouseConnection = self._mouseConnection; + mouseConnection:disconnect(); + mouseConnection:add(inputEnded:Connect(function (input) + self._mouseDown = false; + local inputType = input.UserInputType; + if inputType == mouseButton1 or inputType == touch then + local position = input.Position; + local x = position.X; + local y = position.Y; + self:update(self._targetX + ((x - downX) * mouseUpMut),self._targetY + ((- y + downY)*mouseUpMut)); + mouseConnection:disconnect(); + self:fit(); + end + end)); + mouseConnection:add(inputChanged:Connect(function (input) + if input.UserInputType == mouseMovement then + local position = input.Position; + local x = position.X; + local y = position.Y; + local inputIndex = self:update(self._targetX + x - downX,self._targetY - y + downY); + delay(fitTime,function () + if inputIndex == self._inputIndex then + self:fit(); + end + end); + downX,downY = x,y; + end + end)); + end; + frame { + [event.prop "AbsoluteSize"] = function () + self:fit(); + end; + BackgroundTransparency = 1; + Size = props "CanvasSize"; + ZIndex = props "ZIndex"; + Position = UDim2.fromOffset(self._targetX,self._targetY); + [event.createdSync] = function (this) + self._holder = this; + end; + }; + -- button { + + -- } + }; +end + +-- Set scroll position with allowing overflow +function scrollFrame:update(x,y) + x,y = x or self._targetX,y or self._targetY; + local inputIndex = self._inputIndex + 1; + self._inputIndex = inputIndex; + + local holder = self._holder; + local holderAbsSize = holder.AbsoluteSize; + local objectAbsSize = self.__object.AbsoluteSize; + local maxX = holderAbsSize.X - objectAbsSize.X; + local maxY = holderAbsSize.Y - objectAbsSize.Y; + + local elastic = self.Elastic; + x = self.OverscrollX and calcElastic(elastic,x,maxX) or max(min(maxX,x),0); + y = self.OverscrollY and calcElastic(elastic,y,maxY) or max(min(maxY,y),0); + + runTween(self._holder,tweenData,{ + Position = UDim2.fromOffset(x,-y); + }); + self._targetX,self._targetY = x,y; + return inputIndex; +end + +-- Get canvas position +function scrollFrame.getter:CanvasPosition() + return Vector2.new(self._targetX,self._targetY); +end + +-- Set canvas postion +function scrollFrame.setter:CanvasPosition(pos) + local inputIndex = self:update(pos.X,pos.Y); + delay(fitTime,function () + if inputIndex == self._inputIndex then + self:fit(); + end + end); +end + +-- Fit position (remove overflow) +function scrollFrame:fit() + local targetY = self._targetY; + local targetX = self._targetX; + local holder = self._holder; + local holderAbsoluteSize = holder.AbsoluteSize; + local objectAbsSize = self.__object.AbsoluteSize; + local maxX = holderAbsoluteSize.X - objectAbsSize.X; + local maxY = holderAbsoluteSize.Y - objectAbsSize.Y; + if targetX<0 or targetY<0 or targetX>maxX or targetY>maxY then + self:update( + max(min(targetX,maxX),0), + max(min(targetY,maxY),0) + ); + end +end + +return scrollFrame; diff --git a/src/event.lua b/src/event.lua index 913263b..304d606 100644 --- a/src/event.lua +++ b/src/event.lua @@ -8,7 +8,7 @@ function module.init(shared) local prefix = "Event::"; local special = { ["Property::(.+)"] = function (this,func,property) - this.GetPropertyChangedSignal(property):Connect(function() + this:GetPropertyChangedSignal(property):Connect(function() func(this,this[property]); end); end; @@ -80,6 +80,12 @@ function module.init(shared) function disconnecter:add(connection) insert(self,connection); end + function disconnecter:disconnect() + for i,v in pairs(self) do + pcall(v.Disconnect,v); + self[i] = nil; + end + end function disconnecter:destroy() local id = self.id; if id then diff --git a/src/libs/AdvancedTween/init.lua b/src/libs/AdvancedTween/init.lua index 47cb201..57aab49 100644 --- a/src/libs/AdvancedTween/init.lua +++ b/src/libs/AdvancedTween/init.lua @@ -32,7 +32,7 @@ for i,v in pairs(EasingFunctions) do module.EasingFunctions[i] = v end -module.EasingDirection = { +module.EasingDirections = { Out = "Out"; -- 반전된 방향 In = "In" ; -- 기본방향 } diff --git a/src/store.lua b/src/store.lua index 2e65e61..84301e6 100644 --- a/src/store.lua +++ b/src/store.lua @@ -145,7 +145,11 @@ function module.init(shared) return register; end function store:default(key,value) - self[key] = self[key] or value; + local old = self[key]; + if type(old) == "nil" then + self[key] = value; + return; + end end function new.new(self,id) if id then