1 /* -copyright-
2 #-#
3 #-# xsnow: let it snow on your desktop
4 #-# Copyright (C) 1984,1988,1990,1993-1995,2000-2001 Rick Jansen
5 #-# 2019,2020,2021 Willem Vermin
6 #-#
7 #-# This program is free software: you can redistribute it and/or modify
8 #-# it under the terms of the GNU General Public License as published by
9 #-# the Free Software Foundation, either version 3 of the License, or
10 #-# (at your option) any later version.
11 #-#
12 #-# This program is distributed in the hope that it will be useful,
13 #-# but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 #-# GNU General Public License for more details.
16 #-#
17 #-# You should have received a copy of the GNU General Public License
18 #-# along with this program. If not, see <http://www.gnu.org/licenses/>.
19 #-#
20 */
21
22 #define DEFAULTTREETYPE 2
23
24 #define NOTACTIVE \
25 (Flags.BirdsOnly || !WorkspaceActive())
26
27 #ifdef HAVE_CONFIG_H
28 #include "config.h"
29 #endif
30 #include <stdio.h>
31 #include <gtk/gtk.h>
32 #include <stdlib.h>
33 #include <X11/Intrinsic.h>
34 #include <X11/xpm.h>
35 #include "debug.h"
36 #include "scenery.h"
37 #include "windows.h"
38 #include "utils.h"
39 #include "ixpm.h"
40 #include "flags.h"
41 #include "pixmaps.h"
42 #include "fallensnow.h"
43 #include "csvpos.h"
44 #include "treesnow.h"
45
46 static int do_initbaum(void *);
47 static void ReInitTree0(void);
48 static void InitTreePixmaps(void);
49 static void RedrawTrees(void);
50 static cairo_surface_t *tree_surface(int flip, const char **xpm, float scale);
51 static void create_tree_surfaces(void);
52 static void create_tree_dimensions(int tt);
53 static int compartrees(const void *a, const void *b);
54 static void setTreeScale(void);
55
56 static int NtreeTypes = 0;
57 static int TreeRead = 0;
58 static char **TreeXpm = NULL;
59 static Pixmap TreePixmap[MAXTREETYPE+1][2];
60 static Pixmap TreeMaskPixmap[MAXTREETYPE+1][2];
61 static int TreeWidth[MAXTREETYPE+1], TreeHeight[MAXTREETYPE+1];
62 static int *TreeType = NULL;
63 static int NTrees = 0; // actual number of trees
64 static int Newtrees = 1; // switch to determine if trees are to be repositioned
65 static Treeinfo **Trees = NULL;
66
67 static float treeScale = 1.0;
68 static const float LocalScale = 0.7; // correction scale: if scenery is always too smnall, enlarge this and vice versa
69 static float MinScale = 0.6; // scale for items with low y-coordinate
70
71
scenery_init()72 void scenery_init()
73 {
74 {
75 P("scenery_init\n");
76 // sanitize Flags.TreeType
77 int *a;
78 int n;
79 csvpos(Flags.TreeType,&a,&n);
80 int i;
81 int *b = (int *)malloc(sizeof(int)*n);
82 int m = 0;
83 for (i=0; i<n; i++)
84 {
85 if(a[i] >=0 && a[i] <= MAXTREETYPE)
86 {
87 b[m] = a[i];
88 m++;
89 }
90 }
91 free(Flags.TreeType);
92 vsc(&Flags.TreeType,b,m);
93 WriteFlags();
94 free(a);
95 free(b);
96 }
97 P("treecolor: %s\n",Flags.TreeColor);
98 setTreeScale();
99 P("treeScale: %f\n",treeScale);
100 //global.TreeRegion = XCreateRegion();
101 global.TreeRegion = cairo_region_create();
102 InitTreePixmaps();
103 add_to_mainloop(PRIORITY_DEFAULT, time_initbaum, do_initbaum);
104 }
105
setTreeScale()106 void setTreeScale()
107 {
108 treeScale = LocalScale*0.01*Flags.Scale*global.WindowScale;
109 }
110
compartrees(const void * a,const void * b)111 int compartrees(const void *a, const void *b)
112 {
113 Treeinfo *ta = *(Treeinfo **)a;
114 Treeinfo *tb = *(Treeinfo **)b;
115 P("compartrees %d %d %d %d\n",ta->y, tb->y, ta->h, tb->h);
116 return ta->y + ta->h*ta->scale - tb->y - tb->h*tb->scale;
117 }
118
scenery_draw(cairo_t * cr)119 int scenery_draw(cairo_t *cr)
120 {
121 int i;
122
123 if(Flags.NoTrees)
124 return TRUE;
125 for (i=0; i<NTrees; i++)
126 {
127 Treeinfo *tree = Trees[i];
128 P("scenery: %d\n",tree->y+tree->h);
129 cairo_set_source_surface (cr, tree->surface, tree->x, tree->y);
130 my_cairo_paint_with_alpha(cr,ALPHA);
131 }
132 return TRUE;
133 }
134
scenery_ui()135 void scenery_ui()
136 {
137 UIDOS(TreeType , RedrawTrees(););
138 UIDO (DesiredNumberOfTrees , RedrawTrees(););
139 UIDO (TreeFill , RedrawTrees(););
140 UIDO (NoTrees , if(!global.IsDouble) RedrawTrees(););
141 UIDOS(TreeColor , ReInitTree0(););
142 UIDO (Overlap , RedrawTrees(););
143
144 static int prev = 100;
145 if(ScaleChanged(&prev))
146 {
147 setTreeScale();
148 RedrawTrees();
149 }
150
151 }
152
RedrawTrees()153 void RedrawTrees()
154 {
155 Newtrees = 1; // this signals initbaum to recreate the trees
156 reinit_treesnow_region();
157 ClearScreen();
158 }
159
EraseTrees()160 void EraseTrees()
161 {
162 RedrawTrees();
163 }
164
tree_surface(int flip,const char ** xpm,float scale)165 cairo_surface_t *tree_surface(int flip, const char **xpm, float scale)
166 {
167 P("xpm[2]: %s\n",xpm[2]);
168 GdkPixbuf *pixbuf, *pixbuf1;
169 pixbuf1 = gdk_pixbuf_new_from_xpm_data((const char **)xpm);
170 if (flip)
171 {
172 pixbuf = gdk_pixbuf_flip(pixbuf1,1);
173 g_clear_object(&pixbuf1);
174 }
175 else
176 pixbuf = pixbuf1;
177
178 int w,h;
179 sscanf(xpm[0],"%d %d",&w,&h);
180 P("tree_surface: %d %d %f\n",w,h,scale);
181 w *= scale;
182 h *= scale;
183 if (w < 1) w = 1;
184 if (h < 1) h = 1;
185 if (w == 1 && h == 1) h = 2;
186 GdkPixbuf *pixbufscaled = gdk_pixbuf_scale_simple(pixbuf,w,h,GDK_INTERP_HYPER);
187 cairo_surface_t *surface = gdk_cairo_surface_create_from_pixbuf (pixbufscaled, 0, NULL);
188 g_clear_object(&pixbuf);
189 g_clear_object(&pixbufscaled);
190 return surface;
191 }
192
create_tree_dimensions(int tt)193 void create_tree_dimensions(int tt)
194 {
195 sscanf(xpmtrees[tt][0],"%d %d",&TreeWidth[tt],&TreeHeight[tt]);
196 }
197
198 // fallen snow and trees must have been initialized
199 // tree coordinates and so are recalculated here, in anticipation
200 // of a changed window size
201 // The function returns immediately if Newtrees==0, otherwize an attempt
202 // is done to place the DesiredNumberOfTrees
do_initbaum(void * d)203 int do_initbaum(void *d)
204 {
205 (void)d;
206 if (Flags.Done)
207 return FALSE;
208 P("%d initbaum %d %d\n",global.counter++,Newtrees, Flags.NoTrees);
209
210 static int count = 0;
211
212 if(global.RemoveFluff && count++ > 2)
213 global.RemoveFluff = 0;
214
215 if (Flags.NoTrees || Newtrees == 0)
216 return TRUE;
217
218 P("%d initbaum really...\n",global.counter++);
219 ClearScreen();
220 Newtrees = 0;
221
222 int i,h,w;
223
224 for (i=0; i<NTrees; i++)
225 free(Trees[i]);
226 free(Trees);
227 Trees = NULL;
228
229 global.RemoveFluff = 1;
230 count = 0;
231
232
233 cairo_region_destroy(global.gSnowOnTreesRegion);
234 cairo_region_destroy(global.TreeRegion);
235
236 global.gSnowOnTreesRegion = cairo_region_create();
237 global.TreeRegion = cairo_region_create();
238
239 // determine which trees are to be used
240 //
241 int *tmptreetype, ntemp;
242 if(TreeRead)
243 {
244 TreeType = (int *)realloc(TreeType,1*sizeof(*TreeType));
245 TreeType[0] = 0;
246 }
247 else
248 {
249 if (!strcmp("all",Flags.TreeType))
250 // user wants all treetypes
251 {
252 ntemp = 1+MAXTREETYPE;
253 tmptreetype = (int *)malloc(sizeof(*tmptreetype)*ntemp);
254 int i;
255 for (i=0; i<ntemp; i++)
256 tmptreetype[i] = i;
257 }
258 else if (strlen(Flags.TreeType) == 0)
259 // default: use 1..MAXTREETYPE
260 {
261 ntemp = MAXTREETYPE;
262 tmptreetype = (int *)malloc(sizeof(*tmptreetype)*ntemp);
263 int i;
264 for (i=0; i<ntemp; i++)
265 tmptreetype[i] = i+1;
266 }
267 else
268 {
269 // decode string like "1,1,3,2,4"
270 csvpos(Flags.TreeType,&tmptreetype,&ntemp);
271 }
272
273 NtreeTypes = 0;
274 for (i=0; i<ntemp; i++)
275 {
276 if (tmptreetype[i] >=0 && tmptreetype[i]<=MAXTREETYPE)
277 {
278 int j;
279 int unique = 1;
280 // investigate if this is already contained in TreeType.
281 // if so, do not use it. Note that this algorithm is not
282 // good scalable, when ntemp is large (100 ...) one should
283 // consider an algorithm involving qsort()
284 //
285 for (j=0; j<NtreeTypes; j++)
286 if (tmptreetype[i] == TreeType[j])
287 {
288 unique = 0;
289 break;
290 }
291 if (unique)
292 {
293 TreeType = (int *)realloc(TreeType,(NtreeTypes+1)*sizeof(*TreeType));
294 TreeType[NtreeTypes] = tmptreetype[i];
295 NtreeTypes++;
296 }
297 }
298 }
299 if(NtreeTypes == 0)
300 {
301 TreeType = (int *)realloc(TreeType,sizeof(*TreeType));
302 TreeType[0] = DEFAULTTREETYPE;
303 NtreeTypes++;
304 }
305 free(tmptreetype);
306 }
307
308 // determine placement of trees and NTrees:
309
310 NTrees = 0;
311 for (i=0; i< 4*Flags.DesiredNumberOfTrees; i++) // no overlap permitted in certain cases
312 {
313 if (NTrees >= Flags.DesiredNumberOfTrees)
314 break;
315
316 int tt = TreeType[randint(NtreeTypes)];
317 w = TreeWidth[tt];
318 h = TreeHeight[tt];
319
320 int y1 = global.SnowWinHeight - global.MaxScrSnowDepth - h*treeScale;
321 int y2 = global.SnowWinHeight*(1.0 - 0.01*Flags.TreeFill);
322 if (y2>y1) y1=y2+1;
323
324 int x = randint(global.SnowWinWidth-w*treeScale);
325 int y = y1 - randint(y1-y2);
326
327 float myScale = (1-MinScale)*(y - y2)/(y1 - y2) + MinScale;
328 P("%d myScale: %d %d %d %f\n",global.counter++,y,y1,y2,myScale);
329 myScale *=treeScale;
330 cairo_rectangle_int_t grect = {x-1,y-1,(int)(myScale*w+2),(int)(myScale*h+2)};
331 cairo_region_overlap_t in = cairo_region_contains_rectangle(global.TreeRegion,&grect);
332
333 // no overlap considerations if:
334
335 if(!global.IsDouble || !Flags.Overlap)
336 if (in == CAIRO_REGION_OVERLAP_IN || in == CAIRO_REGION_OVERLAP_PART)
337 {
338 P("skiptree\n");
339 continue;
340 }
341
342 int flop = (drand48()>0.5);
343
344 Treeinfo *tree = (Treeinfo *)malloc(sizeof(Treeinfo));
345 tree->x = x;
346 tree->y = y;
347 tree->w = w;
348 tree->h = h;
349 tree->type = tt;
350 tree->rev = flop;
351 tree->scale = myScale;
352 tree->surface = NULL;
353 P("tree: %d %d %d %d %d %p\n",tree->x, tree->y, tree->type, tree->rev, NTrees,(void *)tree);
354
355 cairo_region_t *r;
356
357 switch(tt)
358 {
359 case -SOMENUMBER:
360 r = gregionfromxpm((const char **)TreeXpm,tree->rev,tree->scale);
361 break;
362 default:
363 r = gregionfromxpm(xpmtrees[tt],tree->rev,tree->scale);
364 break;
365 }
366 cairo_region_translate(r,x,y);
367 cairo_region_union(global.TreeRegion,r);
368 cairo_region_destroy(r);
369
370 NTrees++;
371 Trees = (Treeinfo **)realloc(Trees,NTrees*sizeof(Treeinfo*));
372 Trees[NTrees-1] = tree;
373 }
374
375 // sort using y+h values of trees, so that higher trees are painted first
376 P("%d qsort: %d %ld\n",counter++,NTrees,sizeof(*Trees));
377 qsort(Trees,NTrees,sizeof(*Trees),compartrees);
378 create_tree_surfaces();
379 ReInitTree0();
380
381 global.OnTrees = 0;
382 return TRUE;
383 }
384
create_tree_surfaces()385 void create_tree_surfaces()
386 {
387 int i;
388 for (i=0; i<NTrees; i++)
389 {
390 Treeinfo *tree = Trees[i];
391 if(tree->surface)
392 cairo_surface_destroy(tree->surface);
393 if(TreeRead)
394 tree->surface = tree_surface(tree->rev, (const char **)TreeXpm,tree->scale);
395 else
396 tree->surface = tree_surface(tree->rev, (const char **)xpmtrees[tree->type],tree->scale);
397 }
398 }
399
InitTreePixmaps()400 void InitTreePixmaps()
401 {
402 XpmAttributes attributes;
403 attributes.valuemask = XpmDepth;
404 attributes.depth = global.SnowWinDepth;
405 char *path = NULL;
406 FILE *f = HomeOpen("xsnow/pixmaps/tree.xpm","r",&path);
407 if (f)
408 {
409 // there seems to be a local definition of tree
410 // set TreeType to some number, so we can respond accordingly
411 TreeType = (int *)realloc(TreeType,sizeof(*TreeType));
412 NtreeTypes = 1;
413 TreeRead = 1;
414 int rc = XpmReadFileToData(path,&TreeXpm);
415 if(rc == XpmSuccess)
416 {
417 int i;
418 for(i=0; i<2; i++)
419 {
420 iXpmCreatePixmapFromData(global.display, global.SnowWin, (const char **)TreeXpm,
421 &TreePixmap[0][i], &TreeMaskPixmap[0][i], &attributes,i);
422 create_tree_dimensions(0);
423 }
424 sscanf(*TreeXpm,"%d %d", &TreeWidth[0],&TreeHeight[0]);
425 P("treexpm %d %d\n",TreeWidth[0],TreeHeight[0]);
426 printf("using external tree: %s\n",path);
427 if (!Flags.NoMenu)
428 printf("Disabling menu.\n");
429 Flags.NoMenu = 1;
430 }
431 else
432 {
433 printf("Invalid external xpm for tree given: %s\n",path);
434 exit(1);
435 }
436 fclose(f);
437 }
438 else
439 {
440 int i;
441 for(i=0; i<2; i++)
442 {
443 int tt;
444 for (tt=0; tt<=MAXTREETYPE; tt++)
445 {
446 iXpmCreatePixmapFromData(global.display, global.SnowWin, xpmtrees[tt],
447 &TreePixmap[tt][i],&TreeMaskPixmap[tt][i],&attributes,i);
448 sscanf(xpmtrees[tt][0],"%d %d",&TreeWidth[tt],&TreeHeight[tt]);
449 create_tree_dimensions(tt);
450 }
451 }
452 }
453 if(path)
454 free(path);
455 global.OnTrees = 0;
456 }
457
458
459 //
460 // apply TreeColor to xpmtree[0] and xpmtree[1]
ReInitTree0()461 void ReInitTree0()
462 {
463 P("Reinittree0 %s\n",Flags.TreeColor);
464 XpmAttributes attributes;
465 attributes.valuemask = XpmDepth;
466 attributes.depth = global.SnowWinDepth;
467 int i;
468 int n = TreeHeight[0]+3;
469 char **xpmtmp = (char **)malloc(n*sizeof(char *));
470 int j;
471 for (j=0; j<2; j++)
472 xpmtmp[j] = strdup(xpmtrees[0][j]);
473 xpmtmp[2] = strdup(". c ");
474 xpmtmp[2] = (char *)realloc(xpmtmp[2],strlen(xpmtmp[2])+strlen(Flags.TreeColor)+1);
475 strcat(xpmtmp[2],Flags.TreeColor);
476 for(j=3; j<n; j++)
477 xpmtmp[j] = strdup(xpmtrees[0][j]);
478 //P("xpmtmp:\n");xpm_print(xpmtmp);
479 for(i=0; i<2; i++)
480 {
481 XFreePixmap(global.display,TreePixmap[0][i]);
482 iXpmCreatePixmapFromData(global.display, global.SnowWin, (const char **)xpmtmp,
483 &TreePixmap[0][i],&TreeMaskPixmap[0][i],&attributes,i);
484 create_tree_dimensions(0);
485 }
486 for (i=0; i<NTrees; i++)
487 {
488 Treeinfo *tree = Trees[i];
489 P("tree->type %d\n",tree->type);
490 if (tree->type == 0)
491 {
492 tree->surface = tree_surface(tree->rev,(const char **)xpmtmp,tree->scale);
493 }
494 }
495
496 for (j=0; j<n; j++)
497 free(xpmtmp[j]);
498 free(xpmtmp);
499 }
500
501