touch-bar.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. 'use strict'
  2. const { EventEmitter } = require('events')
  3. let nextItemID = 1
  4. class TouchBar extends EventEmitter {
  5. // Bind a touch bar to a window
  6. static _setOnWindow (touchBar, window) {
  7. if (window._touchBar != null) {
  8. window._touchBar._removeFromWindow(window)
  9. }
  10. if (touchBar == null) {
  11. window._setTouchBarItems([])
  12. return
  13. }
  14. if (Array.isArray(touchBar)) {
  15. touchBar = new TouchBar(touchBar)
  16. }
  17. touchBar._addToWindow(window)
  18. }
  19. constructor (options) {
  20. super()
  21. if (options == null) {
  22. throw new Error('Must specify options object as first argument')
  23. }
  24. let { items, escapeItem } = options
  25. if (!Array.isArray(items)) {
  26. items = []
  27. }
  28. this.changeListener = (item) => {
  29. this.emit('change', item.id, item.type)
  30. }
  31. this.windowListeners = {}
  32. this.items = {}
  33. this.ordereredItems = []
  34. this.escapeItem = escapeItem
  35. const registerItem = (item) => {
  36. this.items[item.id] = item
  37. item.on('change', this.changeListener)
  38. if (item.child instanceof TouchBar) {
  39. item.child.ordereredItems.forEach(registerItem)
  40. }
  41. }
  42. items.forEach((item) => {
  43. if (!(item instanceof TouchBarItem)) {
  44. throw new Error('Each item must be an instance of TouchBarItem')
  45. }
  46. this.ordereredItems.push(item)
  47. registerItem(item)
  48. })
  49. }
  50. set escapeItem (item) {
  51. if (item != null && !(item instanceof TouchBarItem)) {
  52. throw new Error('Escape item must be an instance of TouchBarItem')
  53. }
  54. if (this.escapeItem != null) {
  55. this.escapeItem.removeListener('change', this.changeListener)
  56. }
  57. this._escapeItem = item
  58. if (this.escapeItem != null) {
  59. this.escapeItem.on('change', this.changeListener)
  60. }
  61. this.emit('escape-item-change', item)
  62. }
  63. get escapeItem () {
  64. return this._escapeItem
  65. }
  66. _addToWindow (window) {
  67. const { id } = window
  68. // Already added to window
  69. if (this.windowListeners.hasOwnProperty(id)) return
  70. window._touchBar = this
  71. const changeListener = (itemID) => {
  72. window._refreshTouchBarItem(itemID)
  73. }
  74. this.on('change', changeListener)
  75. const escapeItemListener = (item) => {
  76. window._setEscapeTouchBarItem(item != null ? item : {})
  77. }
  78. this.on('escape-item-change', escapeItemListener)
  79. const interactionListener = (event, itemID, details) => {
  80. let item = this.items[itemID]
  81. if (item == null && this.escapeItem != null && this.escapeItem.id === itemID) {
  82. item = this.escapeItem
  83. }
  84. if (item != null && item.onInteraction != null) {
  85. item.onInteraction(details)
  86. }
  87. }
  88. window.on('-touch-bar-interaction', interactionListener)
  89. const removeListeners = () => {
  90. this.removeListener('change', changeListener)
  91. this.removeListener('escape-item-change', escapeItemListener)
  92. window.removeListener('-touch-bar-interaction', interactionListener)
  93. window.removeListener('closed', removeListeners)
  94. window._touchBar = null
  95. delete this.windowListeners[id]
  96. const unregisterItems = (items) => {
  97. for (const item of items) {
  98. item.removeListener('change', this.changeListener)
  99. if (item.child instanceof TouchBar) {
  100. unregisterItems(item.child.ordereredItems)
  101. }
  102. }
  103. }
  104. unregisterItems(this.ordereredItems)
  105. if (this.escapeItem) {
  106. this.escapeItem.removeListener('change', this.changeListener)
  107. }
  108. }
  109. window.once('closed', removeListeners)
  110. this.windowListeners[id] = removeListeners
  111. window._setTouchBarItems(this.ordereredItems)
  112. escapeItemListener(this.escapeItem)
  113. }
  114. _removeFromWindow (window) {
  115. const removeListeners = this.windowListeners[window.id]
  116. if (removeListeners != null) removeListeners()
  117. }
  118. }
  119. class TouchBarItem extends EventEmitter {
  120. constructor () {
  121. super()
  122. this._addImmutableProperty('id', `${nextItemID++}`)
  123. this._parents = []
  124. }
  125. _addImmutableProperty (name, value) {
  126. Object.defineProperty(this, name, {
  127. get: function () {
  128. return value
  129. },
  130. set: function () {
  131. throw new Error(`Cannot override property ${name}`)
  132. },
  133. enumerable: true,
  134. configurable: false
  135. })
  136. }
  137. _addLiveProperty (name, initialValue) {
  138. const privateName = `_${name}`
  139. this[privateName] = initialValue
  140. Object.defineProperty(this, name, {
  141. get: function () {
  142. return this[privateName]
  143. },
  144. set: function (value) {
  145. this[privateName] = value
  146. this.emit('change', this)
  147. },
  148. enumerable: true
  149. })
  150. }
  151. _addParent (item) {
  152. const existing = this._parents.some(test => test.id === item.id)
  153. if (!existing) {
  154. this._parents.push({
  155. id: item.id,
  156. type: item.type
  157. })
  158. }
  159. }
  160. }
  161. TouchBar.TouchBarButton = class TouchBarButton extends TouchBarItem {
  162. constructor (config) {
  163. super()
  164. if (config == null) config = {}
  165. this._addImmutableProperty('type', 'button')
  166. const { click, icon, iconPosition, label, backgroundColor } = config
  167. this._addLiveProperty('label', label)
  168. this._addLiveProperty('backgroundColor', backgroundColor)
  169. this._addLiveProperty('icon', icon)
  170. this._addLiveProperty('iconPosition', iconPosition)
  171. if (typeof click === 'function') {
  172. this._addImmutableProperty('onInteraction', () => {
  173. config.click()
  174. })
  175. }
  176. }
  177. }
  178. TouchBar.TouchBarColorPicker = class TouchBarColorPicker extends TouchBarItem {
  179. constructor (config) {
  180. super()
  181. if (config == null) config = {}
  182. this._addImmutableProperty('type', 'colorpicker')
  183. const { availableColors, change, selectedColor } = config
  184. this._addLiveProperty('availableColors', availableColors)
  185. this._addLiveProperty('selectedColor', selectedColor)
  186. if (typeof change === 'function') {
  187. this._addImmutableProperty('onInteraction', (details) => {
  188. this._selectedColor = details.color
  189. change(details.color)
  190. })
  191. }
  192. }
  193. }
  194. TouchBar.TouchBarGroup = class TouchBarGroup extends TouchBarItem {
  195. constructor (config) {
  196. super()
  197. if (config == null) config = {}
  198. this._addImmutableProperty('type', 'group')
  199. const defaultChild = (config.items instanceof TouchBar) ? config.items : new TouchBar(config.items)
  200. this._addLiveProperty('child', defaultChild)
  201. this.child.ordereredItems.forEach((item) => item._addParent(this))
  202. }
  203. }
  204. TouchBar.TouchBarLabel = class TouchBarLabel extends TouchBarItem {
  205. constructor (config) {
  206. super()
  207. if (config == null) config = {}
  208. this._addImmutableProperty('type', 'label')
  209. this._addLiveProperty('label', config.label)
  210. this._addLiveProperty('textColor', config.textColor)
  211. }
  212. }
  213. TouchBar.TouchBarPopover = class TouchBarPopover extends TouchBarItem {
  214. constructor (config) {
  215. super()
  216. if (config == null) config = {}
  217. this._addImmutableProperty('type', 'popover')
  218. this._addLiveProperty('label', config.label)
  219. this._addLiveProperty('icon', config.icon)
  220. this._addLiveProperty('showCloseButton', config.showCloseButton)
  221. const defaultChild = (config.items instanceof TouchBar) ? config.items : new TouchBar(config.items)
  222. this._addLiveProperty('child', defaultChild)
  223. this.child.ordereredItems.forEach((item) => item._addParent(this))
  224. }
  225. }
  226. TouchBar.TouchBarSlider = class TouchBarSlider extends TouchBarItem {
  227. constructor (config) {
  228. super()
  229. if (config == null) config = {}
  230. this._addImmutableProperty('type', 'slider')
  231. const { change, label, minValue, maxValue, value } = config
  232. this._addLiveProperty('label', label)
  233. this._addLiveProperty('minValue', minValue)
  234. this._addLiveProperty('maxValue', maxValue)
  235. this._addLiveProperty('value', value)
  236. if (typeof change === 'function') {
  237. this._addImmutableProperty('onInteraction', (details) => {
  238. this._value = details.value
  239. change(details.value)
  240. })
  241. }
  242. }
  243. }
  244. TouchBar.TouchBarSpacer = class TouchBarSpacer extends TouchBarItem {
  245. constructor (config) {
  246. super()
  247. if (config == null) config = {}
  248. this._addImmutableProperty('type', 'spacer')
  249. this._addImmutableProperty('size', config.size)
  250. }
  251. }
  252. TouchBar.TouchBarSegmentedControl = class TouchBarSegmentedControl extends TouchBarItem {
  253. constructor (config) {
  254. super()
  255. if (config == null) config = {}
  256. const { segmentStyle, segments, selectedIndex, change, mode } = config
  257. this._addImmutableProperty('type', 'segmented_control')
  258. this._addLiveProperty('segmentStyle', segmentStyle)
  259. this._addLiveProperty('segments', segments || [])
  260. this._addLiveProperty('selectedIndex', selectedIndex)
  261. this._addLiveProperty('mode', mode)
  262. if (typeof change === 'function') {
  263. this._addImmutableProperty('onInteraction', (details) => {
  264. this._selectedIndex = details.selectedIndex
  265. change(details.selectedIndex, details.isSelected)
  266. })
  267. }
  268. }
  269. }
  270. TouchBar.TouchBarScrubber = class TouchBarScrubber extends TouchBarItem {
  271. constructor (config) {
  272. super()
  273. if (config == null) config = {}
  274. const { items, selectedStyle, overlayStyle, showArrowButtons, continuous, mode } = config
  275. let { select, highlight } = config
  276. this._addImmutableProperty('type', 'scrubber')
  277. this._addLiveProperty('items', items)
  278. this._addLiveProperty('selectedStyle', selectedStyle || null)
  279. this._addLiveProperty('overlayStyle', overlayStyle || null)
  280. this._addLiveProperty('showArrowButtons', showArrowButtons || false)
  281. this._addLiveProperty('mode', mode || 'free')
  282. this._addLiveProperty('continuous', typeof continuous === 'undefined' ? true : continuous)
  283. if (typeof select === 'function' || typeof highlight === 'function') {
  284. if (select == null) select = () => {}
  285. if (highlight == null) highlight = () => {}
  286. this._addImmutableProperty('onInteraction', (details) => {
  287. if (details.type === 'select' && typeof select === 'function') {
  288. select(details.selectedIndex)
  289. } else if (details.type === 'highlight' && typeof highlight === 'function') {
  290. highlight(details.highlightedIndex)
  291. }
  292. })
  293. }
  294. }
  295. }
  296. module.exports = TouchBar