]> git.sesse.net Git - vlc/blob - modules/gui/skins2/controls/ctrl_slider.cpp
skins2: don't default the first visible item to selected
[vlc] / modules / gui / skins2 / controls / ctrl_slider.cpp
1 /*****************************************************************************
2  * ctrl_slider.cpp
3  *****************************************************************************
4  * Copyright (C) 2003 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Cyril Deguet     <asmax@via.ecp.fr>
8  *          Olivier Teulière <ipkiss@via.ecp.fr>
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License along
21  * with this program; if not, write to the Free Software Foundation, Inc.,
22  * 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23  *****************************************************************************/
24
25 #include "ctrl_slider.hpp"
26 #include "../events/evt_enter.hpp"
27 #include "../events/evt_mouse.hpp"
28 #include "../events/evt_scroll.hpp"
29 #include "../src/generic_bitmap.hpp"
30 #include "../src/scaled_bitmap.hpp"
31 #include "../src/top_window.hpp"
32 #include "../src/os_factory.hpp"
33 #include "../src/os_graphics.hpp"
34 #include "../utils/position.hpp"
35 #include "../utils/var_percent.hpp"
36
37
38 #define RANGE 40
39
40 static inline float scroll( bool up, float pct, float step )
41 {
42     return pct + (up? step : -step);
43 }
44
45
46 CtrlSliderCursor::CtrlSliderCursor( intf_thread_t *pIntf,
47                                     const GenericBitmap &rBmpUp,
48                                     const GenericBitmap &rBmpOver,
49                                     const GenericBitmap &rBmpDown,
50                                     const Bezier &rCurve,
51                                     VarPercent &rVariable,
52                                     VarBool *pVisible,
53                                     const UString &rTooltip,
54                                     const UString &rHelp ):
55     CtrlGeneric( pIntf, rHelp, pVisible ), m_fsm( pIntf ),
56     m_rVariable( rVariable ), m_tooltip( rTooltip ),
57     m_width( rCurve.getWidth() ), m_height( rCurve.getHeight() ),
58     m_cmdOverDown( this ), m_cmdDownOver( this ),
59     m_cmdOverUp( this ), m_cmdUpOver( this ),
60     m_cmdMove( this ), m_cmdScroll( this ),
61     m_lastPercentage( 0 ), m_xOffset( 0 ), m_yOffset( 0 ),
62     m_pEvt( NULL ), m_rCurve( rCurve )
63 {
64     // Build the images of the cursor
65     OSFactory *pOsFactory = OSFactory::instance( getIntf() );
66     m_pImgUp = pOsFactory->createOSGraphics( rBmpUp.getWidth(),
67                                              rBmpUp.getHeight() );
68     m_pImgUp->drawBitmap( rBmpUp, 0, 0 );
69     m_pImgDown = pOsFactory->createOSGraphics( rBmpDown.getWidth(),
70                                                rBmpDown.getHeight() );
71     m_pImgDown->drawBitmap( rBmpDown, 0, 0 );
72     m_pImgOver = pOsFactory->createOSGraphics( rBmpOver.getWidth(),
73                                                rBmpOver.getHeight() );
74     m_pImgOver->drawBitmap( rBmpOver, 0, 0 );
75
76     // States
77     m_fsm.addState( "up" );
78     m_fsm.addState( "over" );
79     m_fsm.addState( "down" );
80
81     // Transitions
82     m_fsm.addTransition( "over", "mouse:left:down", "down",
83                          &m_cmdOverDown );
84     m_fsm.addTransition( "down", "mouse:left:up", "over",
85                          &m_cmdDownOver );
86     m_fsm.addTransition( "over", "leave", "up", &m_cmdOverUp );
87     m_fsm.addTransition( "up", "enter", "over", &m_cmdUpOver );
88     m_fsm.addTransition( "down", "motion", "down", &m_cmdMove );
89     m_fsm.addTransition( "over", "scroll", "over", &m_cmdScroll );
90
91     // Initial state
92     m_fsm.setState( "up" );
93     m_pImg = m_pImgUp;
94
95     // Observe the position variable
96     m_rVariable.addObserver( this );
97
98     // Initial position of the cursor
99     m_lastPercentage = m_rVariable.get();
100 }
101
102
103 CtrlSliderCursor::~CtrlSliderCursor()
104 {
105     m_rVariable.delObserver( this );
106     delete m_pImgUp;
107     delete m_pImgDown;
108     delete m_pImgOver;
109 }
110
111
112 void CtrlSliderCursor::handleEvent( EvtGeneric &rEvent )
113 {
114     // Save the event to use it in callbacks
115     m_pEvt = &rEvent;
116
117     m_fsm.handleTransition( rEvent.getAsString() );
118 }
119
120
121 bool CtrlSliderCursor::mouseOver( int x, int y ) const
122 {
123     if( m_pImg )
124     {
125         // Compute the position of the cursor
126         int xPos, yPos;
127         m_rCurve.getPoint( m_rVariable.get(), xPos, yPos );
128
129         // Compute the resize factors
130         float factorX, factorY;
131         getResizeFactors( factorX, factorY );
132         xPos = (int)(xPos * factorX);
133         yPos = (int)(yPos * factorY);
134
135         return m_pImg->hit( x - xPos + m_pImg->getWidth() / 2,
136                             y - yPos + m_pImg->getHeight() / 2 );
137     }
138     else
139     {
140         return false;
141     }
142 }
143
144
145 void CtrlSliderCursor::draw( OSGraphics &rImage, int xDest, int yDest, int w, int h )
146 {
147     if( m_pImg )
148     {
149         // Draw the current image
150         rect inter;
151         rect clip( xDest, yDest, w, h);
152
153         if( rect::intersect( m_currentCursorRect, clip, &inter ) )
154             rImage.drawGraphics( *m_pImg,
155                              inter.x - m_currentCursorRect.x,
156                              inter.y - m_currentCursorRect.y,
157                              inter.x, inter.y, inter.width, inter.height );
158     }
159 }
160
161
162 void CtrlSliderCursor::onPositionChange()
163 {
164     // Compute the position of the cursor
165     int xPos, yPos;
166     m_rCurve.getPoint( m_rVariable.get(), xPos, yPos );
167
168     // Compute the resize factors
169     float factorX, factorY;
170     getResizeFactors( factorX, factorY );
171     xPos = (int)(xPos * factorX);
172     yPos = (int)(yPos * factorY);
173
174     const Position *pPos = getPosition();
175
176     int x = pPos->getLeft() + xPos - m_pImg->getWidth() / 2;
177     int y = pPos->getTop() + yPos - m_pImg->getHeight() / 2;
178
179     m_currentCursorRect = rect( x, y, m_pImg->getWidth(), m_pImg->getHeight() );
180 }
181
182
183 void CtrlSliderCursor::onResize()
184 {
185     onPositionChange();
186 }
187
188
189 void CtrlSliderCursor::notifyLayout( int width, int height, int xOffSet, int yOffSet )
190 {
191     if( width > 0 && height > 0 )
192     {
193         CtrlGeneric::notifyLayout( width, height, xOffSet, yOffSet );
194     }
195     else
196     {
197         onPositionChange();
198
199         const Position *pPos = getPosition();
200         CtrlGeneric::notifyLayout( m_currentCursorRect.width,
201                                    m_currentCursorRect.height,
202                                    m_currentCursorRect.x - pPos->getLeft(),
203                                    m_currentCursorRect.y - pPos->getTop() );
204     }
205 }
206
207
208 void CtrlSliderCursor::onUpdate( Subject<VarPercent> &rVariable, void *arg  )
209 {
210     (void)rVariable; (void)arg;
211     // The position has changed
212     refreshLayout( false );
213 }
214
215
216 void CtrlSliderCursor::CmdOverDown::execute()
217 {
218     EvtMouse *pEvtMouse = static_cast<EvtMouse*>(m_pParent->m_pEvt);
219
220     // Compute the resize factors
221     float factorX, factorY;
222     m_pParent->getResizeFactors( factorX, factorY );
223
224     // Get the position of the control
225     const Position *pPos = m_pParent->getPosition();
226
227     // Compute the offset
228     int tempX, tempY;
229     m_pParent->m_rCurve.getPoint( m_pParent->m_rVariable.get(), tempX, tempY );
230     m_pParent->m_xOffset = pEvtMouse->getXPos() - pPos->getLeft()
231                        - (int)(tempX * factorX);
232     m_pParent->m_yOffset = pEvtMouse->getYPos() - pPos->getTop()
233                        - (int)(tempY * factorY);
234
235     m_pParent->captureMouse();
236     m_pParent->m_pImg = m_pParent->m_pImgDown;
237     m_pParent->refreshLayout();
238 }
239
240
241 void CtrlSliderCursor::CmdDownOver::execute()
242 {
243     // Save the position
244     m_pParent->m_lastPercentage = m_pParent->m_rVariable.get();
245
246     m_pParent->releaseMouse();
247     m_pParent->m_pImg = m_pParent->m_pImgUp;
248     m_pParent->refreshLayout();
249 }
250
251
252 void CtrlSliderCursor::CmdUpOver::execute()
253 {
254     m_pParent->m_pImg = m_pParent->m_pImgOver;
255     m_pParent->refreshLayout();
256 }
257
258
259 void CtrlSliderCursor::CmdOverUp::execute()
260 {
261     m_pParent->m_pImg = m_pParent->m_pImgUp;
262     m_pParent->refreshLayout();
263 }
264
265
266 void CtrlSliderCursor::CmdMove::execute()
267 {
268     EvtMouse *pEvtMouse = static_cast<EvtMouse*>(m_pParent->m_pEvt);
269
270     // Get the position of the control
271     const Position *pPos = m_pParent->getPosition();
272
273     // Compute the resize factors
274     float factorX, factorY;
275     m_pParent->getResizeFactors( factorX, factorY );
276
277     // Compute the relative position of the centre of the cursor
278     float relX = pEvtMouse->getXPos() - pPos->getLeft() - m_pParent->m_xOffset;
279     float relY = pEvtMouse->getYPos() - pPos->getTop() - m_pParent->m_yOffset;
280     // Ponderate with the resize factors
281     int relXPond = (int)(relX / factorX);
282     int relYPond = (int)(relY / factorY);
283
284     // Update the position
285     if( m_pParent->m_rCurve.getMinDist( relXPond, relYPond ) < RANGE )
286     {
287         float percentage = m_pParent->m_rCurve.getNearestPercent( relXPond,
288                                                               relYPond );
289         m_pParent->m_rVariable.set( percentage );
290     }
291     else
292     {
293         m_pParent->m_rVariable.set( m_pParent->m_lastPercentage );
294     }
295 }
296
297 void CtrlSliderCursor::CmdScroll::execute()
298 {
299     int dir = static_cast<EvtScroll*>(m_pParent->m_pEvt)->getDirection();
300     m_pParent->m_rVariable.set( scroll( EvtScroll::kUp == dir,
301                                         m_pParent->m_rVariable.get(),
302                                         m_pParent->m_rVariable.getStep()) );
303 }
304
305
306 void CtrlSliderCursor::getResizeFactors( float &rFactorX,
307                                          float &rFactorY ) const
308 {
309     // Get the position of the control
310     const Position *pPos = getPosition();
311
312     rFactorX = 1.0;
313     rFactorY = 1.0;
314
315     // Compute the resize factors
316     if( m_width > 0 )
317         rFactorX = (float)pPos->getWidth() / (float)m_width;
318     if( m_height > 0 )
319         rFactorY = (float)pPos->getHeight() / (float)m_height;
320 }
321
322
323 void CtrlSliderCursor::refreshLayout( bool force )
324 {
325     // Compute the position of the cursor
326     int xPos, yPos;
327     m_rCurve.getPoint( m_rVariable.get(), xPos, yPos );
328
329     // Compute the resize factors
330     float factorX, factorY;
331     getResizeFactors( factorX, factorY );
332     xPos = (int)(xPos * factorX);
333     yPos = (int)(yPos * factorY);
334
335     const Position *pPos = getPosition();
336
337     int x = pPos->getLeft() + xPos - m_pImg->getWidth() / 2;
338     int y = pPos->getTop() + yPos - m_pImg->getHeight() / 2;
339
340     rect region( x, y, m_pImg->getWidth(), m_pImg->getHeight() );
341
342
343     if( !force &&
344         region.x == m_currentCursorRect.x &&
345         region.y == m_currentCursorRect.y &&
346         region.width == m_currentCursorRect.width &&
347         region.height == m_currentCursorRect.height )
348     {
349         return;
350     }
351
352     rect join;
353     if( rect::join( m_currentCursorRect, region, &join ) )
354     {
355         m_currentCursorRect = region;
356         notifyLayout( join.width, join.height,
357                       join.x - pPos->getLeft(),
358                       join.y - pPos->getTop() );
359     }
360 }
361
362
363 CtrlSliderBg::CtrlSliderBg( intf_thread_t *pIntf,
364                             const Bezier &rCurve, VarPercent &rVariable,
365                             int thickness, GenericBitmap *pBackground,
366                             int nbHoriz, int nbVert, int padHoriz, int padVert,
367                             VarBool *pVisible, const UString &rHelp ):
368     CtrlGeneric( pIntf, rHelp, pVisible ), m_pCursor( NULL ),
369     m_rVariable( rVariable ), m_thickness( thickness ), m_rCurve( rCurve ),
370     m_width( rCurve.getWidth() ), m_height( rCurve.getHeight() ),
371     m_pImgSeq( pBackground ), m_pScaledBmp( NULL ), m_nbHoriz( nbHoriz ),
372     m_nbVert( nbVert ), m_padHoriz( padHoriz ), m_padVert( padVert ),
373     m_bgWidth( 0 ), m_bgHeight( 0 ), m_position( 0 )
374 {
375     if( m_pImgSeq )
376     {
377         // Build the background image sequence
378         // Note: we suppose that the last padding is not included in the
379         // given image
380         // TODO: we should probably change this assumption, as it would make
381         // the code a bit simpler and it would be more natural for the skins
382         // designers
383         m_bgWidth = (pBackground->getWidth() + m_padHoriz) / nbHoriz;
384         m_bgHeight = (pBackground->getHeight() + m_padVert) / nbVert;
385
386         // Observe the position variable
387         m_rVariable.addObserver( this );
388
389         // Initial position
390         m_position = (int)( m_rVariable.get() * (m_nbHoriz * m_nbVert - 1) );
391     }
392 }
393
394
395 CtrlSliderBg::~CtrlSliderBg()
396 {
397     if( m_pImgSeq )
398         m_rVariable.delObserver( this );
399
400     delete m_pScaledBmp;
401 }
402
403
404 bool CtrlSliderBg::mouseOver( int x, int y ) const
405 {
406     // Compute the resize factors
407     float factorX, factorY;
408     getResizeFactors( factorX, factorY );
409
410     return (m_rCurve.getMinDist( (int)(x / factorX), (int)(y / factorY),
411                                  factorX, factorY ) < m_thickness );
412 }
413
414
415 void CtrlSliderBg::draw( OSGraphics &rImage, int xDest, int yDest, int w, int h )
416 {
417     if( !m_pImgSeq || m_bgWidth <=0 || m_bgHeight <= 0 )
418         return;
419
420     // Compute the resize factors
421     float factorX, factorY;
422     getResizeFactors( factorX, factorY );
423
424     int width = m_bgWidth * m_nbHoriz - (int)(m_padHoriz * factorX);
425     int height = m_bgHeight * m_nbVert - (int)(m_padVert * factorY);
426
427     // Rescale the image with the actual size of the control if needed
428     if( !m_pScaledBmp ||
429         m_pScaledBmp->getWidth() != width ||
430         m_pScaledBmp->getHeight() != height )
431     {
432         delete m_pScaledBmp;
433         m_pScaledBmp = new ScaledBitmap( getIntf(), *m_pImgSeq, width, height );
434     }
435
436     // Locate the right image in the background bitmap
437     int x = m_bgWidth * ( m_position % m_nbHoriz );
438     int y = m_bgHeight * ( m_position / m_nbHoriz );
439
440     // Draw the background image
441     const Position *pPos = getPosition();
442     rect region( pPos->getLeft(), pPos->getTop(),
443                  m_bgWidth - (int)(m_padHoriz * factorX),
444                  m_bgHeight - (int)(m_padVert * factorY) );
445     rect clip( xDest, yDest, w, h );
446     rect inter;
447     if( rect::intersect( region, clip, &inter ) )
448         rImage.drawBitmap( *m_pScaledBmp,
449                            x + inter.x - region.x,
450                            y + inter.y - region.y,
451                            inter.x, inter.y,
452                            inter.width, inter.height );
453 }
454
455
456 void CtrlSliderBg::handleEvent( EvtGeneric &rEvent )
457 {
458     if( rEvent.getAsString().find( "mouse:left:down" ) != string::npos )
459     {
460         // Compute the resize factors
461         float factorX, factorY;
462         getResizeFactors( factorX, factorY );
463
464         // Get the position of the control
465         const Position *pPos = getPosition();
466
467         // Get the value corresponding to the position of the mouse
468         EvtMouse &rEvtMouse = (EvtMouse&)rEvent;
469         int x = rEvtMouse.getXPos();
470         int y = rEvtMouse.getYPos();
471         m_rVariable.set( m_rCurve.getNearestPercent(
472                             (int)((x - pPos->getLeft()) / factorX),
473                             (int)((y - pPos->getTop()) / factorY) ) );
474
475         // Forward the clic to the cursor
476         EvtMouse evt( getIntf(), x, y, EvtMouse::kLeft, EvtMouse::kDown );
477         TopWindow *pWin = getWindow();
478         if( pWin && m_pCursor )
479         {
480             EvtEnter evtEnter( getIntf() );
481             // XXX It was not supposed to be implemented like that !!
482             pWin->forwardEvent( evtEnter, *m_pCursor );
483             pWin->forwardEvent( evt, *m_pCursor );
484         }
485     }
486     else if( rEvent.getAsString().find( "scroll" ) != string::npos )
487     {
488         int dir = static_cast<EvtScroll*>(&rEvent)->getDirection();
489         m_rVariable.set( scroll( EvtScroll::kUp == dir,
490                                  m_rVariable.get(), m_rVariable.getStep() ) );
491     }
492 }
493
494
495 void CtrlSliderBg::onResize()
496 {
497     if( m_pImgSeq )
498     {
499         // Compute only the new size of an elementary image.
500         // The actual resizing is done in the draw() method for now...
501
502         // Compute the resize factors
503         float factorX, factorY;
504         getResizeFactors( factorX, factorY );
505
506         // Size of one elementary background image (padding included)
507         m_bgWidth = (int)((m_pImgSeq->getWidth() + m_padHoriz) * factorX / m_nbHoriz);
508         m_bgHeight = (int)((m_pImgSeq->getHeight() + m_padVert) * factorY / m_nbVert);
509     }
510 }
511
512
513 void CtrlSliderBg::associateCursor( CtrlSliderCursor &rCursor )
514 {
515     m_pCursor = &rCursor;
516 }
517
518
519 void CtrlSliderBg::onUpdate( Subject<VarPercent> &rVariable, void*arg )
520 {
521     (void)rVariable; (void)arg;
522     int position = (int)( m_rVariable.get() * (m_nbHoriz * m_nbVert - 1) );
523     if( position == m_position )
524         return;
525
526     m_position = position;
527     notifyLayout( m_bgWidth, m_bgHeight );
528 }
529
530
531 void CtrlSliderBg::getResizeFactors( float &rFactorX, float &rFactorY ) const
532 {
533     // Get the position of the control
534     const Position *pPos = getPosition();
535
536     rFactorX = 1.0;
537     rFactorY = 1.0;
538
539     // Compute the resize factors
540     if( m_width > 0 )
541         rFactorX = (float)pPos->getWidth() / (float)m_width;
542     if( m_height > 0 )
543         rFactorY = (float)pPos->getHeight() / (float)m_height;
544 }
545