Advertisement
Guest User

Untitled

a guest
Nov 27th, 2019
28
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 9.02 KB | None | 0 0
  1. /*
  2. GitHub - exyte/fan-menu: Menu with a circular layout based on Macaw
  3. https://github.com/exyte/fan-menu
  4. We need to update this file ourselves because we had to customize the menu.
  5. */
  6.  
  7. import Foundation
  8. import Macaw
  9.  
  10. public struct FanMenuButton {
  11. public let id: String
  12. public let image: String
  13. public let color: Color
  14.  
  15. public init(id: String, image: String, color: Color) {
  16. self.id = id
  17. self.image = image
  18. self.color = color
  19. }
  20.  
  21. }
  22.  
  23. public class FanMenu: MacawView {
  24.  
  25. public var duration = 0.20 {
  26. didSet {
  27. updateNode()
  28. }
  29. }
  30.  
  31. public var delay = 0.05 {
  32. didSet {
  33. updateNode()
  34. }
  35. }
  36.  
  37. public var menuRadius = 95.0 {
  38. didSet {
  39. updateNode()
  40. }
  41. }
  42.  
  43. public var radius = 30.0 {
  44. didSet {
  45. updateNode()
  46. }
  47. }
  48.  
  49. public var button: FanMenuButton? {
  50. didSet {
  51. updateNode()
  52. }
  53. }
  54.  
  55. public var items: [FanMenuButton] = [] {
  56. didSet {
  57. updateNode()
  58. }
  59. }
  60.  
  61. public var interval: (Double, Double) = (0, 2.0 * .pi) {
  62. didSet {
  63. updateNode()
  64. }
  65. }
  66.  
  67. public var menuBackground: Color? {
  68. didSet {
  69. updateNode()
  70. }
  71. }
  72.  
  73. public var onItemWillClick: ((_ button: FanMenuButton) -> ())?
  74. public var onItemDidClick: ((_ button: FanMenuButton) -> ())?
  75.  
  76. var scene: FanMenuScene?
  77.  
  78. public var isOpen: Bool {
  79. get {
  80. if let sceneValue = scene {
  81. return sceneValue.isOpen
  82. }
  83. return false
  84. }
  85. }
  86.  
  87. public func open() {
  88. scene?.updateMenu(open: true)
  89. }
  90.  
  91. public func close() {
  92. scene?.updateMenu(open: false)
  93. }
  94.  
  95. public func updateNode() {
  96. guard let _ = button else {
  97. self.node = Macaw.Group()
  98. self.scene = .none
  99. return
  100. }
  101.  
  102. let scene = FanMenuScene(fanMenu: self)
  103. let node = scene.node
  104. node.place = Transform.move(
  105. dx: Double(self.frame.width) / 2,
  106. dy: Double(self.frame.height) / 2
  107. )
  108. self.node = node
  109. self.scene = scene
  110. }
  111.  
  112. open override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
  113. return findNodeAt(location: point) != nil
  114. }
  115.  
  116. fileprivate var sizeIsTooSmall: Bool {
  117. let minSize = CGFloat((menuRadius + radius) * 2.0)
  118. return bounds.size.width < minSize || bounds.size.height < minSize
  119. }
  120.  
  121. }
  122.  
  123. class FanMenuScene {
  124.  
  125. let fanMenu: FanMenu
  126.  
  127. let buttonNode: Macaw.Group
  128. let buttonsNode: Macaw.Group
  129. let backgroundCircle: Macaw.Shape
  130.  
  131. let menuCircle: Macaw.Shape
  132. let menuIcon: Image?
  133.  
  134. let node: Macaw.Group
  135.  
  136. init(fanMenu: FanMenu) {
  137. self.fanMenu = fanMenu
  138. let button = fanMenu.button!
  139.  
  140. menuCircle = Shape(
  141. form: Circle(r: fanMenu.radius),
  142. fill: button.color
  143. )
  144.  
  145. buttonNode = [menuCircle].group()
  146. if let uiImage = UIImage(named: button.image) {
  147. menuIcon = Image(
  148. src: button.image,
  149. place: Transform.move(
  150. dx: -Double(uiImage.size.width) / 2,
  151. dy: -Double(uiImage.size.height) / 2
  152. )
  153. )
  154. buttonNode.contents.append(menuIcon!)
  155. } else {
  156. menuIcon = .none
  157. }
  158.  
  159. buttonsNode = fanMenu.items.map {
  160. return FanMenuScene.createFanButtonNode(button: $0, fanMenu: fanMenu)
  161. }.group()
  162.  
  163.  
  164. backgroundCircle = Shape(
  165. form: Circle(r: fanMenu.radius - 1)
  166. )
  167.  
  168. if let color = fanMenu.menuBackground {
  169. backgroundCircle.fill = Color.clear
  170. backgroundCircle.stroke = Macaw.Stroke(fill: color, width: 0.7, cap: .round, join: .miter, dashes: [])
  171. } else {
  172. backgroundCircle.fill = Color.clear
  173. backgroundCircle.stroke = Macaw.Stroke(fill: button.color, width: 0.7, cap: .round, join: .miter, dashes: [])
  174. }
  175.  
  176. node = [backgroundCircle, buttonsNode].group()
  177.  
  178. buttonNode.onTouchPressed { _ in
  179. if let animationValue = self.animation {
  180. if animationValue.state() != .paused {
  181. return
  182. }
  183. }
  184. self.updateMenu(open: !self.isOpen)
  185. }
  186. }
  187.  
  188. var animation: Animation?
  189. var isOpen: Bool = false
  190.  
  191. func updateMenu(open: Bool) {
  192. if let button = fanMenu.button {
  193. self.fanMenu.onItemWillClick?(button)
  194.  
  195. self.updateState(open: open, buttonId: button.id) {
  196. self.fanMenu.onItemDidClick?(button)
  197. }
  198. }
  199. }
  200.  
  201. func updateState(open: Bool, buttonId: String, callback: @escaping () -> Void) {
  202. if(buttonId == "rotateMarker") {
  203. callback()
  204. return
  205. }
  206. if(buttonId == "changeStatus"){
  207. callback()
  208. return
  209. }
  210.  
  211. if(buttonId == "deleteDocumentation"){
  212. callback()
  213. return
  214. }
  215.  
  216. if(buttonId == "copyDocumentation"){
  217. callback()
  218. return
  219.  
  220. }
  221.  
  222. if open == isOpen {
  223. return
  224. }
  225.  
  226. isOpen = open
  227.  
  228. let scale = isOpen ? fanMenu.menuRadius / fanMenu.radius : fanMenu.radius / fanMenu.menuRadius
  229. let backgroundAnimation = self.backgroundCircle.placeVar.animation(
  230. to: Transform.scale(sx: scale, sy: scale),
  231. during: fanMenu.duration
  232. )
  233.  
  234. let nodes = self.buttonsNode.contents.enumerated()
  235. let expandAnimation = nodes.map { (index, node) in
  236. let transform = isOpen ? self.placeButtonsOnCircle(index: index) : Transform.identity
  237. let mainAnimation = [
  238. node.opacityVar.animation(to: isOpen ? 1.0 : 0.0, during: fanMenu.duration),
  239. node.placeVar.animation(
  240. to: transform,
  241. during: fanMenu.duration
  242. ).easing(Easing.easeOut)
  243. ].combine()
  244.  
  245. let delay = fanMenu.delay * Double(index)
  246. if delay == 0.0 {
  247. return mainAnimation
  248. }
  249.  
  250. let filterOpacity = isOpen ? 0.0 : 1.0
  251. let fillerAnimation = node.opacityVar.animation(from: filterOpacity, to: filterOpacity, during: delay)
  252. return [fillerAnimation, mainAnimation].sequence()
  253. }.combine()
  254.  
  255. // stub
  256. let buttonAnimation = self.buttonNode.opacityVar.animation(
  257. to: 1.0,
  258. during: fanMenu.duration + fanMenu.delay * Double(buttonsNode.contents.count - 1)
  259. )
  260.  
  261. animation = [backgroundAnimation, expandAnimation, buttonAnimation].combine()
  262. animation?.onComplete {
  263. callback()
  264. }
  265. animation?.play()
  266. }
  267.  
  268.  
  269. class func createFanButtonNode(button: FanMenuButton, fanMenu: FanMenu) -> Macaw.Group {
  270. var contents: [Node] = [
  271. Shape(
  272. form: Circle(r: fanMenu.radius),
  273. fill: button.color
  274. )
  275. ]
  276. if let uiImage = UIImage(named: button.image) {
  277. let image = Image(
  278. src: button.image,
  279. place: Transform.move(
  280. dx: -Double(uiImage.size.width) / 2,
  281. dy: -Double(uiImage.size.height) / 2
  282. )
  283. )
  284. contents.append(image)
  285. }
  286. let node = Macaw.Group(contents: contents)
  287. node.opacity = 0.0
  288.  
  289. node.onTouchPressed { _ in
  290. fanMenu.onItemWillClick?(button)
  291.  
  292. fanMenu.scene?.updateState(open: false, buttonId: button.id) {
  293. fanMenu.onItemDidClick?(button)
  294. }
  295. }
  296. return node
  297. }
  298.  
  299. func placeButtonsOnCircle(index: Int) -> Transform {
  300. let size = Double(buttonsNode.contents.count)
  301. let endValue = fanMenu.interval.1
  302. let startValue = fanMenu.interval.0
  303. let interval = endValue - startValue
  304.  
  305. var step: Double = 1.0
  306. if interval.truncatingRemainder(dividingBy: 2 * Double.pi) < 0.00001 {
  307. // full circle
  308. step = interval / size
  309. } else {
  310. step = interval / (size - 1)
  311. }
  312.  
  313. // First and last button of the fanMenu
  314. let firstBtn = fanMenu.items.first
  315. let lastBtn = fanMenu.items.last
  316.  
  317. let alpha = startValue + step * Double(index + 1)
  318. return Transform.move(
  319. // Place the button on the x-axis
  320. dx: cos(alpha) * fanMenu.menuRadius,
  321. // Place the buttons on the y-axis
  322. dy: sin(alpha) * fanMenu.menuRadius
  323. )
  324. }
  325. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement