frodo-wii/menu.cpp

436 lines
9.1 KiB
C++
Raw Normal View History

2009-01-13 19:46:42 +01:00
/*********************************************************************
*
* Copyright (C) 2004,2008, Simon Kagstrom
*
* Filename: menu.c
* Author: Simon Kagstrom <simon.kagstrom@gmail.com>
* Description: Code for menus (originally for Mophun)
*
* $Id$
*
********************************************************************/
2009-01-13 19:46:42 +01:00
#include <stdio.h>
#include <stdlib.h>
2009-11-19 18:42:02 +01:00
#include "menu.hh"
#include "utils.hh"
2009-01-13 19:46:42 +01:00
#define IS_SUBMENU(p_msg) ( (p_msg)[0] == '^' )
void Menu::printText(SDL_Surface *where, const char *msg, SDL_Color clr,
2009-11-22 09:24:06 +01:00
int x, int y, int w, int h)
{
SDL_Surface *font_surf;
SDL_Rect dst = {x, y, 0, 0};
char buf[255];
unsigned int i;
int tw, th;
TTF_SizeText(this->font, msg, &tw, &th);
memset(buf, 0, sizeof(buf));
strncpy(buf, msg, 254);
/* Crop text */
if (x + tw > w)
{
int pixels_per_char = tw / strlen(msg);
int last_char = w / pixels_per_char;
/* FIXME! Handle some corner cases here (short strings etc) */
2009-11-23 19:52:30 +01:00
panic_if((unsigned)last_char > strlen(msg),
"last character (%d) is after the string length (%d)\n",
last_char, strlen(msg));
if (last_char > 3)
2009-11-22 09:24:06 +01:00
{
buf[last_char - 2] = '.';
buf[last_char - 1] = '.';
buf[last_char] = '\0';
2009-11-22 09:24:06 +01:00
}
}
2009-11-22 09:24:06 +01:00
/* Fixup multi-menu option look */
for (i = 0; i < strlen(buf) ; i++)
{
if (buf[i] == '^' || buf[i] == '|')
buf[i] = ' ';
}
font_surf = TTF_RenderText_Blended(this->font, buf, clr);
panic_if (!font_surf,
"%s\n", TTF_GetError());
2009-11-22 09:24:06 +01:00
SDL_BlitSurface(font_surf, NULL, where, &dst);
2009-11-22 09:24:06 +01:00
SDL_FreeSurface(font_surf);
}
2009-11-23 07:14:38 +01:00
void Menu::highlightBackground(SDL_Surface *where, int x, int y, int w, int h)
2009-11-20 21:28:48 +01:00
{
2009-11-23 07:14:38 +01:00
SDL_Rect dst;
2009-11-22 09:24:06 +01:00
/* Can't highlight without images */
if (!this->text_bg_left ||
!this->text_bg_middle ||
!this->text_bg_right)
return;
2009-11-23 07:14:38 +01:00
int font_height = TTF_FontHeight(this->font);
2009-11-20 21:28:48 +01:00
int bg_y_start = y + font_height / 2 -
this->text_bg_left->h / 2;
2009-11-24 07:36:11 +01:00
int bg_x_start = x - this->text_bg_left->w / 3;
int bg_x_end = x + w -
2009-11-24 07:36:11 +01:00
(2 * this->text_bg_right->w) / 3;
int n_mid = (bg_x_end - bg_x_start + this->text_bg_left->w / 3) / this->text_bg_middle->w;
2009-11-20 21:28:48 +01:00
2009-11-23 07:14:38 +01:00
dst = (SDL_Rect){bg_x_start, bg_y_start, 0,0};
2009-11-20 21:28:48 +01:00
SDL_BlitSurface(this->text_bg_left, NULL,
2009-11-23 07:14:38 +01:00
where, &dst);
2009-11-20 21:28:48 +01:00
for (int i = 1; i < n_mid; i++)
2009-11-23 07:14:38 +01:00
{
dst = (SDL_Rect){bg_x_start + i * this->text_bg_middle->w,
bg_y_start, 0,0};
2009-11-20 21:28:48 +01:00
SDL_BlitSurface(this->text_bg_middle, NULL,
2009-11-23 07:14:38 +01:00
where, &dst);
}
dst = (SDL_Rect){bg_x_end, bg_y_start, 0,0};
2009-11-20 21:28:48 +01:00
SDL_BlitSurface(this->text_bg_right, NULL,
2009-11-23 07:14:38 +01:00
where, &dst);
2009-11-20 21:28:48 +01:00
}
2009-01-13 19:46:42 +01:00
2009-11-20 18:09:06 +01:00
void Menu::draw(SDL_Surface *where, int x, int y, int w, int h)
2009-01-13 19:46:42 +01:00
{
2009-11-23 07:14:38 +01:00
int font_height = TTF_FontHeight(this->font);
2009-11-20 18:09:06 +01:00
int line_height = (font_height + font_height / 4);
int x_start = x;
int entries_visible = h / line_height - 2;
2009-11-23 20:52:46 +01:00
int start_entry_visible = 0;
2009-11-20 18:09:06 +01:00
2009-11-23 19:52:30 +01:00
panic_if(!this->pp_msgs, "Set the messages before drawing, thank you\n");
2009-11-23 20:52:46 +01:00
if (this->cur_sel - start_entry_visible > entries_visible)
2009-11-20 18:09:06 +01:00
{
2009-11-23 20:52:46 +01:00
while (this->cur_sel - start_entry_visible > entries_visible)
2009-11-20 18:09:06 +01:00
{
2009-11-23 20:52:46 +01:00
start_entry_visible++;
if (start_entry_visible > this->n_entries)
2009-11-20 18:09:06 +01:00
{
2009-11-23 20:52:46 +01:00
start_entry_visible = 0;
2009-11-20 18:09:06 +01:00
break;
}
}
}
2009-11-23 20:52:46 +01:00
else if ( this->cur_sel < start_entry_visible )
start_entry_visible = this->cur_sel;
2009-01-13 19:46:42 +01:00
2009-11-23 20:52:46 +01:00
for (int i = start_entry_visible;
i <= start_entry_visible + entries_visible; i++)
2009-11-20 18:09:06 +01:00
{
const char *msg = this->pp_msgs[i];
2009-11-20 21:28:48 +01:00
int cur_y;
2009-11-20 18:09:06 +01:00
if (i >= this->n_entries)
break;
2009-11-24 07:36:11 +01:00
cur_y = y + (i - start_entry_visible) * line_height;
2009-11-20 21:28:48 +01:00
/* Draw the background for the selected entry */
if (this->cur_sel == i) {
int tw, th;
TTF_SizeText(this->font, msg, &tw, &th);
2009-11-23 07:14:38 +01:00
this->highlightBackground(where, x_start, cur_y, tw, th);
}
2009-11-20 21:28:48 +01:00
2009-11-20 18:09:06 +01:00
if (IS_SUBMENU(msg))
{
submenu_t *p_submenu = this->findSubmenu(i);
int n_pipe = 0;
int total_chars = 0;
int tw, th, tw_first, th_first;
int n_chars;
char *p;
2009-11-20 18:09:06 +01:00
int n;
for (n = 0; msg[n] != '\0'; n++)
{
if (msg[n] != '|')
continue;
/* msg[n] == '|' */
2009-11-20 21:28:48 +01:00
/* Count the number of chars until next pipe */
for (n_chars = 1; msg[n+n_chars] && msg[n+n_chars] != '|'; n_chars++);
2009-11-20 21:28:48 +01:00
total_chars += n_chars;
2009-11-20 21:28:48 +01:00
n_pipe++;
/* Found the selection yet? */
if (p_submenu->sel == n_pipe-1)
2009-11-20 21:28:48 +01:00
break;
2009-11-20 18:09:06 +01:00
}
2009-11-23 07:14:38 +01:00
p = (char*)xmalloc(total_chars + 1);
strncpy(p, msg, total_chars);
TTF_SizeText(this->font, p, &tw_first, &th_first);
strncpy(p, msg + n, n_chars);
TTF_SizeText(this->font, p, &tw, &th);
2009-11-23 19:28:34 +01:00
this->highlightBackground(where, x_start + tw_first,
cur_y, tw, th);
free(p);
2009-11-20 18:09:06 +01:00
}
2009-11-24 07:25:17 +01:00
/* And print the text on top */
this->printText(where, msg, this->text_color,
x_start, cur_y, w, h);
2009-11-20 18:09:06 +01:00
}
2009-01-13 19:46:42 +01:00
}
2009-11-19 21:05:06 +01:00
int Menu::getNextEntry(int dy)
{
2009-11-23 19:28:34 +01:00
if (this->cur_sel + dy < 0)
2009-11-19 21:05:06 +01:00
return this->n_entries - 1;
2009-11-23 19:28:34 +01:00
if (this->cur_sel + dy > this->n_entries - 1)
2009-11-19 21:05:06 +01:00
return 0;
2009-11-23 19:28:34 +01:00
return this->cur_sel + dy;
2009-11-19 21:05:06 +01:00
}
2009-11-19 21:18:45 +01:00
submenu_t *Menu::findSubmenu(int index)
{
int i;
for (i = 0; i < this->n_submenus; i++)
{
if (this->p_submenus[i].index == index)
return &this->p_submenus[i];
}
return NULL;
}
2009-11-20 07:11:57 +01:00
void Menu::selectOne(int which)
{
if (which >= this->n_entries)
which = 0;
this->cur_sel = which;
if (this->pp_msgs[this->cur_sel][0] == ' ' ||
IS_SUBMENU(this->pp_msgs[this->cur_sel]))
this->selectNext(0, 1);
}
2009-11-19 21:05:06 +01:00
void Menu::selectNext(int dx, int dy)
{
int next;
2009-11-19 21:05:06 +01:00
this->cur_sel = this->getNextEntry(dy);
next = this->getNextEntry(dy + 1);
2009-11-19 21:05:06 +01:00
/* We want to skip this for some reason */
if (this->pp_msgs[this->cur_sel][0] == ' ' ||
IS_SUBMENU(this->pp_msgs[this->cur_sel]) ) {
this->selectNext(dx, dy);
return;
}
2009-11-19 21:05:06 +01:00
/* If the next is a submenu */
if (dx != 0 && IS_SUBMENU(this->pp_msgs[next]))
{
2009-11-23 19:28:34 +01:00
submenu_t *p_submenu = findSubmenu(next);
2009-11-19 21:18:45 +01:00
panic_if(!p_submenu, "submenu in the menu text but no actual submenu\n");
2009-11-19 21:05:06 +01:00
p_submenu->sel = (p_submenu->sel + dx) < 0 ? p_submenu->n_entries - 1 :
(p_submenu->sel + dx) % p_submenu->n_entries;
}
}
2009-11-19 21:05:06 +01:00
void Menu::selectNext(event_t ev)
{
2009-11-19 21:05:06 +01:00
switch (ev)
{
case KEY_UP:
this->selectNext(0, -1); break;
case KEY_DOWN:
this->selectNext(0, 1); break;
case KEY_LEFT:
this->selectNext(-1, 0); break;
case KEY_RIGHT:
this->selectNext(1, 0); break;
default:
panic("selectNext(ev) called with event %d\n", ev);
}
}
2009-11-19 21:05:06 +01:00
void Menu::runLogic()
2009-04-18 12:15:08 +02:00
{
2009-11-19 21:05:06 +01:00
event_t ev;
2009-04-18 12:15:08 +02:00
2009-11-19 21:05:06 +01:00
while ( (ev = this->popEvent()) != EVENT_NONE )
{
switch (ev)
{
case KEY_UP:
case KEY_DOWN:
case KEY_LEFT:
case KEY_RIGHT:
this->selectNext(ev);
break;
case KEY_SELECT:
this->selectCallback(this->cur_sel); break;
case KEY_ESCAPE:
this->escapeCallback(this->cur_sel); break;
break;
default:
break;
}
}
}
2009-11-19 20:23:37 +01:00
event_t Menu::popEvent()
{
event_t out;
if (this->ev_head == this->ev_tail)
return EVENT_NONE;
out = this->event_stack[this->ev_tail];
this->ev_tail = (this->ev_tail + 1) % 8;
return out;
}
void Menu::pushEvent(event_t ev)
{
/* Push... */
this->event_stack[this->ev_head] = ev;
/* ... and update */
this->ev_head = (this->ev_head + 1) % 8;
if (this->ev_head == this->ev_tail)
this->ev_tail = (this->ev_tail + 1) % 8;
}
void Menu::pushEvent(SDL_Event *ev)
{
switch(ev->type)
{
case SDL_KEYDOWN:
switch (ev->key.keysym.sym)
{
case SDLK_UP:
2009-11-19 21:05:06 +01:00
this->pushEvent(KEY_UP);
2009-11-19 20:23:37 +01:00
break;
case SDLK_DOWN:
2009-11-19 21:05:06 +01:00
this->pushEvent(KEY_DOWN);
2009-11-19 20:23:37 +01:00
break;
case SDLK_LEFT:
2009-11-19 21:05:06 +01:00
this->pushEvent(KEY_LEFT);
2009-11-19 20:23:37 +01:00
break;
case SDLK_RIGHT:
2009-11-19 21:05:06 +01:00
this->pushEvent(KEY_RIGHT);
2009-11-19 20:23:37 +01:00
break;
case SDLK_PAGEDOWN:
2009-11-19 21:05:06 +01:00
this->pushEvent(KEY_PAGEDOWN);
2009-11-19 20:23:37 +01:00
break;
case SDLK_PAGEUP:
2009-11-19 21:05:06 +01:00
this->pushEvent(KEY_PAGEUP);
2009-11-19 20:23:37 +01:00
break;
case SDLK_RETURN:
case SDLK_SPACE:
2009-11-19 21:05:06 +01:00
this->pushEvent(KEY_SELECT);
2009-11-19 20:23:37 +01:00
break;
case SDLK_HOME:
case SDLK_ESCAPE:
2009-11-19 21:05:06 +01:00
this->pushEvent(KEY_ESCAPE);
2009-11-19 20:23:37 +01:00
break;
default:
break;
}
default:
break;
}
}
2009-11-23 19:28:34 +01:00
void Menu::setText(const char **messages)
{
int submenu;
/* Free the old stuff */
this->n_submenus = 0;
free(this->p_submenus);
free(this->pp_msgs);
2009-11-23 19:28:34 +01:00
for (this->n_entries = 0; messages[this->n_entries]; this->n_entries++)
{
/* Is this a submenu? */
if (IS_SUBMENU(messages[this->n_entries]))
{
this->n_submenus++;
continue; /* Length of submenus is unimportant */
}
}
2009-11-19 21:18:45 +01:00
this->pp_msgs = (const char **)xmalloc(this->n_entries * sizeof(const char *));
this->p_submenus = (submenu_t *)xmalloc(this->n_submenus * sizeof(submenu_t));
for (int i = 0; i < this->n_entries; i++)
this->pp_msgs[i] = xstrdup(messages[i]);
submenu = 0;
for (int i = 0; i < this->n_entries; i++)
{
if (IS_SUBMENU(this->pp_msgs[i]))
{
int n;
this->p_submenus[submenu].index = i;
this->p_submenus[submenu].sel = 0;
this->p_submenus[submenu].n_entries = 0;
for (n = 0; this->pp_msgs[i][n] != '\0'; n++)
{
if (this->pp_msgs[i][n] == '|')
this->p_submenus[submenu].n_entries++;
}
submenu++;
}
}
2009-11-20 07:11:57 +01:00
/* Move selection to the first entry */
this->selectOne(0);
}
2009-11-20 18:09:06 +01:00
Menu::Menu(TTF_Font *font)
2009-02-13 08:58:33 +01:00
{
2009-11-20 18:09:06 +01:00
this->setTextColor((SDL_Color){0xff,0xff,0xff,0});
2009-11-19 18:59:43 +01:00
this->font = font;
2009-11-22 09:24:06 +01:00
this->text_bg_left = NULL;
this->text_bg_middle = NULL;
this->text_bg_right = NULL;
2009-11-19 18:59:43 +01:00
this->pp_msgs = NULL;
this->n_entries = 0;
this->p_submenus = NULL;
2009-11-19 18:59:43 +01:00
this->n_submenus = 0;
this->cur_sel = 0;
this->mouse_x = -1;
this->mouse_y = -1;
2009-11-19 20:23:37 +01:00
memset(this->event_stack, 0, sizeof(this->event_stack));
this->ev_head = this->ev_tail = 0;
2009-01-13 19:46:42 +01:00
}
2009-11-20 18:09:06 +01:00
void Menu::setTextColor(SDL_Color clr)
{
2009-11-23 19:28:34 +01:00
this->text_color = clr;
2009-11-20 18:09:06 +01:00
}
Menu::~Menu()
{
free(this->pp_msgs);
free(this->p_submenus);
}