40da1cd3d87402d990c9bf6cdc4c8695a9553303
[pandora-libraries.git] / minimenu / mmui.c
1
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <limits.h>
5 #include "SDL.h"
6 #include "SDL_audio.h"
7 #include "SDL_image.h"
8 #include "SDL_ttf.h"
9 #include "SDL_gfxPrimitives.h"
10 #include "SDL_rotozoom.h"
11
12 #include "pnd_conf.h"
13 #include "pnd_logger.h"
14 #include "pnd_pxml.h"
15 #include "pnd_container.h"
16 #include "pnd_discovery.h"
17 #include "pnd_apps.h"
18
19 #include "mmenu.h"
20 #include "mmcat.h"
21 #include "mmcache.h"
22 #include "mmui.h"
23 #include "mmwrapcmd.h"
24
25 /* SDL
26  */
27 SDL_Surface *sdl_realscreen = NULL;
28 unsigned int sdl_ticks = 0;
29
30 /* app state
31  */
32 unsigned short int g_scale = 1; // 1 == noscale
33
34 SDL_Surface *g_imgcache [ IMG_MAX ];
35
36 TTF_Font *g_big_font = NULL;
37 TTF_Font *g_grid_font = NULL;
38 TTF_Font *g_detailtext_font = NULL;
39 TTF_Font *g_tab_font = NULL;
40
41 extern pnd_conf_handle g_conf;
42
43 /* current display state
44  */
45 int ui_rows_scrolled_down = 0;          // number of rows that should be missing from top of the display
46 mm_appref_t *ui_selected = NULL;
47 unsigned char ui_category = 0;          // current category
48 unsigned char ui_catshift = 0;          // how many cats are offscreen to the left
49
50 extern mm_category_t g_categories [ MAX_CATS ];
51 extern unsigned char g_categorycount;
52
53 static SDL_Surface *ui_scale_image ( SDL_Surface *s, unsigned int maxwidth, int maxheight ); // height -1 means ignore
54 static int ui_selected_index ( void );
55
56 static unsigned int ui_timer ( unsigned int interval ) {
57   sdl_ticks++;
58   return ( interval );
59 }
60
61 unsigned char ui_setup ( void ) {
62
63   /* set up SDL
64    */
65
66   SDL_Init ( SDL_INIT_EVERYTHING | SDL_INIT_NOPARACHUTE );
67
68   SDL_SetTimer ( 30, ui_timer ); // 30fps
69
70   SDL_JoystickOpen ( 0 ); // turn on joy-0
71
72   SDL_WM_SetCaption ( "mmenu", "mmenu" );
73
74   // hide the mouse cursor if we can
75   if ( SDL_ShowCursor ( -1 ) == 1 ) {
76     SDL_ShowCursor ( 0 );
77   }
78
79   atexit ( SDL_Quit );
80
81   // open up a surface
82   unsigned int svm = SDL_SWSURFACE /*| SDL_FULLSCREEN*/ /* 0*/;
83   if ( pnd_conf_get_as_int_d ( g_conf, "display.fullscreen", 0 ) > 0 ) {
84     svm |= SDL_FULLSCREEN;
85   }
86
87   sdl_realscreen =
88     SDL_SetVideoMode ( 800 * g_scale, 480 * g_scale, 16 /*bpp*/, svm );
89
90   if ( ! sdl_realscreen ) {
91     pnd_log ( pndn_error, "ERROR: Couldn't open SDL real screen; dieing." );
92     return ( 0 );
93   }
94
95   pnd_log ( pndn_debug, "Pixel format:" );
96   pnd_log ( pndn_debug, "bpp b: %u\n", sdl_realscreen -> format -> BitsPerPixel );
97   pnd_log ( pndn_debug, "bpp B: %u\n", sdl_realscreen -> format -> BytesPerPixel );
98   pnd_log ( pndn_debug, "R mask: %08x\n", sdl_realscreen -> format -> Rmask );
99   pnd_log ( pndn_debug, "G mask: %08x\n", sdl_realscreen -> format -> Gmask );
100   pnd_log ( pndn_debug, "B mask: %08x\n", sdl_realscreen -> format -> Bmask );
101
102 #if 0 // audio
103   {
104     SDL_AudioSpec fmt;
105
106     /* Set 16-bit stereo audio at 22Khz */
107     fmt.freq = 44100; //22050;
108     fmt.format = AUDIO_S16; //AUDIO_S16;
109     fmt.channels = 1;
110     fmt.samples = 2048;        /* A good value for games */
111     fmt.callback = mixaudio;
112     fmt.userdata = NULL;
113
114     /* Open the audio device and start playing sound! */
115     if ( SDL_OpenAudio ( &fmt, NULL ) < 0 ) {
116       zotlog ( "Unable to open audio: %s\n", SDL_GetError() );
117       exit ( 1 );
118     }
119
120     SDL_PauseAudio ( 0 );
121   }
122 #endif
123
124   // images
125   //IMG_Init ( IMG_INIT_JPG | IMG_INIT_PNG );
126
127   /* fonts
128    */
129
130   // init
131   if ( TTF_Init() == -1 ) {
132     pnd_log ( pndn_error, "ERROR: Couldn't set up SDL TTF lib\n" );
133     return ( 0 ); // couldn't set up SDL TTF
134   }
135
136   char fullpath [ PATH_MAX ];
137   // big font
138   sprintf ( fullpath, "%s/%s", pnd_conf_get_as_char ( g_conf, MMENU_ARTPATH ), pnd_conf_get_as_char ( g_conf, "minimenu.font" ) );
139   g_big_font = TTF_OpenFont ( fullpath, pnd_conf_get_as_int_d ( g_conf, "minimenu.font_ptsize", 24 ) );
140   if ( ! g_big_font ) {
141     pnd_log ( pndn_error, "ERROR: Couldn't load font '%s' for size %u\n",
142               pnd_conf_get_as_char ( g_conf, "minimenu.font" ), pnd_conf_get_as_int_d ( g_conf, "minimenu.font_ptsize", 24 ) );
143     return ( 0 ); // couldn't set up SDL TTF
144   }
145
146   // grid font
147   sprintf ( fullpath, "%s/%s", pnd_conf_get_as_char ( g_conf, MMENU_ARTPATH ), pnd_conf_get_as_char ( g_conf, MMENU_GRID_FONT ) );
148   g_grid_font = TTF_OpenFont ( fullpath, pnd_conf_get_as_int_d ( g_conf, MMENU_GRID_FONTSIZE, 10 ) );
149   if ( ! g_grid_font ) {
150     pnd_log ( pndn_error, "ERROR: Couldn't load font '%s' for size %u\n",
151               pnd_conf_get_as_char ( g_conf, MMENU_GRID_FONT ), pnd_conf_get_as_int_d ( g_conf, MMENU_GRID_FONTSIZE, 10 ) );
152     return ( 0 ); // couldn't set up SDL TTF
153   }
154
155   // detailtext font
156   sprintf ( fullpath, "%s/%s", pnd_conf_get_as_char ( g_conf, MMENU_ARTPATH ), pnd_conf_get_as_char ( g_conf, "detailtext.font" ) );
157   g_detailtext_font = TTF_OpenFont ( fullpath, pnd_conf_get_as_int_d ( g_conf, "detailtext.font_ptsize", 10 ) );
158   if ( ! g_detailtext_font ) {
159     pnd_log ( pndn_error, "ERROR: Couldn't load font '%s' for size %u\n",
160               pnd_conf_get_as_char ( g_conf, "detailtext.font" ), pnd_conf_get_as_int_d ( g_conf, "detailtext.font_ptsize", 10 ) );
161     return ( 0 ); // couldn't set up SDL TTF
162   }
163
164   // tab font
165   sprintf ( fullpath, "%s/%s", pnd_conf_get_as_char ( g_conf, MMENU_ARTPATH ), pnd_conf_get_as_char ( g_conf, "tabs.font" ) );
166   g_tab_font = TTF_OpenFont ( fullpath, pnd_conf_get_as_int_d ( g_conf, "tabs.font_ptsize", 10 ) );
167   if ( ! g_tab_font ) {
168     pnd_log ( pndn_error, "ERROR: Couldn't load font '%s' for size %u\n",
169               pnd_conf_get_as_char ( g_conf, "tabs.font" ), pnd_conf_get_as_int_d ( g_conf, "tabs.font_ptsize", 10 ) );
170     return ( 0 ); // couldn't set up SDL TTF
171   }
172
173   return ( 1 );
174 }
175
176 mm_imgcache_t g_imagecache [ IMG_TRUEMAX ] = {
177   { IMG_BACKGROUND_800480,    "graphics.IMG_BACKGROUND_800480" },
178   { IMG_BACKGROUND_TABMASK,   "graphics.IMG_BACKGROUND_TABMASK" },
179   { IMG_DETAIL_PANEL,         "graphics.IMG_DETAIL_PANEL" },
180   { IMG_DETAIL_BG,            "graphics.IMG_DETAIL_BG" },
181   { IMG_SELECTED_ALPHAMASK,   "graphics.IMG_SELECTED_ALPHAMASK" },
182   { IMG_TAB_SEL,              "graphics.IMG_TAB_SEL" },
183   { IMG_TAB_UNSEL,            "graphics.IMG_TAB_UNSEL" },
184   { IMG_ICON_MISSING,         "graphics.IMG_ICON_MISSING" },
185   { IMG_SELECTED_HILITE,      "graphics.IMG_SELECTED_HILITE" },
186   { IMG_PREVIEW_MISSING,      "graphics.IMG_PREVIEW_MISSING" },
187   { IMG_ARROW_UP,             "graphics.IMG_ARROW_UP", },
188   { IMG_ARROW_DOWN,           "graphics.IMG_ARROW_DOWN", },
189   { IMG_ARROW_SCROLLBAR,      "graphics.IMG_ARROW_SCROLLBAR", },
190   { IMG_MAX,                  NULL },
191 };
192
193 unsigned char ui_imagecache ( char *basepath ) {
194   unsigned int i;
195   char fullpath [ PATH_MAX ];
196
197   // loaded
198
199   for ( i = 0; i < IMG_MAX; i++ ) {
200
201     if ( g_imagecache [ i ].id != i ) {
202       pnd_log ( pndn_error, "ERROR: Internal table mismatch during caching [%u]\n", i );
203       exit ( -1 );
204     }
205
206     char *filename = pnd_conf_get_as_char ( g_conf, g_imagecache [ i ].confname );
207
208     if ( ! filename ) {
209       pnd_log ( pndn_error, "ERROR: Missing filename in conf for key: %s\n", g_imagecache [ i ].confname );
210       return ( 0 );
211     }
212
213     sprintf ( fullpath, "%s/%s", basepath, filename );
214
215     if ( ! ( g_imagecache [ i ].i = IMG_Load ( fullpath ) ) ) {
216       pnd_log ( pndn_error, "ERROR: Couldn't load static cache image: %s\n", fullpath );
217       return ( 0 );
218     }
219
220   } // for
221
222   // generated
223   //g_imagecache [ IMG_SELECTED_ALPHAMASK ].i = SDL_CreateRGBSurface ( SDL_SWSURFACE, 60, 60, 32, 0xFF0000, 0x00FF00, 0xFF, 0xFF000000 );
224   //boxRGBA ( g_imagecache [ IMG_SELECTED_ALPHAMASK ].i, 0, 0, 60, 60, 100, 100, 100, 250 );
225
226   // post processing
227   //
228
229   // scale icons
230   g_imagecache [ IMG_SELECTED_ALPHAMASK ].i = ui_scale_image ( g_imagecache [ IMG_SELECTED_ALPHAMASK ].i, pnd_conf_get_as_int_d ( g_conf, "grid.icon_max_width", 50 ), -1 );
231   g_imagecache [ IMG_ICON_MISSING ].i = ui_scale_image ( g_imagecache [ IMG_ICON_MISSING ].i, pnd_conf_get_as_int_d ( g_conf, "grid.icon_max_width", 50 ), -1 );
232   // scale text hilight
233   g_imagecache [ IMG_SELECTED_HILITE ].i = ui_scale_image ( g_imagecache [ IMG_SELECTED_HILITE ].i, pnd_conf_get_as_int_d ( g_conf, "grid.text_width", 50 ), -1 );
234   // scale preview no-pic
235   g_imagecache [ IMG_PREVIEW_MISSING ].i = ui_scale_image ( g_imagecache [ IMG_PREVIEW_MISSING ].i,
236                                                             pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_width", 50 ),
237                                                             pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_height", 50 ) );
238
239   // set alpha on detail panel
240   SDL_SetAlpha ( g_imagecache [ IMG_DETAIL_BG ].i, SDL_SRCALPHA, pnd_conf_get_as_int_d ( g_conf, "display.detail_bg_alpha", 50 ) );
241
242   return ( 1 );
243 } // ui_imagecache
244
245 void ui_render ( unsigned int render_mask ) {
246
247   // 800x480:
248   // divide width: 550 / 250
249   // divide left side: 5 columns == 110px width
250   //   20px buffer either side == 70px wide icon + 20 + 20?
251
252   unsigned int icon_rows;
253
254 #define MAXRECTS 200
255   SDL_Rect rects [ MAXRECTS ], src;
256   SDL_Rect *dest = rects;
257   bzero ( dest, sizeof(SDL_Rect)*MAXRECTS );
258
259   unsigned int row, displayrow, col;
260   mm_appref_t *appiter;
261
262   unsigned int screen_width = pnd_conf_get_as_int_d ( g_conf, "display.screen_width", 800 );
263
264   unsigned char row_max = pnd_conf_get_as_int_d ( g_conf, MMENU_DISP_ROWMAX, 4 );
265   unsigned char col_max = pnd_conf_get_as_int_d ( g_conf, MMENU_DISP_COLMAX, 5 );
266
267   unsigned int font_rgba_r = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_r", 200 );
268   unsigned int font_rgba_g = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_g", 200 );
269   unsigned int font_rgba_b = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_b", 200 );
270   unsigned int font_rgba_a = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_a", 100 );
271
272   unsigned int grid_offset_x = pnd_conf_get_as_int ( g_conf, "grid.grid_offset_x" );
273   unsigned int grid_offset_y = pnd_conf_get_as_int ( g_conf, "grid.grid_offset_y" );
274
275   unsigned int icon_offset_x = pnd_conf_get_as_int ( g_conf, "grid.icon_offset_x" );
276   unsigned int icon_offset_y = pnd_conf_get_as_int ( g_conf, "grid.icon_offset_y" );
277
278   unsigned int text_width = pnd_conf_get_as_int ( g_conf, "grid.text_width" );
279   unsigned int text_clip_x = pnd_conf_get_as_int ( g_conf, "grid.text_clip_x" );
280   unsigned int text_offset_x = pnd_conf_get_as_int ( g_conf, "grid.text_offset_x" );
281   unsigned int text_offset_y = pnd_conf_get_as_int ( g_conf, "grid.text_offset_y" );
282
283   unsigned int cell_width = pnd_conf_get_as_int ( g_conf, "grid.cell_width" );
284   unsigned int cell_height = pnd_conf_get_as_int ( g_conf, "grid.cell_height" );
285
286   // how many total rows do we need?
287   icon_rows = g_categories [ ui_category ].refcount / col_max;
288   if ( g_categories [ ui_category ].refcount % col_max > 0 ) {
289     icon_rows++;
290   }
291
292   // if no selected app yet, select the first one
293 #if 0
294   if ( ! ui_selected ) {
295     ui_selected = g_categories [ ui_category ].refs;
296   }
297 #endif
298
299   // ensure selection is visible
300   if ( ui_selected ) {
301
302     int index = ui_selected_index();
303     int topleft = col_max * ui_rows_scrolled_down;
304     int botright = ( col_max * ( ui_rows_scrolled_down + row_max ) - 1 );
305
306     //pnd_log ( PND_LOG_DEFAULT, "index %u tl %u br %u\n", index, topleft, botright );
307
308     if ( index < topleft ) {
309       ui_rows_scrolled_down -= pnd_conf_get_as_int_d ( g_conf, "grid.scroll_increment", 1 );
310     } else if ( index > botright ) {
311       ui_rows_scrolled_down += pnd_conf_get_as_int_d ( g_conf, "grid.scroll_increment", 1 );
312     }
313
314     if ( ui_rows_scrolled_down < 0 ) {
315       ui_rows_scrolled_down = 0;
316     } else if ( ui_rows_scrolled_down > icon_rows ) {
317       ui_rows_scrolled_down = icon_rows;
318     }
319
320   } // ensire visible
321
322   // render background
323   if ( g_imagecache [ IMG_BACKGROUND_800480 ].i ) {
324     dest -> x = 0;
325     dest -> y = 0;
326     dest -> w = sdl_realscreen -> w;
327     dest -> h = sdl_realscreen -> h;
328     SDL_BlitSurface ( g_imagecache [ IMG_BACKGROUND_800480 ].i, NULL /* whole image */, sdl_realscreen, dest /* 0,0 */ );
329     //SDL_UpdateRects ( sdl_realscreen, 1, &dest );
330     dest++;
331   }
332
333   // tabmask
334   if ( g_imagecache [ IMG_BACKGROUND_TABMASK ].i ) {
335     dest -> x = 0;
336     dest -> y = 0;
337     dest -> w = sdl_realscreen -> w;
338     dest -> h = sdl_realscreen -> h;
339     SDL_BlitSurface ( g_imagecache [ IMG_BACKGROUND_TABMASK ].i, NULL /* whole image */, sdl_realscreen, dest /* 0,0 */ );
340     //SDL_UpdateRects ( sdl_realscreen, 1, &dest );
341     dest++;
342   }
343
344   // tabs
345   if ( g_imagecache [ IMG_TAB_SEL ].i && g_imagecache [ IMG_TAB_UNSEL ].i ) {
346     unsigned int tab_width = pnd_conf_get_as_int ( g_conf, "tabs.tab_width" );
347     unsigned int tab_height = pnd_conf_get_as_int ( g_conf, "tabs.tab_height" );
348     unsigned int tab_offset_x = pnd_conf_get_as_int ( g_conf, "tabs.tab_offset_x" );
349     unsigned int tab_offset_y = pnd_conf_get_as_int ( g_conf, "tabs.tab_offset_y" );
350     unsigned int text_offset_x = pnd_conf_get_as_int ( g_conf, "tabs.text_offset_x" );
351     unsigned int text_offset_y = pnd_conf_get_as_int ( g_conf, "tabs.text_offset_y" );
352     unsigned int text_width = pnd_conf_get_as_int ( g_conf, "tabs.text_width" );
353
354     for ( col = ui_catshift;
355           col < ( 
356                    ( screen_width / tab_width ) < g_categorycount ? ( screen_width / tab_width ) + ui_catshift : g_categorycount + ui_catshift
357                 );
358           col++ )
359     {
360
361       SDL_Surface *s;
362       if ( col == ui_category ) {
363         s = g_imagecache [ IMG_TAB_SEL ].i;
364       } else {
365         s = g_imagecache [ IMG_TAB_UNSEL ].i;
366       }
367
368       // draw tab
369       src.x = 0;
370       src.y = 0;
371       src.w = tab_width;
372       src.h = tab_height;
373       dest -> x = tab_offset_x + ( (col-ui_catshift) * tab_width );
374       dest -> y = tab_offset_y;
375       //pnd_log ( pndn_debug, "tab %u at %ux%u\n", col, dest.x, dest.y );
376       SDL_BlitSurface ( s, &src, sdl_realscreen, dest );
377       //SDL_UpdateRects ( sdl_realscreen, 1, &dest );
378       dest++;
379
380       // draw text
381       SDL_Surface *rtext;
382       SDL_Color tmpfontcolor = { font_rgba_r, font_rgba_g, font_rgba_b, font_rgba_a };
383       rtext = TTF_RenderText_Blended ( g_tab_font, g_categories [ col ].catname, tmpfontcolor );
384       src.x = 0;
385       src.y = 0;
386       src.w = rtext -> w < text_width ? rtext -> w : text_width;
387       src.h = rtext -> h;
388       dest -> x = tab_offset_x + ( (col-ui_catshift) * tab_width ) + text_offset_x;
389       dest -> y = tab_offset_y + text_offset_y;
390       SDL_BlitSurface ( rtext, &src, sdl_realscreen, dest );
391       //SDL_UpdateRects ( sdl_realscreen, 1, &dest );
392       dest++;
393
394     } // for
395
396   } // tabs
397
398   // scroll bars and arrows
399   {
400     unsigned char show_bar = 0;
401
402     // up?
403     if ( ui_rows_scrolled_down && g_imagecache [ IMG_ARROW_UP ].i ) {
404       dest -> x = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_up_x", 450 );
405       dest -> y = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_up_y", 80 );
406       SDL_BlitSurface ( g_imagecache [ IMG_ARROW_UP ].i, NULL /* whole image */, sdl_realscreen, dest );
407       //SDL_UpdateRects ( sdl_realscreen, 1, &dest );
408       dest++;
409
410       show_bar = 1;
411     }
412
413     // down?
414     if ( ui_rows_scrolled_down + row_max < icon_rows && g_imagecache [ IMG_ARROW_DOWN ].i ) {
415       dest -> x = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_down_x", 450 );
416       dest -> y = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_down_y", 80 );
417       SDL_BlitSurface ( g_imagecache [ IMG_ARROW_DOWN ].i, NULL /* whole image */, sdl_realscreen, dest );
418       //SDL_UpdateRects ( sdl_realscreen, 1, &dest );
419       dest++;
420
421       show_bar = 1;
422     }
423
424     if ( show_bar ) {
425       // show scrollbar as well
426       src.x = 0;
427       src.y = 0;
428       src.w = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_bar_clip_w", 10 );
429       src.h = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_bar_clip_h", 100 );
430       dest -> x = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_bar_x", 450 );
431       dest -> y = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_bar_y", 100 );
432       SDL_BlitSurface ( g_imagecache [ IMG_ARROW_SCROLLBAR ].i, &src /* whole image */, sdl_realscreen, dest );
433       //SDL_UpdateRects ( sdl_realscreen, 1, &dest );
434       dest++;
435     } // bar
436
437   } // scroll bars
438
439   // render detail pane bg
440   if ( pnd_conf_get_as_int_d ( g_conf, "detailpane.show", 1 ) ) {
441
442     if ( g_imagecache [ IMG_DETAIL_BG ].i ) {
443       src.x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 );
444       src.y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 );
445       src.w = ((SDL_Surface*)(g_imagecache [ IMG_DETAIL_PANEL ].i)) -> w;
446       src.h = ((SDL_Surface*)(g_imagecache [ IMG_DETAIL_PANEL ].i)) -> h;
447       dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 );
448       dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 );
449       SDL_BlitSurface ( g_imagecache [ IMG_DETAIL_BG ].i, &src, sdl_realscreen, dest );
450       //SDL_UpdateRects ( sdl_realscreen, 1, &dest );
451       dest++;
452     }
453
454     // render detail pane
455     if ( g_imagecache [ IMG_DETAIL_PANEL ].i ) {
456       dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 );
457       dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 );
458       SDL_BlitSurface ( g_imagecache [ IMG_DETAIL_PANEL ].i, NULL /* whole image */, sdl_realscreen, dest );
459       //SDL_UpdateRects ( sdl_realscreen, 1, &dest );
460       dest++;
461     }
462
463   } // detailpane frame/bg
464
465   // anything to render?
466   if ( g_categories [ ui_category ].refs ) {
467
468     appiter = g_categories [ ui_category ].refs;
469     row = 0;
470     displayrow = 0;
471
472     // until we run out of apps, or run out of space
473     while ( appiter != NULL ) {
474
475       for ( col = 0; col < col_max && appiter != NULL; col++ ) {
476
477         // do we even need to render it? or are we suppressing it due to rows scrolled off the top?
478         if ( row >= ui_rows_scrolled_down ) {
479
480           // selected? show hilights
481           if ( appiter == ui_selected ) {
482             // icon
483             dest -> x = grid_offset_x + ( col * cell_width ) + icon_offset_x;
484             dest -> y = grid_offset_y + ( displayrow * cell_height ) + icon_offset_y;
485             SDL_BlitSurface ( g_imagecache [ IMG_SELECTED_ALPHAMASK ].i, NULL /* all */, sdl_realscreen, dest );
486             //SDL_UpdateRects ( sdl_realscreen, 1, &dest );
487             dest++;
488             // text
489             dest -> x = grid_offset_x + ( col * cell_width ) + text_clip_x;
490             dest -> y = grid_offset_y + ( displayrow * cell_height ) + pnd_conf_get_as_int ( g_conf, "grid.text_hilite_offset_y" );
491             SDL_BlitSurface ( g_imagecache [ IMG_SELECTED_HILITE ].i, NULL /* all */, sdl_realscreen, dest );
492             //SDL_UpdateRects ( sdl_realscreen, 1, &dest );
493             dest++;
494           } // selected?
495
496           // show icon
497           mm_cache_t *ic = cache_query_icon ( appiter -> ref -> unique_id );
498           SDL_Surface *iconsurface;
499           if ( ic ) {
500             iconsurface = ic -> i;
501           } else {
502             //pnd_log ( pndn_warning, "WARNING: TBD: Need Missin-icon icon for '%s'\n", IFNULL(appiter -> ref -> title_en,"No Name") );
503             iconsurface = g_imagecache [ IMG_ICON_MISSING ].i;
504           }
505           if ( iconsurface ) {
506             //pnd_log ( pndn_debug, "Got an icon for '%s'\n", IFNULL(appiter -> ref -> title_en,"No Name") );
507
508             src.x = 0;
509             src.y = 0;
510             src.w = 60;
511             src.h = 60;
512             dest -> x = grid_offset_x + ( col * cell_width ) + icon_offset_x;
513             dest -> y = grid_offset_y + ( displayrow * cell_height ) + icon_offset_y;
514
515             SDL_BlitSurface ( iconsurface, &src, sdl_realscreen, dest );
516             //SDL_UpdateRects ( sdl_realscreen, 1, &dest );
517             dest++;
518
519           }
520
521           // show text
522           if ( appiter -> ref -> title_en ) {
523             SDL_Surface *rtext;
524             SDL_Color tmpfontcolor = { font_rgba_r, font_rgba_g, font_rgba_b, font_rgba_a };
525             rtext = TTF_RenderText_Blended ( g_grid_font, appiter -> ref -> title_en, tmpfontcolor );
526             src.x = 0;
527             src.y = 0;
528             src.w = text_width < rtext -> w ? text_width : rtext -> w;
529             src.h = rtext -> h;
530             if ( rtext -> w > text_width ) {
531               dest -> x = grid_offset_x + ( col * cell_width ) + text_clip_x;
532             } else {
533               dest -> x = grid_offset_x + ( col * cell_width ) + text_offset_x - ( rtext -> w / 2 );
534             }
535             dest -> y = grid_offset_y + ( displayrow * cell_height ) + text_offset_y;
536             SDL_BlitSurface ( rtext, &src, sdl_realscreen, dest );
537             //SDL_UpdateRects ( sdl_realscreen, 1, &dest );
538             dest++;
539           }
540
541         } // display now? or scrolled away..
542
543         // next
544         appiter = appiter -> next;
545
546       } // for column 1...X
547
548       if ( row >= ui_rows_scrolled_down ) {
549         displayrow++;
550       }
551
552       row ++;
553
554       // are we done displaying rows?
555       if ( displayrow >= row_max ) {
556         break;
557       }
558
559     } // while
560
561   } else {
562     // no apps to render?
563     pnd_log ( pndn_rem, "No applications to render?\n" );
564   } // apps to renser?
565
566   // detail panel
567   if ( ui_selected ) {
568
569     unsigned int cell_offset_x = pnd_conf_get_as_int ( g_conf, "detailtext.cell_offset_x" );
570     unsigned int cell_offset_y = pnd_conf_get_as_int ( g_conf, "detailtext.cell_offset_y" );
571     unsigned int cell_width = pnd_conf_get_as_int ( g_conf, "detailtext.cell_width" );
572
573     unsigned int desty = cell_offset_y;
574
575     char buffer [ 256 ];
576
577     // full name
578     if ( ui_selected -> ref -> title_en ) {
579       SDL_Surface *rtext;
580       SDL_Color tmpfontcolor = { font_rgba_r, font_rgba_g, font_rgba_b, font_rgba_a };
581       rtext = TTF_RenderText_Blended ( g_detailtext_font, ui_selected -> ref -> title_en, tmpfontcolor );
582       src.x = 0;
583       src.y = 0;
584       src.w = rtext -> w < cell_width ? rtext -> w : cell_width;
585       src.h = rtext -> h;
586       dest -> x = cell_offset_x;
587       dest -> y = desty;
588       SDL_BlitSurface ( rtext, &src, sdl_realscreen, dest );
589       //SDL_UpdateRects ( sdl_realscreen, 1, &dest );
590       dest++;
591       desty += src.h;
592     }
593
594     // category
595     if ( ui_selected -> ref -> main_category ) {
596
597       sprintf ( buffer, "Category: %s", ui_selected -> ref -> main_category );
598
599       SDL_Surface *rtext;
600       SDL_Color tmpfontcolor = { font_rgba_r, font_rgba_g, font_rgba_b, font_rgba_a };
601       rtext = TTF_RenderText_Blended ( g_detailtext_font, buffer, tmpfontcolor );
602       src.x = 0;
603       src.y = 0;
604       src.w = rtext -> w < cell_width ? rtext -> w : cell_width;
605       src.h = rtext -> h;
606       dest -> x = cell_offset_x;
607       dest -> y = desty;
608       SDL_BlitSurface ( rtext, &src, sdl_realscreen, dest );
609       //SDL_UpdateRects ( sdl_realscreen, 1, &dest );
610       dest++;
611       desty += src.h;
612     }
613
614     // clock
615     if ( ui_selected -> ref -> clockspeed ) {
616
617       sprintf ( buffer, "CPU Clock: %s", ui_selected -> ref -> clockspeed );
618
619       SDL_Surface *rtext;
620       SDL_Color tmpfontcolor = { font_rgba_r, font_rgba_g, font_rgba_b, font_rgba_a };
621       rtext = TTF_RenderText_Blended ( g_detailtext_font, buffer, tmpfontcolor );
622       src.x = 0;
623       src.y = 0;
624       src.w = rtext -> w < cell_width ? rtext -> w : cell_width;
625       src.h = rtext -> h;
626       dest -> x = cell_offset_x;
627       dest -> y = desty;
628       SDL_BlitSurface ( rtext, &src, sdl_realscreen, dest );
629       //SDL_UpdateRects ( sdl_realscreen, 1, &dest );
630       dest++;
631       desty += src.h;
632     }
633
634     // preview pic
635     mm_cache_t *ic = cache_query_preview ( ui_selected -> ref -> unique_id );
636     SDL_Surface *previewpic;
637
638     if ( ic ) {
639       previewpic = ic -> i;
640     } else {
641       previewpic = g_imagecache [ IMG_PREVIEW_MISSING ].i;
642     }
643
644     if ( previewpic ) {
645       dest -> x = pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_offset_x", 50 ) +
646         ( ( pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_width", 50 ) - previewpic -> w ) / 2 );
647       dest -> y = pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_offset_y", 50 );
648       SDL_BlitSurface ( previewpic, NULL /* whole image */, sdl_realscreen, dest );
649       //SDL_UpdateRects ( sdl_realscreen, 1, &dest );
650       dest++;
651     }
652
653   } // selected?
654
655   // update all the rects and send it all to sdl
656   SDL_UpdateRects ( sdl_realscreen, dest - rects, rects );
657
658 } // ui_render
659
660 void ui_process_input ( unsigned char block_p ) {
661   SDL_Event event;
662
663   unsigned char ui_event = 0; // if we get a ui event, flip to 1 and break
664   static ui_sdl_button_e ui_mask = uisb_none; // current buttons down
665
666   while ( ! ui_event &&
667           block_p ? SDL_WaitEvent ( &event ) : SDL_PollEvent ( &event ) )
668   {
669
670     switch ( event.type ) {
671
672 #if 0 // joystick motion
673     case SDL_JOYAXISMOTION:
674
675       pnd_log ( PND_LOG_DEFAULT, "joystick axis\n" );
676
677         if ( event.jaxis.axis == 0 ) {
678           // horiz
679           if ( event.jaxis.value < 0 ) {
680             ui_push_left();
681             pnd_log ( PND_LOG_DEFAULT, "joystick axis - LEFT\n" );
682           } else if ( event.jaxis.value > 0 ) {
683             ui_push_right();
684             pnd_log ( PND_LOG_DEFAULT, "joystick axis - RIGHT\n" );
685           }
686         } else if ( event.jaxis.axis == 1 ) {
687           // vert
688           if ( event.jaxis.value < 0 ) {
689             ui_push_up();
690           } else if ( event.jaxis.value > 0 ) {
691             ui_push_down();
692           }
693         }
694
695         ui_event++;
696
697         break;
698 #endif
699
700 #if 0 // joystick buttons
701     case SDL_JOYBUTTONDOWN:
702
703       pnd_log ( PND_LOG_DEFAULT, "joystick button down %u\n", event.jbutton.button );
704
705       if ( event.jbutton.button == 0 ) { // B
706         ui_mask |= uisb_b;
707       } else if ( event.jbutton.button == 1 ) { // Y
708         ui_mask |= uisb_y;
709       } else if ( event.jbutton.button == 2 ) { // X
710         ui_mask |= uisb_x;
711       } else if ( event.jbutton.button == 3 ) { // A
712         ui_mask |= uisb_a;
713
714       } else if ( event.jbutton.button == 4 ) { // Select
715         ui_mask |= uisb_select;
716       } else if ( event.jbutton.button == 5 ) { // Start
717         ui_mask |= uisb_start;
718
719       } else if ( event.jbutton.button == 7 ) { // L
720         ui_mask |= uisb_l;
721       } else if ( event.jbutton.button == 8 ) { // R
722         ui_mask |= uisb_r;
723
724       }
725
726       ui_event++;
727
728       break;
729
730     case SDL_JOYBUTTONUP:
731
732       pnd_log ( PND_LOG_DEFAULT, "joystick button up %u\n", event.jbutton.button );
733
734       if ( event.jbutton.button == 0 ) { // B
735         ui_mask &= ~uisb_b;
736         ui_push_exec();
737       } else if ( event.jbutton.button == 1 ) { // Y
738         ui_mask &= ~uisb_y;
739       } else if ( event.jbutton.button == 2 ) { // X
740         ui_mask &= ~uisb_x;
741       } else if ( event.jbutton.button == 3 ) { // A
742         ui_mask &= ~uisb_a;
743
744       } else if ( event.jbutton.button == 4 ) { // Select
745         ui_mask &= ~uisb_select;
746       } else if ( event.jbutton.button == 5 ) { // Start
747         ui_mask &= ~uisb_start;
748
749       } else if ( event.jbutton.button == 7 ) { // L
750         ui_mask &= ~uisb_l;
751         ui_push_ltrigger();
752       } else if ( event.jbutton.button == 8 ) { // R
753         ui_mask &= ~uisb_r;
754         ui_push_rtrigger();
755
756       }
757
758       ui_event++;
759
760       break;
761 #endif
762
763 #if 1 // keyboard events
764     case SDL_KEYUP:
765
766       //pnd_log ( pndn_debug, "key up %u\n", event.key.keysym.sym );
767
768       // directional
769       if ( event.key.keysym.sym == SDLK_RIGHT ) {
770         ui_push_right();
771         ui_event++;
772       } else if ( event.key.keysym.sym == SDLK_LEFT ) {
773         ui_push_left();
774         ui_event++;
775       } else if ( event.key.keysym.sym == SDLK_UP ) {
776         ui_push_up();
777         ui_event++;
778       } else if ( event.key.keysym.sym == SDLK_DOWN ) {
779         ui_push_down();
780         ui_event++;
781       } else if ( event.key.keysym.sym == SDLK_SPACE || event.key.keysym.sym == SDLK_END ) {
782         ui_push_exec();
783         ui_event++;
784       } else if ( event.key.keysym.sym == SDLK_z || event.key.keysym.sym == SDLK_RSHIFT ) {
785         ui_push_ltrigger();
786         ui_event++;
787       } else if ( event.key.keysym.sym == SDLK_x || event.key.keysym.sym == SDLK_RCTRL ) {
788         ui_push_rtrigger();
789         ui_event++;
790       }
791
792       // extras
793       if ( event.key.keysym.sym == SDLK_q ) {
794         emit_and_quit ( MM_QUIT );
795       }
796
797       break;
798 #endif
799
800 #if 0 // mouse / touchscreen
801     case SDL_MOUSEBUTTONDOWN:
802       if ( event.button.button == SDL_BUTTON_LEFT ) {
803         cb_pointer_press ( gc, event.button.x / g_scale, event.button.y / g_scale );
804         ui_event++;
805       }
806       break;
807
808     case SDL_MOUSEBUTTONUP:
809       if ( event.button.button == SDL_BUTTON_LEFT ) {
810         cb_pointer_release ( gc, event.button.x / g_scale, event.button.y / g_scale );
811         retval |= STAT_pen;
812         ui_event++;
813       }
814       break;
815 #endif
816
817     case SDL_QUIT:
818       exit ( 0 );
819       break;
820
821     default:
822       break;
823
824     } // switch event type
825
826   } // while poll
827
828   return;
829 }
830
831 void ui_push_left ( void ) {
832
833   if ( ! ui_selected ) {
834     ui_push_right();
835     return;
836   }
837
838   // are we alreadt at first item?
839   if ( g_categories [ ui_category ].refs == ui_selected ) {
840     // can't go any more left, we're at the head
841   } else {
842     // figure out the previous item; yay for singly linked list :/
843     mm_appref_t *i = g_categories [ ui_category ].refs;
844     while ( i ) {
845       if ( i -> next == ui_selected ) {
846         ui_selected = i;
847         break;
848       }
849       i = i -> next;
850     }
851   }
852
853   return;
854 }
855
856 void ui_push_right ( void ) {
857
858   if ( ui_selected ) {
859
860     if ( ui_selected -> next ) {
861       ui_selected = ui_selected -> next;
862     }
863
864   } else {
865     ui_selected = g_categories [ ui_category ].refs;
866   }
867
868   return;
869 }
870
871 void ui_push_up ( void ) {
872   unsigned char col_max = pnd_conf_get_as_int ( g_conf, MMENU_DISP_COLMAX );
873
874   while ( col_max ) {
875     ui_push_left();
876     col_max--;
877   }
878
879   return;
880 }
881
882 void ui_push_down ( void ) {
883   unsigned char col_max = pnd_conf_get_as_int ( g_conf, MMENU_DISP_COLMAX );
884
885   if ( ui_selected ) {
886     while ( col_max ) {
887       ui_push_right();
888       col_max--;
889     }
890   } else {
891     ui_push_right();
892   }
893
894   return;
895 }
896
897 void ui_push_exec ( void ) {
898
899   if ( ui_selected ) {
900     char buffer [ PATH_MAX ];
901     sprintf ( buffer, "%s/%s", ui_selected -> ref -> object_path, ui_selected -> ref -> object_filename );
902     pnd_apps_exec ( pnd_run_script,
903                     buffer,
904                     ui_selected -> ref -> unique_id,
905                     ui_selected -> ref -> exec,
906                     ui_selected -> ref -> startdir,
907                     ui_selected -> ref -> execargs,
908                     atoi ( ui_selected -> ref -> clockspeed ),
909                     PND_EXEC_OPTION_NORUN );
910     sprintf ( buffer, "%s %s\n", MM_RUN, pnd_apps_exec_runline() );
911     emit_and_quit ( buffer );
912   }
913
914   return;
915 }
916
917 void ui_push_ltrigger ( void ) {
918   unsigned char oldcat = ui_category;
919
920   if ( ui_category > 0 ) {
921     ui_category--;
922   } else {
923     if ( pnd_conf_get_as_int_d ( g_conf, "tabs.wraparound", 0 ) > 0 ) {
924       ui_category = g_categorycount - 1;
925     }
926   }
927
928   if ( oldcat != ui_category ) {
929     ui_selected = NULL;
930   }
931
932   // make tab visible?
933   if ( ui_catshift > 0 && ui_category == ui_catshift - 1 ) {
934     ui_catshift--;
935   }
936
937   // unscroll
938   ui_rows_scrolled_down = 0;
939
940   return;
941 }
942
943 void ui_push_rtrigger ( void ) {
944   unsigned char oldcat = ui_category;
945
946   unsigned int screen_width = pnd_conf_get_as_int_d ( g_conf, "display.screen_width", 800 );
947   unsigned int tab_width = pnd_conf_get_as_int ( g_conf, "tabs.tab_width" );
948
949   if ( ui_category < ( g_categorycount - 1 ) ) {
950     ui_category++;
951   } else {
952     if ( pnd_conf_get_as_int_d ( g_conf, "tabs.wraparound", 0 ) > 0 ) {
953       ui_category = 0;
954     }
955   }
956
957   if ( oldcat != ui_category ) {
958     ui_selected = NULL;
959   }
960
961   // make tab visible?
962   if ( ui_category > ui_catshift + ( screen_width / tab_width ) - 1 ) {
963     ui_catshift++;
964   }
965
966   // unscroll
967   ui_rows_scrolled_down = 0;
968
969   return;
970 }
971
972 SDL_Surface *ui_scale_image ( SDL_Surface *s, unsigned int maxwidth, int maxheight ) {
973   double scale = 1000000.0;
974   double scalex = 1000000.0;
975   double scaley = 1000000.0;
976   SDL_Surface *scaled;
977
978   scalex = (double)maxwidth / (double)s -> w;
979
980   if ( maxheight == -1 ) {
981     scale = scalex;
982   } else {
983     scaley = (double)maxheight / (double)s -> h;
984
985     if ( scaley < scalex ) {
986       scale = scaley;
987     } else {
988       scale = scalex;
989     }
990
991   }
992
993   pnd_log ( pndn_debug, "  Upscaling; scale factor %f\n", scale );
994   scaled = rotozoomSurface ( s, 0 /* angle*/, scale /* scale */, 1 /* smooth==1*/ );
995   SDL_FreeSurface ( s );
996   s = scaled;
997
998   return ( s );
999 }
1000
1001 void ui_loadscreen ( void ) {
1002
1003   SDL_Rect dest;
1004
1005   unsigned int font_rgba_r = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_r", 200 );
1006   unsigned int font_rgba_g = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_g", 200 );
1007   unsigned int font_rgba_b = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_b", 200 );
1008   unsigned int font_rgba_a = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_a", 100 );
1009
1010   // clear the screen
1011   SDL_FillRect( SDL_GetVideoSurface(), NULL, 0 );
1012
1013   // render text
1014   SDL_Surface *rtext;
1015   SDL_Color tmpfontcolor = { font_rgba_r, font_rgba_g, font_rgba_b, font_rgba_a };
1016   rtext = TTF_RenderText_Blended ( g_big_font, "Setting up menu...", tmpfontcolor );
1017   dest.x = 20;
1018   dest.y = 20;
1019   SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, &dest );
1020   SDL_UpdateRects ( sdl_realscreen, 1, &dest );
1021
1022   return;
1023 }
1024
1025 void ui_discoverscreen ( unsigned char clearscreen ) {
1026
1027   SDL_Rect dest;
1028
1029   unsigned int font_rgba_r = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_r", 200 );
1030   unsigned int font_rgba_g = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_g", 200 );
1031   unsigned int font_rgba_b = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_b", 200 );
1032   unsigned int font_rgba_a = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_a", 100 );
1033
1034   // clear the screen
1035   if ( clearscreen ) {
1036     SDL_FillRect( SDL_GetVideoSurface(), NULL, 0 );
1037
1038     // render background
1039     if ( g_imagecache [ IMG_BACKGROUND_800480 ].i ) {
1040       dest.x = 0;
1041       dest.y = 0;
1042       dest.w = sdl_realscreen -> w;
1043       dest.h = sdl_realscreen -> h;
1044       SDL_BlitSurface ( g_imagecache [ IMG_BACKGROUND_800480 ].i, NULL /* whole image */, sdl_realscreen, NULL /* 0,0 */ );
1045       SDL_UpdateRects ( sdl_realscreen, 1, &dest );
1046     }
1047
1048   }
1049
1050   // render text
1051   SDL_Surface *rtext;
1052   SDL_Color tmpfontcolor = { font_rgba_r, font_rgba_g, font_rgba_b, font_rgba_a };
1053   rtext = TTF_RenderText_Blended ( g_big_font, "Looking for applications...", tmpfontcolor );
1054   if ( clearscreen ) {
1055     dest.x = 20;
1056     dest.y = 20;
1057   } else {
1058     dest.x = 20;
1059     dest.y = 40;
1060   }
1061   SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, &dest );
1062   SDL_UpdateRects ( sdl_realscreen, 1, &dest );
1063
1064   // render icon
1065   if ( g_imagecache [ IMG_ICON_MISSING ].i ) {
1066     dest.x = rtext -> w + 30;
1067     dest.y = 20;
1068     SDL_BlitSurface ( g_imagecache [ IMG_ICON_MISSING ].i, NULL, sdl_realscreen, &dest );
1069     SDL_UpdateRects ( sdl_realscreen, 1, &dest );
1070   }
1071
1072   return;
1073 }
1074
1075 void ui_cachescreen ( unsigned char clearscreen, char *filename ) {
1076
1077   SDL_Rect rects [ 4 ];
1078   SDL_Rect *dest = rects;
1079   bzero ( dest, sizeof(SDL_Rect)* 4 );
1080
1081   unsigned int font_rgba_r = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_r", 200 );
1082   unsigned int font_rgba_g = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_g", 200 );
1083   unsigned int font_rgba_b = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_b", 200 );
1084   unsigned int font_rgba_a = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_a", 100 );
1085
1086   static unsigned int stepx = 0;
1087
1088   // clear the screen
1089   if ( clearscreen ) {
1090     SDL_FillRect( SDL_GetVideoSurface(), NULL, 0 );
1091
1092     // render background
1093     if ( g_imagecache [ IMG_BACKGROUND_800480 ].i ) {
1094       dest -> x = 0;
1095       dest -> y = 0;
1096       dest -> w = sdl_realscreen -> w;
1097       dest -> h = sdl_realscreen -> h;
1098       SDL_BlitSurface ( g_imagecache [ IMG_BACKGROUND_800480 ].i, NULL /* whole image */, sdl_realscreen, NULL /* 0,0 */ );
1099       dest++;
1100     }
1101
1102   }
1103
1104   // render text
1105   SDL_Surface *rtext;
1106   SDL_Color tmpfontcolor = { font_rgba_r, font_rgba_g, font_rgba_b, font_rgba_a };
1107   rtext = TTF_RenderText_Blended ( g_big_font, "Caching applications artwork...", tmpfontcolor );
1108   if ( clearscreen ) {
1109     dest -> x = 20;
1110     dest -> y = 20;
1111   } else {
1112     dest -> x = 20;
1113     dest -> y = 40;
1114   }
1115   SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, dest );
1116   dest++;
1117
1118   // render icon
1119   if ( g_imagecache [ IMG_ICON_MISSING ].i ) {
1120     dest -> x = rtext -> w + 30 + stepx;
1121     dest -> y = 20;
1122     SDL_BlitSurface ( g_imagecache [ IMG_ICON_MISSING ].i, NULL, sdl_realscreen, dest );
1123     dest++;
1124   }
1125
1126   // filename
1127   if ( filename ) {
1128     rtext = TTF_RenderText_Blended ( g_tab_font, filename, tmpfontcolor );
1129     dest -> x = 20;
1130     dest -> y = 50;
1131     SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, dest );
1132     dest++;
1133   }
1134
1135   // move across
1136   stepx += 20;
1137
1138   if ( stepx > 350 ) {
1139     stepx = 0;
1140   }
1141
1142   SDL_UpdateRects ( sdl_realscreen, dest - rects, rects );
1143
1144   return;
1145 }
1146
1147 int ui_selected_index ( void ) {
1148
1149   if ( ! ui_selected ) {
1150     return ( -1 ); // no index
1151   }
1152
1153   mm_appref_t *r = g_categories [ ui_category ].refs;
1154   int counter = 0;
1155   while ( r ) {
1156     if ( r == ui_selected ) {
1157       return ( counter );
1158     }
1159     r = r -> next;
1160     counter++;
1161   }
1162
1163   return ( -1 );
1164 }