UNPKG

8.9 kBJavaScriptView Raw
1/*
2 Terminal Kit
3
4 Copyright (c) 2009 - 2020 Cédric Ronvel
5
6 The MIT License (MIT)
7
8 Permission is hereby granted, free of charge, to any person obtaining a copy
9 of this software and associated documentation files (the "Software"), to deal
10 in the Software without restriction, including without limitation the rights
11 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 copies of the Software, and to permit persons to whom the Software is
13 furnished to do so, subject to the following conditions:
14
15 The above copyright notice and this permission notice shall be included in all
16 copies or substantial portions of the Software.
17
18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 SOFTWARE.
25*/
26
27"use strict" ;
28
29
30
31const Element = require( './Element.js' ) ;
32const Button = require( './Button.js' ) ;
33
34// Default transfer function
35const IDENTITY = v => v ;
36
37
38
39function Slider( options ) {
40 // Clone options if necessary
41 options = ! options ? {} : options.internal ? options : Object.create( options ) ;
42 options.internal = true ;
43
44 Element.call( this , options ) ;
45
46 this.onClick = this.onClick.bind( this ) ;
47 this.onDrag = this.onDrag.bind( this ) ;
48 this.onWheel = this.onWheel.bind( this ) ;
49 this.onButtonSubmit = this.onButtonSubmit.bind( this ) ;
50
51 this.isVertical = !! options.isVertical ;
52 this.slideRate = 0 ;
53 this.handleOffset = 0 ;
54
55 this.rateToValue = typeof options.rateToValue === 'function' ? options.rateToValue : IDENTITY ;
56 this.valueToRate = typeof options.valueToRate === 'function' ? options.valueToRate : IDENTITY ;
57
58 this.buttonBlurAttr = options.buttonBlurAttr || { bgColor: 'black' , color: 'white' , bold: true } ;
59 this.buttonFocusAttr = options.buttonFocusAttr || { bgColor: 'white' , color: 'black' , bold: true } ;
60 this.buttonSubmittedAttr = options.buttonSubmittedAttr || { bgColor: 'gray' , color: 'brightWhite' , bold: true } ;
61
62 this.backwardSymbol = options.backwardSymbol || ( this.isVertical ? '▲' : '◀' ) ;
63 this.forwardSymbol = options.forwardSymbol || ( this.isVertical ? '▼' : '▶' ) ;
64
65 this.handleAttr = options.handleAttr || { bgColor: 'brightWhite' , color: 'black' } ;
66 this.handleSymbol = options.handleSymbol || '◆' ;
67
68 this.barAttr = options.barAttr || { bgColor: 'gray' , color: 'brightWhite' } ;
69 this.barSymbol = options.barSymbol || ' ' ;
70
71 this.backwardButton = this.forwardButton = null ;
72
73 this.on( 'click' , this.onClick ) ;
74 this.on( 'drag' , this.onDrag ) ;
75 this.on( 'wheel' , this.onWheel ) ;
76
77 this.initChildren() ;
78
79 // Only draw if we are not a superclass of the object
80 if ( this.elementType === 'Slider' && ! options.noDraw ) { this.draw() ; }
81}
82
83module.exports = Slider ;
84
85Slider.prototype = Object.create( Element.prototype ) ;
86Slider.prototype.constructor = Slider ;
87Slider.prototype.elementType = 'Slider' ;
88
89
90
91// Unused ATM: no onKey registered
92Slider.prototype.keyBindings = {
93 UP: 'backward' ,
94 DOWN: 'forward' ,
95 LEFT: 'backward' ,
96 RIGHT: 'forward' ,
97 PAGE_UP: 'backward' ,
98 PAGE_DOWN: 'forward' ,
99 ' ': 'forward' ,
100 HOME: 'start' ,
101 END: 'end'
102} ;
103
104
105
106Slider.prototype.buttonKeyBindings = {
107 ENTER: 'submit' ,
108 KP_ENTER: 'submit'
109} ;
110
111
112
113Slider.prototype.destroy = function( isSubDestroy ) {
114 this.off( 'click' , this.onClick ) ;
115 this.off( 'drag' , this.onDrag ) ;
116 this.off( 'wheel' , this.onWheel ) ;
117 Element.prototype.destroy.call( this , isSubDestroy ) ;
118} ;
119
120
121
122// Create Buttons automatically
123Slider.prototype.initChildren = function() {
124 this.backwardButton = new Button( {
125 internal: true ,
126 parent: this ,
127 internalRole: 'backward' ,
128 content: this.backwardSymbol ,
129 outputX: this.outputX ,
130 outputY: this.outputY ,
131 blurAttr: this.buttonBlurAttr ,
132 focusAttr: this.buttonFocusAttr ,
133 //disabledAttr: this.buttonDisabledAttr ,
134 submittedAttr: this.buttonSubmittedAttr ,
135 keyBindings: this.buttonKeyBindings ,
136 //shortcuts: def.shortcuts ,
137 noDraw: true
138 } ) ;
139
140 this.backwardButton.on( 'submit' , this.onButtonSubmit ) ;
141
142 this.forwardButton = new Button( {
143 internal: true ,
144 parent: this ,
145 internalRole: 'forward' ,
146 content: this.forwardSymbol ,
147 outputX: this.isVertical ? this.outputX : this.outputX + this.outputWidth - 1 ,
148 outputY: this.isVertical ? this.outputY + this.outputHeight - 1 : this.outputY ,
149 blurAttr: this.buttonBlurAttr ,
150 focusAttr: this.buttonFocusAttr ,
151 //disabledAttr: this.buttonDisabledAttr ,
152 submittedAttr: this.buttonSubmittedAttr ,
153 keyBindings: this.buttonKeyBindings ,
154 //shortcuts: def.shortcuts ,
155 noDraw: true
156 } ) ;
157
158 this.forwardButton.on( 'submit' , this.onButtonSubmit ) ;
159
160 this.computeHandleOffset() ;
161} ;
162
163
164
165Slider.prototype.preDrawSelf = function() {
166 return this.isVertical ? this.preDrawSelfVertical() : this.preDrawSelfHorizontal() ;
167} ;
168
169
170
171Slider.prototype.preDrawSelfVertical = function() {
172 var offset = 0 ,
173 y = this.outputY + 1 ,
174 yMax = this.outputY + this.outputHeight - 2 ;
175
176 for ( ; y <= yMax ; y ++ , offset ++ ) {
177 if ( offset === this.handleOffset ) {
178 this.outputDst.put( { x: this.outputX , y , attr: this.handleAttr } , this.handleSymbol ) ;
179 }
180 else {
181 this.outputDst.put( { x: this.outputX , y , attr: this.barAttr } , this.barSymbol ) ;
182 }
183 }
184} ;
185
186
187
188Slider.prototype.preDrawSelfHorizontal = function() {
189 var offset = 0 ,
190 x = this.outputX + 1 ,
191 xMax = this.outputX + this.outputWidth - 2 ;
192
193 for ( ; x <= xMax ; x ++ , offset ++ ) {
194 if ( offset === this.handleOffset ) {
195 this.outputDst.put( { x , y: this.outputY , attr: this.handleAttr } , this.handleSymbol ) ;
196 }
197 else {
198 this.outputDst.put( { x , y: this.outputY , attr: this.barAttr } , this.barSymbol ) ;
199 }
200 }
201} ;
202
203
204
205// No need to draw the cursor, however, when drawing, it is necessary to move it (not draw) at the handle position,
206// in case no other widget would draw the cursor, it avoid the cursor to be hanging at the bottom and one cell off
207// to the right of the slider, which is pretty annoying...
208Slider.prototype.postDrawSelf = function() {
209 if ( this.isVertical ) {
210 this.outputDst.moveTo( this.outputX , this.outputY + this.handleOffset + 1 ) ;
211 }
212 else {
213 this.outputDst.moveTo( this.outputX + this.handleOffset + 1 , this.outputY ) ;
214 }
215} ;
216
217/*
218Slider.prototype.drawSelfCursor = function() {
219 // Move the cursor back to the handle
220 this.outputDst.moveTo( this.outputX , this.outputY + this.handleOffset + 1 ) ;
221 this.outputDst.drawCursor() ;
222} ;
223*/
224
225
226
227// Compute the handle y position from the slideRate value
228Slider.prototype.computeHandleOffset = function() {
229 var delta = ( this.isVertical ? this.outputHeight : this.outputWidth ) - 3 ; // minus the two buttons
230 this.handleOffset = Math.round( delta * this.slideRate ) ;
231} ;
232
233
234
235// Set the handle position and compute the slideRate
236Slider.prototype.setHandleOffset = function( offset , internalAndNoDraw = false ) {
237 var delta = ( this.isVertical ? this.outputHeight : this.outputWidth ) - 3 ; // minus the two buttons
238
239 this.handleOffset = Math.max( 0 , Math.min( delta , Math.round( offset || 0 ) ) ) ;
240 this.slideRate = Math.max( 0 , Math.min( 1 , this.handleOffset / delta || 0 ) ) ;
241
242 if ( ! internalAndNoDraw ) {
243 this.emit( 'slide' , this.getValue() ) ;
244 this.draw() ;
245 }
246} ;
247
248
249
250Slider.prototype.setSlideRate = function( rate , internalAndNoDraw = false ) {
251 this.slideRate = Math.max( 0 , Math.min( 1 , rate || 0 ) ) ;
252 this.computeHandleOffset() ;
253
254 if ( ! internalAndNoDraw ) {
255 this.emit( 'slide' , this.getValue() ) ;
256 this.draw() ;
257 }
258} ;
259
260
261
262Slider.prototype.getHandleOffset = function() { return this.handleOffset ; } ;
263Slider.prototype.getSlideRate = function() { return this.slideRate ; } ;
264
265
266
267Slider.prototype.onButtonSubmit = function( buttonValue , action , button ) {
268 switch ( button.internalRole ) {
269 case 'backward' :
270 this.emit( 'slideStep' , -1 ) ;
271 break ;
272 case 'forward' :
273 this.emit( 'slideStep' , 1 ) ;
274 break ;
275 }
276} ;
277
278
279
280Slider.prototype.getValue = function() {
281 return this.rateToValue( this.slideRate ) ;
282} ;
283
284
285
286Slider.prototype.setValue = function( value , internalAndNoDraw ) {
287 return this.setSlideRate( this.valueToRate( value ) , internalAndNoDraw ) ;
288} ;
289
290
291
292Slider.prototype.onClick = function( data ) {
293 if ( ! this.hasFocus ) { this.document.giveFocusTo( this , 'select' ) ; }
294 this.setHandleOffset( ( this.isVertical ? data.y : data.x ) - 1 ) ;
295} ;
296
297
298
299Slider.prototype.onDrag = function( data ) {
300 this.setHandleOffset( ( this.isVertical ? data.y : data.x ) - 1 ) ;
301} ;
302
303
304
305Slider.prototype.onWheel = function( data ) {
306 this.emit( 'slideStep' , data.yDirection ) ;
307} ;
308