1 /*
2 * Copyright(c) 2016, OpenAV
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in
12 * the documentation and/or other materials provided with the
13 * distribution.
14 * * Neither the name of the <organization> nor the
15 * names of its contributors may be used to endorse or promote
16 * products derived from this software without specific prior
17 * written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
22 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL OPENAV BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
26 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
27 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
28 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
29 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31
32 #include "scroll.hxx"
33
34 #include "ui.hxx"
35 #include "theme.hxx"
36 #include "slider.hxx"
37 #include "listitem.hxx"
38
39 #include <stdio.h>
40
41 using namespace Avtk;
42
43 #define AVTK_SCROLL_BAR_SIZE 15
44
Scroll(Avtk::UI * ui,int x_,int y_,int w_,int h_,std::string label_)45 Scroll::Scroll( Avtk::UI* ui, int x_, int y_, int w_, int h_, std::string label_) :
46 Group( ui, x_, y_, w_, h_, label_ ),
47 newChildCr( false ),
48 childCr( 0x0 ),
49 scrollX_( 0 ),
50 scrollY_( 0 ),
51 scrollV_( false ),
52 scrollH_( false ),
53 setCtrlZoom_( false ),
54
55 vSlider( new Avtk::Slider( ui, x_ + w_ - AVTK_SCROLL_BAR_SIZE, y_, AVTK_SCROLL_BAR_SIZE, h_, "Scroll VSlider") ),
56 hSlider( new Avtk::Slider( ui, x_, y_ - w_ - AVTK_SCROLL_BAR_SIZE, w_, AVTK_SCROLL_BAR_SIZE, "Scroll HSlider") )
57 {
58 // deal with sliders: they're a unique case where they're owned by the scroll,
59 // but they are *not* part of the group. First remove the widget from the UI
60 vSlider->parent()->remove( vSlider );
61 hSlider->parent()->remove( hSlider );
62 // then set the callbacks to the scoll movement
63 vSlider->callback = staticSliderCB;
64 vSlider->callbackUD = this;
65 hSlider->callback = staticSliderCB;
66 hSlider->callbackUD = this;
67 }
68
sliderCB(Widget * w)69 void Scroll::sliderCB( Widget* w )
70 {
71 if( w == vSlider ) {
72 vertical( w->value() );
73 }
74 if( w == hSlider ) {
75 horizontal( w->value() );
76 }
77 }
78
childResize(Widget * w)79 void Scroll::childResize( Widget* w )
80 {
81 set( w );
82 }
83
set(Widget * child)84 void Scroll::set( Widget* child )
85 {
86 Group::add( child );
87 newChildCr = true;
88
89 // set child to draw at 0,0 of the childCr
90 child->x( 0 );
91 child->y( 0 );
92
93 redrawChild_ = true;
94
95 if( child->h() > h_ ) {
96 // child is bigger than our vertical size:
97 scrollV_ = true;
98 scrollVamount = child->h() - h_;
99 } else {
100 // set the childs size to the scroll area
101 child->h( h_ - AVTK_SCROLL_BAR_SIZE );
102 scrollV_ = false;
103 scrollY_ = 0;
104 }
105
106 if( child->w() > w_ ) {
107 // child is bigger than our vertical size:
108 scrollH_ = true;
109 scrollHamount = child->w() - w_;
110 if( scrollV_ )
111 scrollHamount = child->w() - (w_ - 4);
112 //printf("Scroll::set() scrollHamount %i\n",scrollHamount);
113 } else {
114 // set the childs size to the scroll area
115 child->w( w_ - AVTK_SCROLL_BAR_SIZE );
116 scrollH_ = false;
117 scrollX_ = 0;
118 }
119
120 // show top left corner or scroll window
121 vertical ( 1 );
122 horizontal( 0 );
123 }
124
draw(cairo_t * cr)125 void Scroll::draw( cairo_t* cr )
126 {
127 if( Widget::visible() ) {
128 cairo_save( cr );
129
130 if( newChildCr && children.size() ) {
131 Widget* child = children.at(0);
132
133 // create a cairo context for the child widgets size
134 if( childCr ) {
135 //printf("destroying existing childCr surface + context\n");
136 cairo_surface_destroy( cairo_get_target( childCr ) );
137 cairo_destroy( childCr );
138 }
139
140 cairo_surface_t* surface = cairo_surface_create_similar(
141 cairo_get_target( cr ),
142 CAIRO_CONTENT_COLOR_ALPHA,
143 child->w(), child->h() );
144
145 if (!surface) {
146 fprintf(stderr, "failed to create child cairo surface\n");
147 }
148
149 if (!(childCr = cairo_create(surface)) ) {
150 fprintf(stderr, "failed to create child cairo context\n");
151 }
152
153 //cairo_surface_write_to_png( cairo_get_target( childCr ), "childCr.png" );
154 newChildCr = false;
155
156 // flag child to be drawn
157 redrawChild_ = true;
158 }
159
160 if( childCr ) {
161 if( redrawChild_ ) {
162 //printf("cairo redrawing child in scroll group\n");
163 redrawChild( cr );
164 }
165
166 // clip the Scroll context, to draw only what will be shown
167 cairo_rectangle( cr, x_, y_, w_, h_ );
168 cairo_clip( cr );
169
170 // paint to the x_,y_ co-ord of the scroll window
171 cairo_surface_t* s = cairo_get_target( childCr );
172 cairo_set_source_surface( cr, s, x_ + scrollX_, y_ + scrollY_ );
173 cairo_paint( cr );
174 }
175 // draw box / scroll bars
176 roundedBox(cr, x_, y_, w_, h_, theme_->cornerRadius_ );
177 theme_->color( cr, FG );
178 cairo_set_line_width(cr, 0.5);
179 cairo_stroke( cr );
180
181 if( scrollV_ ) {
182 vSlider->draw( cr );
183 }
184 if( scrollH_ ) {
185 hSlider->draw( cr );
186 }
187
188 cairo_restore( cr );
189 }
190 }
191
vertical(float v)192 void Scroll::vertical( float v )
193 {
194 if( scrollV_ ) { // child->h() > h()
195 scrollY_ = -( (1-v)*scrollVamount);
196 vSlider->value( v );
197 ui->redraw();
198 }
199 }
200
horizontal(float v)201 void Scroll::horizontal( float v )
202 {
203 if( scrollH_ ) { // child->w() > w()
204 scrollX_ = -( v*scrollHamount );
205 ui->redraw();
206 //printf("scrollH_ %i, value %f, scrollHamount %i\n",scrollX_,v, scrollHamount);
207 }
208 }
209
setCtrlZoom(bool zoom)210 void Scroll::setCtrlZoom( bool zoom )
211 {
212 setCtrlZoom_ = zoom;
213 }
214
handle(const PuglEvent * event)215 int Scroll::handle( const PuglEvent* event )
216 {
217 // handle slider, so slider-click is responeded to
218 if( vSlider->handle( event ) )
219 return 1;
220
221 bool handle = false;
222
223 // check if the scroll event is in scroll area; if yes scroll action
224 if( event->type == PUGL_SCROLL ) {
225 if( touches( event->scroll.x, event->scroll.y ) ) {
226 // not control pressed
227 if( !(((PuglEventScroll*)event)->state & PUGL_MOD_CTRL) ) {
228 // shift scrolls horizontal
229 if( !(((PuglEventScroll*)event)->state & PUGL_MOD_SHIFT) ) {
230 if( event->scroll.dy > 0 )
231 vSlider->value( vSlider->value() + 0.1 );
232 else
233 vSlider->value( vSlider->value() - 0.1 );
234 vertical( vSlider->value() );
235 } else {
236 if( event->scroll.dy > 0 )
237 hSlider->value( hSlider->value() - 0.1 );
238 else
239 hSlider->value( hSlider->value() + 0.1 );
240 horizontal( hSlider->value() );
241 }
242
243 ui->redraw( this );
244
245 // return, eating event, so child group won't react
246 return 1;
247 } else {
248 if( !setCtrlZoom_ ) {
249 handle = true;
250 } else {
251 // zoom on the child widget: aka, change its
252 if( children.size() ) {
253 Widget* w = children.at(0);
254 float scale = 0.75;
255 if( event->scroll.dy > 0 )
256 scale = 1.5;
257
258 int newW = w->w() * scale;
259 int newH = w->h() * scale;
260
261 if( newW > 2048 * 2 || newH > 2048 * 2 )
262 return 1; // no more zooming: cairo_t context gets too big
263
264 w->w( newW );
265 w->h( newH );
266
267 childResize( w );
268
269 // handled
270 return 1;
271 }
272 }
273 }
274 }
275 }
276
277 // create group event, so we can offset the mouse-click co-ord according to
278 // the scroll position. This might seem a lot of work, but it allows for easy
279 // mouse handling in the child widget, because the co-ords are normal
280 if( event->type == PUGL_BUTTON_PRESS ||
281 event->type == PUGL_BUTTON_RELEASE ) {
282 if( touches( event->button.x, event->button.y ) ) {
283 PuglEvent childEvent;
284 // offset to new location
285 offsetEvent( event, &childEvent );
286 // pass event on to children
287 if( Group::handle( &childEvent ) ) {
288 newChildCr = true;
289 ui->redraw();
290 return 1;
291 }
292 }
293 }
294
295
296 if( handle ) {
297 if( Group::handle( event ) ) {
298 newChildCr = true;
299 ui->redraw();
300 return 1;
301 }
302 }
303
304 return 0;
305 }
306
redrawChild(cairo_t * cr)307 void Scroll::redrawChild( cairo_t* cr )
308 {
309 if( !childCr ) {
310 redrawChild_ = false;
311 return;
312 }
313 cairo_save( cr );
314
315 /*
316 /// clear the screen
317 cairo_rectangle( childCr, 0, 0, w_, h_ );
318 cairo_set_source_rgb( childCr, 24/255., 24/255., 24/255. );
319 cairo_fill( childCr );
320 */
321
322 // draw the widget on the childCr cairo_t*
323 Group::draw( childCr );
324
325 cairo_surface_t* s = cairo_get_target( childCr );
326 cairo_surface_flush( s );
327
328 redrawChild_ = false;
329
330 cairo_restore( cr );
331 }
332
~Scroll()333 Scroll::~Scroll()
334 {
335 cairo_destroy( childCr );
336 delete vSlider;
337 delete hSlider;
338 }
339
offsetEvent(const PuglEvent * inEvent,PuglEvent * outEvent)340 void Scroll::offsetEvent( const PuglEvent* inEvent, PuglEvent* outEvent )
341 {
342 // copy inEvent to outEvent
343 *outEvent = *inEvent;
344
345 // adjust properties as needed
346 if( outEvent->type == PUGL_SCROLL ) {
347 outEvent->scroll.x += ( x_ + scrollX_ );
348 outEvent->scroll.y -= ( y_ + scrollY_ );
349 } else if( outEvent->type == PUGL_BUTTON_PRESS ||
350 outEvent->type == PUGL_BUTTON_RELEASE ) {
351 outEvent->button.x -= ( x_ + scrollX_ );
352 outEvent->button.y -= ( y_ + scrollY_ );
353 } else {
354 printf("%s, event type not handled!\n", __PRETTY_FUNCTION__ );
355 }
356 }
357