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