1 /*
2 * this file is part of the oxygen gtk engine
3 * Copyright (c) 2010 Hugo Pereira Da Costa <hugo.pereira@free.fr>
4 * Copyright (c) 2010 Ruslan Kabatsayev <b7.10110111@gmail.com>
5 *
6 * This  library is free  software; you can  redistribute it and/or
7 * modify it  under  the terms  of the  GNU Lesser  General  Public
8 * License  as published  by the Free  Software  Foundation; either
9 * version 2 of the License, or( at your option ) any later version.
10 *
11 * This library is distributed  in the hope that it will be useful,
12 * but  WITHOUT ANY WARRANTY; without even  the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License  along  with  this library;  if not,  write to  the Free
18 * Software Foundation, Inc., 51  Franklin St, Fifth Floor, Boston,
19 * MA 02110-1301, USA.
20 */
21 
22 #include "oxygenwindowshadow.h"
23 #include "oxygencachekey.h"
24 #include "oxygencairoutils.h"
25 #include "oxygencolorutils.h"
26 
27 namespace Oxygen
28 {
29 
30     //________________________________________________________________________________
render(cairo_t * cr,gint x,gint y,gint w,gint h)31     void WindowShadow::render(cairo_t* cr, gint x, gint y, gint w, gint h)
32     {
33         ColorUtils::Rgba background = settings().palette().color(Palette::Window);
34         WindowShadowKey key;
35         key.active=_wopt&WinDeco::Active;
36         tileSet( background, key).render( cr, x, y, w, h, TileSet::Full );
37     }
38 
39     //________________________________________________________________________________
tileSet(const ColorUtils::Rgba & color,WindowShadowKey key) const40     const TileSet& WindowShadow::tileSet(const ColorUtils::Rgba& color, WindowShadowKey key) const
41     {
42 
43         // check if tileset already in cache
44         const TileSet& tileSet( helper().windowShadowCache().value(key) );
45         if( tileSet.isValid() ) return tileSet;
46 
47         const double size( shadowSize() );
48         return helper().windowShadowCache().insert(key, TileSet( shadowPixmap( color, key ), int(size), int(size), 1, 1 ) );
49 
50     }
51 
52     //________________________________________________________________________________
shadowPixmap(const ColorUtils::Rgba & color,const WindowShadowKey & key) const53     Cairo::Surface WindowShadow::shadowPixmap(const ColorUtils::Rgba& color, const WindowShadowKey& key ) const
54     {
55 
56         const bool active( key.active );
57         const ShadowConfiguration& shadowConfiguration( active ? activeShadowConfiguration_ : inactiveShadowConfiguration_ );
58 
59         static const double fixedSize=25.5;
60         const double size( shadowSize() );
61         const double shadowSize( shadowConfiguration.isEnabled() ? shadowConfiguration.shadowSize() : 0 );
62 
63         Cairo::Surface shadow( helper().createSurface( int(size*2), int(size*2) ) );
64         Cairo::Context p(shadow);
65 
66         // some gradients rendering are different at bottom corners if client has no border
67         const bool hasTopBorder( key.hasTopBorder );
68         const bool hasBottomBorder( key.hasBottomBorder );
69 
70         if( shadowSize )
71         {
72 
73             if( active )
74             {
75                 {
76                     // inner (shark) gradient
77                     const double gradientSize = std::min( shadowSize, (shadowSize+fixedSize)/2 );
78                     const double hoffset = shadowConfiguration.horizontalOffset()*gradientSize/fixedSize;
79                     const double voffset = shadowConfiguration.verticalOffset()*gradientSize/fixedSize;
80 
81                     Cairo::Pattern rg( cairo_pattern_create_radial( size+12.0*hoffset, size+12.0*voffset, gradientSize ) );
82                     cairo_pattern_add_color_stop( rg, 1, ColorUtils::Rgba::transparent() );
83 
84                     // gaussian shadow is used
85                     int nPoints( int( (10*gradientSize)/fixedSize ) );
86                     Gaussian f( 0.85, 0.17 );
87                     ColorUtils::Rgba c(shadowConfiguration.innerColor());
88                     for( double i=0; i < nPoints; i++ )
89                     {
90                         double x = i/nPoints;
91                         c.setAlpha( f(x) );
92                         cairo_pattern_add_color_stop( rg, x, c );
93                     }
94 
95                     cairo_set_source(p, rg);
96                     GdkRectangle rect={0,0,int(size*2),int(size*2)};
97                     renderGradient(p, rect, rg, hasTopBorder, hasBottomBorder );
98                 }
99 
100                 {
101                     // outer (spread) gradient
102                     const double gradientSize = shadowSize;
103                     const double hoffset = shadowConfiguration.horizontalOffset()*gradientSize/fixedSize;
104                     const double voffset = shadowConfiguration.verticalOffset()*gradientSize/fixedSize;
105 
106                     Cairo::Pattern rg( cairo_pattern_create_radial( size+12.0*hoffset, size+12.0*voffset, gradientSize ) );
107                     cairo_pattern_add_color_stop( rg, 1, ColorUtils::Rgba::transparent() );
108 
109                     // gaussian shadow is used
110                     int nPoints( int( (10*gradientSize)/fixedSize ) );
111                     Gaussian f( 0.46, 0.34 );
112                     ColorUtils::Rgba c(shadowConfiguration.outerColor());
113                     for( double i=0; i < nPoints; i++ )
114                     {
115                         double x = i/nPoints;
116                         c.setAlpha( f(x) );
117                         cairo_pattern_add_color_stop( rg, x, c );
118                     }
119 
120                     cairo_set_source(p, rg);
121                     cairo_rectangle(p, 0,0,size*2,size*2);
122                     cairo_fill(p);
123                 }
124             } else {
125 
126                 {
127                     // inner (sharp) gradient
128                     const double gradientSize = std::min( shadowSize, fixedSize );
129                     const double hoffset = shadowConfiguration.horizontalOffset()*gradientSize/fixedSize;
130                     const double voffset = shadowConfiguration.verticalOffset()*gradientSize/fixedSize;
131 
132                     Cairo::Pattern rg( cairo_pattern_create_radial( size+hoffset, size+voffset, gradientSize ) );
133                     cairo_pattern_add_color_stop( rg, 1, ColorUtils::Rgba::transparent() );
134 
135                     // parabolic shadow is used
136                     int nPoints( int( (10*gradientSize)/fixedSize ) );
137                     Parabolic f( 1, 0.22 );
138                     ColorUtils::Rgba c(shadowConfiguration.outerColor());
139                     for( double i=0; i < nPoints; i++ )
140                     {
141                         double x = i/nPoints;
142                         c.setAlpha( f(x) );
143                         cairo_pattern_add_color_stop( rg, x, c );
144                     }
145 
146                     cairo_set_source(p, rg);
147                     GdkRectangle rect={0,0,int(size*2),int(size*2)};
148                     renderGradient(p, rect, rg, hasTopBorder, hasBottomBorder );
149                 }
150 
151                 {
152                     // mid gradient
153                     const double gradientSize = std::min( shadowSize, (shadowSize+2*fixedSize)/3 );
154                     const double hoffset = shadowConfiguration.horizontalOffset()*gradientSize/fixedSize;
155                     const double voffset = shadowConfiguration.verticalOffset()*gradientSize/fixedSize;
156 
157                     Cairo::Pattern rg( cairo_pattern_create_radial( size+8.0*hoffset, size+8.0*voffset, gradientSize ) );
158                     cairo_pattern_add_color_stop( rg, 1, ColorUtils::Rgba::transparent() );
159 
160                     // gaussian shadow is used
161                     int nPoints( int( (10*gradientSize)/fixedSize ) );
162                     Gaussian f( 0.54, 0.21 );
163                     ColorUtils::Rgba c(shadowConfiguration.outerColor());
164                     for( double i=0; i < nPoints; i++ )
165                     {
166                         double x = i/nPoints;
167                         c.setAlpha( f(x) );
168                         cairo_pattern_add_color_stop( rg, x, c );
169                     }
170 
171                     cairo_set_source(p, rg);
172                     cairo_rectangle(p, 0,0,size*2,size*2);
173                     cairo_fill(p);
174                 }
175 
176                 {
177                     // outer (spread) gradient
178                     const double gradientSize = shadowSize;
179                     const double hoffset = shadowConfiguration.horizontalOffset()*gradientSize/fixedSize;
180                     const double voffset = shadowConfiguration.verticalOffset()*gradientSize/fixedSize;
181 
182                     Cairo::Pattern rg( cairo_pattern_create_radial( size+20.0*hoffset, size+20.0*voffset, gradientSize ) );
183                     cairo_pattern_add_color_stop( rg, 1, ColorUtils::Rgba::transparent() );
184 
185                     // gaussian shadow is used
186                     int nPoints( int( (20*gradientSize)/fixedSize ) );
187                     Gaussian f( 0.155, 0.445 );
188                     ColorUtils::Rgba c(shadowConfiguration.outerColor());
189                     for( double i=0; i < nPoints; i++ )
190                     {
191                         double x = i/nPoints;
192                         c.setAlpha( f(x) );
193                         cairo_pattern_add_color_stop( rg, x, c );
194                     }
195 
196                     cairo_set_source(p, rg);
197                     cairo_rectangle(p, 0,0,size*2,size*2);
198                     cairo_fill(p);
199                 }
200             }
201 
202             cairo_set_source_rgb(p,0,0,0);
203             cairo_set_operator(p,CAIRO_OPERATOR_CLEAR);
204             cairo_ellipse(p, size-3,size-3,6,6);
205             cairo_fill(p);
206 
207         }
208 
209         return shadow;
210     }
211 
212     //________________________________________________________________________________
renderGradient(cairo_t * p,const GdkRectangle & rect,cairo_pattern_t * rg,bool hasTopBorder,bool hasBottomBorder) const213     void WindowShadow::renderGradient( cairo_t* p, const GdkRectangle& rect, cairo_pattern_t* rg, bool hasTopBorder, bool hasBottomBorder ) const
214     {
215         if( hasTopBorder && hasBottomBorder )
216         {
217             cairo_set_source(p,rg);
218             gdk_cairo_rectangle(p, &rect);
219             cairo_fill(p);
220             return;
221         }
222 
223         double size( rect.width/2.0 );
224 
225         // get pattern definition
226         double x0(0), y0(0), r0(0);
227         double x1(0), y1(0), r1(0);
228         const cairo_status_t status( cairo_pattern_get_radial_circles( rg, &x0, &y0, &r0, &x1, &y1, &r1 ) );
229         assert( status == CAIRO_STATUS_SUCCESS );
230 
231         const double hoffset( x0 - size );
232         const double voffset( y0 - size );
233         const double radius( r1 );
234 
235         ColorStop::List stops( cairo_pattern_get_color_stops( rg ) );
236 
237         // draw ellipse for the upper rect
238         if( hasTopBorder ) {
239             cairo_set_source( p, rg );
240             cairo_rectangle( p, hoffset, voffset, 2*size-hoffset, size );
241             cairo_fill( p );
242         } else {
243 
244             // draw square gradients for the lower rect
245             {
246                 // vertical lines
247                 Cairo::Pattern pattern( cairo_pattern_create_linear( hoffset, 0.0, 2*size+hoffset, 0.0 ) );
248                 for( unsigned int i = 0; i < stops.size(); ++i )
249                 {
250                     const ColorUtils::Rgba c( stops[i]._color );
251                     const double x( stops[i]._x * radius );
252                     cairo_pattern_add_color_stop( pattern, (size-x)/(2.0*size), c );
253                     cairo_pattern_add_color_stop( pattern, (size+x)/(2.0*size), c );
254                 }
255 
256                 cairo_set_source( p, pattern );
257                 cairo_rectangle( p, hoffset, size+voffset-4, 2*size-hoffset, 4 );
258                 cairo_fill( p );
259             }
260 
261             {
262 
263                 // horizontal line
264                 Cairo::Pattern pattern( cairo_pattern_create_linear( 0, voffset, 0, 2*size+voffset ) );
265                 for( unsigned int i = 0; i < stops.size(); ++i )
266                 {
267                     const ColorUtils::Rgba c( stops[i]._color );
268                     const double x( stops[i]._x * radius );
269                     cairo_pattern_add_color_stop( pattern, (size-x)/(2.0*size), c );
270                 }
271 
272                 cairo_set_source( p, pattern );
273                 cairo_rectangle( p, size-4+hoffset, voffset, 8, size );
274                 cairo_fill( p );
275 
276             }
277 
278             {
279 
280                 // top-left corner
281                 Cairo::Pattern pattern( cairo_pattern_create_radial( size+hoffset-4, size+voffset-4, radius ) );
282                 for( unsigned int i = 0; i < stops.size(); ++i )
283                 {
284                     ColorUtils::Rgba c( stops[i]._color );
285                     double x( stops[i]._x -4.0/radius );
286                     if( x<0 )
287                     {
288                         if( i < stops.size()-1 )
289                         {
290                             const double x1( stops[i+1]._x - 4.0/radius );
291                             if( x != x1 ) c = ColorUtils::mix( c, stops[i+1]._color, -x/(x1-x) );
292                             else continue;
293                         }
294 
295                         x = 0;
296 
297                     }
298 
299                     cairo_pattern_add_color_stop( pattern, x, c );
300 
301                 }
302 
303 
304                 cairo_set_source( p, pattern );
305                 cairo_rectangle( p, hoffset, voffset-4, size-4, size );
306                 cairo_fill( p );
307 
308             }
309 
310             {
311 
312                 // top-right corner
313                 Cairo::Pattern pattern( cairo_pattern_create_radial( size+hoffset+4, size+voffset-4, radius ) );
314                 for( unsigned int i = 0; i < stops.size(); ++i )
315                 {
316                     ColorUtils::Rgba c( stops[i]._color );
317                     double x( stops[i]._x -4.0/radius );
318                     if( x<0 )
319                     {
320                         if( i < stops.size()-1 )
321                         {
322                             const double x1( stops[i+1]._x - 4.0/radius );
323                             if( x != x1 ) c = ColorUtils::mix( c, stops[i+1]._color, -x/(x1-x) );
324                             else continue;
325                         }
326 
327                         x = 0;
328 
329                     }
330 
331                     cairo_pattern_add_color_stop( pattern, x, c );
332 
333                 }
334 
335 
336                 cairo_set_source( p, pattern );
337                 cairo_rectangle( p, size+hoffset+4, voffset-4, size-4, size );
338                 cairo_fill( p );
339 
340             }
341 
342         }
343 
344         // Bottom part
345         if( hasBottomBorder ) {
346 
347             cairo_set_source( p, rg );
348             cairo_rectangle( p, hoffset, voffset+size, 2*size-hoffset, size );
349             cairo_fill( p );
350 
351         } else {
352 
353             // draw square gradients for the lower rect
354             {
355                 // vertical lines
356                 Cairo::Pattern pattern( cairo_pattern_create_linear( hoffset, 0.0, 2*size+hoffset, 0.0 ) );
357                 for( unsigned int i = 0; i < stops.size(); ++i )
358                 {
359                     const ColorUtils::Rgba c( stops[i]._color );
360                     const double x( stops[i]._x * radius );
361                     cairo_pattern_add_color_stop( pattern, (size-x)/(2.0*size), c );
362                     cairo_pattern_add_color_stop( pattern, (size+x)/(2.0*size), c );
363                 }
364 
365                 cairo_set_source( p, pattern );
366                 cairo_rectangle( p, hoffset, size+voffset, 2*size-hoffset, 4 );
367                 cairo_fill( p );
368             }
369 
370             {
371 
372                 // horizontal line
373                 Cairo::Pattern pattern( cairo_pattern_create_linear( 0, voffset, 0, 2*size+voffset ) );
374                 for( unsigned int i = 0; i < stops.size(); ++i )
375                 {
376                     const ColorUtils::Rgba c( stops[i]._color );
377                     const double x( stops[i]._x * radius );
378                     cairo_pattern_add_color_stop( pattern, (size+x)/(2.0*size), c );
379                 }
380 
381                 cairo_set_source( p, pattern );
382                 cairo_rectangle( p, size-4+hoffset, size+voffset, 8, size );
383                 cairo_fill( p );
384 
385             }
386 
387             {
388 
389                 // bottom-left corner
390                 Cairo::Pattern pattern( cairo_pattern_create_radial( size+hoffset-4, size+voffset+4, radius ) );
391                 for( unsigned int i = 0; i < stops.size(); ++i )
392                 {
393                     ColorUtils::Rgba c( stops[i]._color );
394                     double x( stops[i]._x -4.0/radius );
395                     if( x<0 )
396                     {
397                         if( i < stops.size()-1 )
398                         {
399                             const double x1( stops[i+1]._x - 4.0/radius );
400                             if( x != x1 ) c = ColorUtils::mix( c, stops[i+1]._color, -x/(x1-x) );
401                             else continue;
402                         }
403 
404                         x = 0;
405 
406                     }
407 
408                     cairo_pattern_add_color_stop( pattern, x, c );
409 
410                 }
411 
412 
413                 cairo_set_source( p, pattern );
414                 cairo_rectangle( p, hoffset, size+voffset+4, size-4, size );
415                 cairo_fill( p );
416 
417             }
418 
419             {
420 
421                 // bottom-right corner
422                 Cairo::Pattern pattern( cairo_pattern_create_radial( size+hoffset+4, size+voffset+4, radius ) );
423                 for( unsigned int i = 0; i < stops.size(); ++i )
424                 {
425                     ColorUtils::Rgba c( stops[i]._color );
426                     double x( stops[i]._x -4.0/radius );
427                     if( x<0 )
428                     {
429                         if( i < stops.size()-1 )
430                         {
431                             const double x1( stops[i+1]._x - 4.0/radius );
432                             if( x != x1 ) c = ColorUtils::mix( c, stops[i+1]._color, -x/(x1-x) );
433                             else continue;
434                         }
435 
436                         x = 0;
437 
438                     }
439 
440                     cairo_pattern_add_color_stop( pattern, x, c );
441 
442                 }
443 
444 
445                 cairo_set_source( p, pattern );
446                 cairo_rectangle( p, size+hoffset+4, size+voffset+4, size-4, size );
447                 cairo_fill( p );
448 
449             }
450 
451         }
452 
453     }
454 }
455