Below is a modified version of the lcb file that will compile in DP-1. I tried to upload a file but the forums don't allow lcb or txt files. I am using the latest code from the "develop" branch as I want to be testing and providing feedback on the latest work the dev team is doing. One of the functions you may be interested in is the scaleAndMaintainAspectRatioTransform() handler. It will scale your paths and maintain the aspect ratio. That way you don't have to have your widget just the right size in order to have the paths display properly. Your control looks cool!
Code: Select all
widget com.livecode.extensions.trevordevore.rotatingSVG
use com.livecode.canvas
use com.livecode.widget
use com.livecode.engine
use com.livecode.math
metadata title is "Rotating SVG"
metadata author is "Trevor DeVore"
metadata version is "1.0.0"
-- public
private variable mRotateBy as Number
private variable mFrequency as Number
private variable mSVGPath as Path
private variable mColor as Color
private variable mIconOrigin as List
private variable mIconSize as Number
private variable mIsAnimating as Boolean
private variable mIconStyle as String
-- private
private variable mCenter as Point
private variable mWorkingPath as Path
private variable mRotation as Number
property rotateBy get mRotateBy set setRotateBy
property currentRotation get mRotateBy set setRotation -- we don't keep track of current rotation. Only set it.
property frequency get mFrequency set mFrequency
property iconStyle get mIconStyle set setIconStyle
property iconSVGPath get getIconPath set setIconPath
property iconColor get getColor set setColor
property iconOrigin get getIconOrigin set setIconOrigin
property iconSize get mIconSize set setIconSize
property animating get mIsAnimating set setIsAnimating
metadata iconSVGPath.editor is "com.livecode.pi.text"
metadata iconColor.editor is "com.livecode.pi.color"
metadata currentRotation.user_visble is "false"
metadata iconStyle.editor is "com.livecode.pi.enum"
metadata iconStyle.options is "circle notch,cog,pulse,spinner,refresh"
private handler setIconSize(in pSize as Number)
put pSize into mIconSize
initializationCalculations()
redraw all
end handler
private handler setIsAnimating(in pBoolean as Boolean)
if pBoolean is not mIsAnimating then
put pBoolean into mIsAnimating
cancel timer
if mIsAnimating then
schedule timer in mFrequency seconds -- add function to keep timer firing exactly right?
end if
end if
end handler
private handler setRotation(in pRotation as Number)
initializationCalculations()
calculateRotatedPath(pRotation)
redraw all
end handler
private handler setRotateBy(in pRotateBy as Number)
put pRotateBy into mRotateBy
initializationCalculations()
redraw all
end handler
private handler getIconPath() as String
return the instructions of mSVGPath
end handler
private handler setIconPath(in pPath as String)
put path pPath into mSVGPath
put "custom" into mIconStyle
initializationCalculations()
redraw all
end handler
private handler getColor() as String
return colorToString(mColor, false)
end handler
private handler setColor(in pColor as String)
put stringToColor(pColor) into mColor
redraw all
end handler
private handler getIconOrigin() as String
return (element 1 of mIconOrigin) formatted as string & "," & (element 2 of mIconOrigin) formatted as string
end handler
private handler setIconOrigin(in pPoint as String)
variable tList as List
split pPoint by "," into tList
put tList parsed as list of number into tList
if the number of elements in tList is 2 then
put tList into mIconOrigin
initializationCalculations()
redraw all
end if
end handler
private handler setIconStyle(in pStyle as String)
_setIconStyle(pStyle)
initializationCalculations()
redraw all
end handler
private handler _setIconStyle(in pStyle as String)
put 3 into mRotateBy
put 0.01 into mFrequency
put [0.50,0.50] into mIconOrigin
put pStyle into mIconStyle
if pStyle is "circle notch" then
put path "M355.8,176.1c0,48.3-20.3,94.1-52,125.9s-77.6,52-125.9,52S83.8,333.8,52,302S0,224.4,0,176.1C0,117.4,28.6,63.7,75.1,30.8 c23.2-16.5,49-26.6,77.4-30.8v51.6C93.9,63.5,50.8,115.2,50.8,176.1c0,34.4,14.7,67.1,37.3,89.8c22.6,22.6,55.4,37.3,89.8,37.3 s67.3-14.7,90-37.3c22.6-22.6,37.1-55.4,37.1-89.8c0-61-43.1-112.6-101.7-124.5V0c57,8.1,105.6,42.9,132.4,93.7 C349.1,119.3,355.8,146.7,355.8,176.1z" into mSVGPath
else if pStyle is "refresh" then
put path "M181.2,15.1V68c0,4.1-3.4,7.6-7.6,7.6h-52.9c-4.1,0-7.6-3.4-7.6-7.6c0-2,0.7-3.8,2.2-5.3l16.3-16.3 c-11.7-10.7-25.4-16.2-41.2-16.2c-21.1,0-40.5,10.9-51.4,28.8c-0.8,1.3-2.9,5.9-6.3,13.8c-0.6,1.8-1.8,2.7-3.5,2.7H5.9 c-2,0-3.8-1.8-3.8-3.8v-0.8C12.4,28.8,47.3,0,90.6,0c23,0,45.5,9.1,62.4,25l15.3-15.2c1.5-1.5,3.3-2.2,5.3-2.2 C177.8,7.6,181.2,11,181.2,15.1z M178.3,109.5c0,0.4,0,0.7-0.1,0.8c-10,42.1-44.9,70.9-88,70.9c-23,0-45.2-9-62.1-25l-15.2,15.2 c-1.5,1.5-3.3,2.2-5.3,2.2c-4.1,0-7.6-3.4-7.6-7.6v-52.9c0-4.1,3.4-7.6,7.6-7.6h52.9c4.1,0,7.6,3.4,7.6,7.6c0,2-0.7,3.8-2.2,5.3 l-16.2,16.2C60.8,145.1,75.4,151,90.6,151c21.1,0,40.5-10.9,51.4-28.8c0.8-1.3,2.9-5.9,6.3-13.8c0.6-1.8,1.8-2.7,3.5-2.7h22.7 C176.5,105.7,178.3,107.5,178.3,109.5z" into mSVGPath
else if pStyle is "cog" then
put path "M322.8,138.5v46.7c0,3.4-2.5,6.9-5.9,7.6l-38.9,5.9c-2.7,7.6-5.5,13.9-8.2,19.1c4.8,6.9,12.4,16.6,22.5,29 c1.5,1.7,2.1,3.4,2.1,5.3c0,1.9-0.6,3.4-1.9,4.8c-7.6,10.3-34.5,37.6-40.6,37.6c-1.7,0-3.6-0.6-5.5-1.9l-29-22.7 c-6.1,3.2-12.6,5.9-19.1,8c-2.3,19.1-4.2,32.2-6.1,39.1c-1.1,4-3.6,5.9-7.6,5.9h-46.7c-4,0-7.4-2.7-7.6-6.3l-5.9-38.7 c-6.9-2.3-13.2-4.8-18.9-7.8l-29.6,22.5c-1.5,1.3-3.2,1.9-5.3,1.9c-1.9,0-3.8-0.8-5.3-2.3c-17.7-16-29.2-27.7-34.7-35.3 c-1.1-1.5-1.5-2.9-1.5-4.8c0-1.7,0.6-3.4,1.7-4.8c2.1-2.9,5.7-7.6,10.7-14.1c5-6.3,8.8-11.1,11.3-14.7c-3.8-6.9-6.7-13.9-8.6-20.8 l-38.5-5.7c-3.6-0.6-6.1-4-6.1-7.6v-46.7c0-3.4,2.5-6.9,5.7-7.6l39.1-5.9c1.9-6.5,4.6-12.8,8.2-19.3c-5.7-8-13-17.7-22.5-29 c-1.5-1.7-2.1-3.4-2.1-5c0-1.5,0.6-2.9,1.9-4.8c7.4-10.1,34.5-37.6,40.6-37.6c1.9,0,3.6,0.6,5.5,2.1l29,22.5 c6.1-3.2,12.6-5.9,19.1-8c2.3-19.1,4.2-32.2,6.1-39.1c1.1-4,3.6-5.9,7.6-5.9h46.7c4,0,7.4,2.7,7.6,6.3l5.9,38.7 c6.9,2.3,13.2,4.8,18.9,7.8L247,30.3c1.3-1.3,2.9-1.9,5-1.9c1.9,0,3.6,0.6,5.3,2.1c18.1,16.6,29.6,28.6,34.7,35.7 c1.1,1.1,1.5,2.7,1.5,4.6c0,1.7-0.6,3.4-1.7,4.8c-2.1,2.9-5.7,7.6-10.7,13.9c-5,6.5-8.8,11.3-11.3,14.9c3.6,6.9,6.5,13.9,8.6,20.6 l38.5,5.9C320.3,131.6,322.8,134.9,322.8,138.5z M199.5,199.5c10.5-10.5,15.8-23.1,15.8-38c0-29.6-24.2-53.8-53.8-53.8 s-53.8,24.2-53.8,53.8s24.2,53.8,53.8,53.8C176.3,215.2,189,210,199.5,199.5z" into mSVGPath
else
put path "M297.3,76.2c0,23.7-19.3,42.9-42.9,42.9c-23.7,0-42.9-19.1-42.9-42.9c0-23.5,19.1-42.9,42.9-42.9 C278,33.3,297.3,52.6,297.3,76.2z M185.2,10.7c7.1,7.1,10.7,15.9,10.7,26c0,20.5-16.3,36.7-36.7,36.7s-36.7-16.3-36.7-36.7 S138.7,0,159.2,0C169.3,0,178.1,3.6,185.2,10.7z M311.3,154c4.8,4.8,7.1,10.7,7.1,17.4c0,13.6-10.9,24.5-24.5,24.5 c-13.6,0-24.5-10.9-24.5-24.5s10.9-24.5,24.5-24.5C300.6,146.9,306.5,149.2,311.3,154z M85.5,54.5c5.9,5.9,9,13.2,9,21.6 c0,16.8-13.8,30.6-30.6,30.6S33.3,93,33.3,76.2s13.8-30.6,30.6-30.6C72.3,45.5,79.6,48.6,85.5,54.5z M279,266.7 c0,13.2-11.3,24.5-24.5,24.5c-13.6,0-24.5-10.9-24.5-24.5c0-13.6,10.9-24.5,24.5-24.5S279,253.1,279,266.7z M41.9,154 c4.8,4.8,7.1,10.7,7.1,17.4c0,13.6-10.9,24.5-24.5,24.5S0,185,0,171.4s10.9-24.5,24.5-24.5C31.2,146.9,37.1,149.2,41.9,154z M176.6,288.7c4.8,4.8,7.1,10.7,7.1,17.4c0,13.6-10.9,24.5-24.5,24.5s-24.5-10.9-24.5-24.5s10.9-24.5,24.5-24.5 C165.9,281.6,171.8,283.9,176.6,288.7z M81.3,249.3c4.8,4.8,7.1,10.7,7.1,17.4c0,13.6-10.9,24.5-24.5,24.5 c-13.2,0-24.5-11.3-24.5-24.5c0-13.6,10.9-24.5,24.5-24.5C70.6,242.2,76.5,244.5,81.3,249.3z" into mSVGPath
put [0.50,0.52] into mIconOrigin
if pStyle is "spinner" then
put 3 into mRotateBy
put 0.01 into mFrequency
else -- pulse
put "pulse" into mIconStyle -- set style in case it was a bad type
put 45 into mRotateBy
put 0.125 into mFrequency
end if
end if
end handler
public handler OnCreate()
_setIconStyle("pulse")
put color [0/255, 0/255, 0/255] into mColor
put [0.50,0.52] into mIconOrigin
put path "" into mWorkingPath
put true into mIsAnimating
put 31 into mIconSize
put 0 into mRotation
end handler
public handler OnSave(out rProperties as Array)
put the empty array into rProperties
put colorToString(mColor, true) into rProperties["color"]
put mRotateBy into rProperties["rotateBy"]
put mFrequency into rProperties["frequency"]
put the instructions of mSVGPath into rProperties["svgPath"]
put mIconOrigin into rProperties["iconOrigin"]
put mIconSize into rProperties["iconSize"]
put mIconStyle into rProperties["iconStyle"]
put mIsAnimating into rProperties["animating"]
end handler
public handler OnLoad(in pProperties as Array)
put path pProperties["svgPath"] into mSVGPath
put stringToColor(pProperties["color"]) into mColor
put pProperties["rotateBy"] into mRotateBy
put pProperties["frequency"] into mFrequency
put pProperties["iconOrigin"] into mIconOrigin
put pProperties["iconSize"] into mIconSize
put pProperties["iconStyle"] into mIconStyle
put pProperties["animating"] into mIsAnimating
end handler
public handler OnOpen()
initializationCalculations()
if mIsAnimating then
schedule timer in mFrequency seconds
end if
end handler
public handler OnGeometryChanged()
initializationCalculations()
end handler
-- this message doesn't seem to be firing yet
public handler OnVisibilityChanged(in pVisible as Boolean)
setIsAnimating(pVisible)
end handler
private handler initializationCalculations() as undefined
-- Scale
transform mSVGPath by scaleAndMaintainAspectRatioTransform(the bounding box of mSVGPath, rectangle [0,0,mIconSize,mIconSize])
-- Reset to 0,0
translate mSVGPath by [- the x of the bounding box of mSVGPath, \
- the y of the bounding box of mSVGPath]
-- Center
variable tCenterX as Number
variable tCenterY as Number
put (the width of the bounding box of mSVGPath) * element 1 of mIconOrigin into tCenterX
put (the height of the bounding box of mSVGPath) * element 2 of mIconOrigin into tCenterY
translate mSVGPath by [my width/2-tCenterX, my height/2-tCenterY]
put point [(the left of the bounding box of mSVGPath) + tCenterX, \
(the top of the bounding box of mSVGPath) + tCenterY] into mCenter
put mSVGPath into mWorkingPath
put 0 into mRotation
end handler
----------
-- called whenever LiveCode needs to redraw the widget
public handler OnPaint()
set the paint of this canvas to solid paint with mColor
fill mWorkingPath on this canvas
end handler
public handler OnClose()
cancel timer
end handler
-- Very useful article: http://docs.rainmeter.net/tips/transformation-matrix-guide
private handler calculateRotatedPath(in pRotation as Number)
variable tX as Number
variable tY as Number
variable tOriginX as Number
variable tOriginY as Number
variable tMatrix as List
variable tA as Number
variable tB as Number
variable tC as Number
variable tD as Number
-- Clockwise: sin for B, -sin for C
-- Counterclockwise: -sin for B, sin for C
variable tAngle as Number
put (pRotation mod 360)/360*2*pi into tAngle
put cos(tAngle) into tA -- scale the x
put sin(tAngle) into tB -- scale the y
put -sin(tAngle) into tC -- skew the x
put cos(tAngle) into tD -- skew the y
put the x of mCenter into tOriginX
put the y of mCenter into tOriginY
put tOriginX - tOriginX*tA - tOriginY*tC into tX
put tOriginY - tOriginX*tB - tOriginY*tD into tY
variable tTransform as Transform
put transform with matrix [tA,tB,tC,tD,tX,tY] into tTransform
put mSVGPath into mWorkingPath
transform mWorkingPath by tTransform
end handler
----------
-- this handler is called when the timer scheduled with 'schedule timer' fires
public handler OnTimer()
put (mRotation + mRotateBy) mod 360 into mRotation
calculateRotatedPath(mRotation)
redraw all
if mIsAnimating then
--cancel timer
schedule timer in mFrequency seconds -- add function to keep timer firing exactly right?
end if
end handler
----------
-- this handler as the amount of time until the seconds change
handler computeNextTimer() as Number
-- use the 'execute script' command
--execute script "return 1 - (the long seconds - round(the long seconds - 0.5))"
--return the result
end handler
-- Translated from some Skia code
private handler scaleAndMaintainAspectRatioTransform(in pSrcBounds as Rectangle, in pDestBounds as Rectangle) as Transform
// Prepare values for matrix transformation
variable isLarger as Boolean
variable sX as Number
variable sY as Number
put false into isLarger
put the width of pDestBounds / the width of pSrcBounds into sX
put the height of pDestBounds / the height of pSrcBounds into sY
if sX > sY then
put true into isLarger
put sY into sX
else
put sX into sY
end if
variable tX as Number
variable tY as Number
put the left of pDestBounds - (the left of pSrcBounds*sX) into tX
put the top of pDestBounds - (the top of pSrcBounds*sY) into tY
variable tDiff as Number
if isLarger then
put my width - (the width of pSrcBounds*sY) into tDiff
else
put my height - (the height of pSrcBounds*sY) into tDiff
end if
// align center
divide tDiff by 2
if isLarger then
add tDiff to tX
else
add tDiff to tY
end if
// create transformation matrix and apply
variable tTransform as Transform
put transform with matrix [sX, 0, 0, sY, tX, tY] into tTransform
return tTransform
end handler
-- Needed until dp-2
handler FormatInt(in pNumber as Number) as String
variable tNumberString as String
put pNumber formatted as string into tNumberString
if "." is in tNumberString then
variable tDotOffset
put the first offset of "." in tNumberString into tDotOffset
delete char tDotOffset to (the number of chars in tNumberString) of tNumberString
end if
return tNumberString
end handler
private handler colorToString(in pColor as Color, in pIncludeAlpha as Boolean) as String
variable tColor as String
put FormatInt(the rounded of ((the red of pColor) * 255)) into tColor
put "," & FormatInt(the rounded of ((the green of pColor) * 255)) after tColor
put "," & FormatInt(the rounded of ((the blue of pColor) * 255)) after tColor
if pIncludeAlpha then
put "," & FormatInt(the rounded of ((the alpha of pColor) * 255)) after tColor
end if
return tColor
end handler
----------
-- this handler converts a String of numbers to an RGBA color
private handler stringToColor(in pString as String) as Color
variable tRed as Real
variable tGreen as Real
variable tBlue as Real
variable tAlpha as Real
variable tComponentList as List
split pString by "," into tComponentList
variable tComponentCount
put the number of elements in tComponentList into tComponentCount
if tComponentCount is not 3 and tComponentCount is not 4 then
// Invalid number of components detected
throw "Invalid color"
end if
put (element 1 of tComponentList) parsed as number into tRed
put (element 2 of tComponentList) parsed as number into tGreen
put (element 3 of tComponentList) parsed as number into tBlue
if tComponentCount is 4 then
put (element 4 of tComponentList) parsed as number into tAlpha
else
put 255 into tAlpha
end if
return color [ tRed/255, tGreen/255, tBlue/255, tAlpha/255 ]
end handler
end widget