39 |
using namespace OxydLib; |
using namespace OxydLib; |
40 |
|
|
41 |
|
|
42 |
//---------------------------------------------------------------------- |
#include "menus_internal.hh" |
|
// Helper classes |
|
|
//---------------------------------------------------------------------- |
|
|
namespace |
|
|
{ |
|
|
class BuildVList { |
|
|
Rect r; |
|
|
Menu *container; |
|
|
int skip; |
|
|
public: |
|
|
BuildVList(Menu *cc, const px::Rect &rr, int s) |
|
|
: r(rr), container(cc), skip(s) |
|
|
{} |
|
|
|
|
|
Widget *add(Widget *w) { |
|
|
container->add(w, r); |
|
|
r.y += r.h+skip; |
|
|
return w; |
|
|
} |
|
|
|
|
|
Rect pos() const { return r; } |
|
|
}; |
|
|
|
|
|
class VTableBuilder { |
|
|
Menu *m_menu; |
|
|
px::Rect m_widgetsize; |
|
|
int m_hspacing, m_vspacing; |
|
|
vector<Widget *> m_widgets; |
|
|
int m_maxheight; |
|
|
public: |
|
|
VTableBuilder (Menu *menu, const px::Rect &widgetsize, |
|
|
int vspacing, int hspacing, int max_height) |
|
|
: m_menu(menu), m_widgetsize(widgetsize) |
|
|
{ |
|
|
m_hspacing = hspacing; |
|
|
m_vspacing = vspacing; |
|
|
m_maxheight = max_height; |
|
|
} |
|
|
|
|
|
Widget *add (Widget *w) { |
|
|
m_widgets.push_back(w); |
|
|
return w; |
|
|
} |
|
|
|
|
|
void finish() { |
|
|
if (m_widgets.empty()) |
|
|
return; |
|
|
int ncolumns = m_widgets.size() / m_maxheight; |
|
|
int nrows = (m_widgets.size() + ncolumns - 1) / ncolumns; |
|
|
int i=0; |
|
|
int x=0; |
|
|
for (int col = 0; col < ncolumns; col++) { |
|
|
int y=0; |
|
|
for (int row=0; row < nrows; row++, i++) { |
|
|
if (i == (int)m_widgets.size()) |
|
|
goto done; |
|
|
px::Rect rr(x, y, m_widgetsize.w, m_widgetsize.h); |
|
|
m_menu->add(m_widgets[i], rr); |
|
|
// m_widgets[i]->move (x, y); |
|
|
y += m_widgetsize.h + m_vspacing; |
|
|
} |
|
|
x += m_widgetsize.w + m_hspacing; |
|
|
} |
|
|
done: |
|
|
return; |
|
|
} |
|
|
}; |
|
|
|
|
|
class BuildHList { |
|
|
Rect r; |
|
|
Menu *container; |
|
|
int skip; |
|
|
public: |
|
|
BuildHList(Menu *cc, const px::Rect &rr, int s) |
|
|
: r(rr), container(cc), skip(s) |
|
|
{} |
|
|
|
|
|
Widget * add(Widget *w) { |
|
|
container->add(w, r); |
|
|
r.x += r.w+skip; |
|
|
return w; |
|
|
} |
|
|
Widget *add (Widget *w, int width) { |
|
|
px::Rect rr(r.x, r.y, width, r.h); |
|
|
container->add(w, rr); |
|
|
r.x += width + skip; |
|
|
return w; |
|
|
} |
|
|
|
|
|
Rect pos() const { return r; } |
|
|
}; |
|
|
} |
|
|
|
|
|
namespace |
|
|
{ |
|
|
|
|
|
class ImageAlloc { |
|
|
public: |
|
|
px::Surface *acquire(const std::string &name) { |
|
|
return px::LoadImage(name.c_str()); |
|
|
} |
|
|
void release(px::Surface *s) { delete s; } |
|
|
}; |
|
|
|
|
|
typedef Cache<px::Surface*, ImageAlloc> ImageCache; |
|
|
|
|
|
|
|
|
class LevelPreviewCacheElem { |
|
|
Surface *surface; |
|
|
unsigned idx; |
|
|
|
|
|
LevelPreviewCacheElem(const LevelPreviewCacheElem&); |
|
|
LevelPreviewCacheElem& operator = (const LevelPreviewCacheElem& other); |
|
|
public: |
|
|
LevelPreviewCacheElem(LevelPack *lp, unsigned idx_, int xsize, int ysize) |
|
|
: surface(0) , idx(idx_) |
|
|
{ |
|
|
surface = LevelPreview(lp, idx); // do not free, points to BackBuffer |
|
|
if (surface) { |
|
|
const Rect& game_area = display::GetGameArea(); |
|
|
Surface *game_surface = Grab(surface, game_area); |
|
|
|
|
|
if (game_surface) { |
|
|
surface = game_surface->zoom(xsize, ysize); |
|
|
delete game_surface; |
|
|
} |
|
|
else { |
|
|
surface = 0; // avoid delete |
|
|
} |
|
|
} |
|
|
|
|
|
// fprintf(stderr, "Created preview for #%i. surface=%p\n", idx, surface); |
|
|
} |
|
|
~LevelPreviewCacheElem() { delete surface; } |
|
|
bool operator<(const LevelPreviewCacheElem& other) { return idx<other.idx; } |
|
|
Surface *get_surface() { return surface; } |
|
|
}; |
|
|
|
|
|
class LevelPreviewCache { |
|
|
typedef std::map<unsigned,LevelPreviewCacheElem*> PreviewMap; |
|
|
|
|
|
PreviewMap cache; |
|
|
int xsize, ysize; |
|
|
|
|
|
void release() { |
|
|
PreviewMap::iterator i = cache.begin(); |
|
|
PreviewMap::iterator e = cache.end(); |
|
|
for (; i != e; ++i) |
|
|
delete i->second; |
|
|
} |
|
|
|
|
|
public: |
|
|
LevelPreviewCache() : xsize(0), ysize(0) {} |
|
|
~LevelPreviewCache() { release(); } |
|
|
|
|
|
void clear() { release(); cache.clear(); } |
|
|
void set_size(int xs, int ys) { xsize = xs; ysize = ys; } |
|
|
|
|
|
Surface *getPreview(LevelPack *lp, unsigned idx) { |
|
|
PreviewMap::iterator i = cache.find(idx); |
|
|
if (i != cache.end()) |
|
|
return i->second->get_surface(); |
|
|
|
|
|
assert(xsize != 0 && ysize != 0); // forgot to call set_size() ? |
|
|
LevelPreviewCacheElem *ce = new LevelPreviewCacheElem(lp, idx, xsize, ysize); |
|
|
|
|
|
Surface *surface = ce->get_surface(); |
|
|
if (!surface) delete ce; // dont add broken levels to cache |
|
|
else cache[idx] = ce; |
|
|
|
|
|
return surface; |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
class LevelPackMenu : public Menu { |
|
|
public: |
|
|
LevelPackMenu(); |
|
|
|
|
|
void on_action(Widget *w); |
|
|
void draw_background(px::GC &gc); |
|
|
int get_selection() const { return m_selection; } |
|
|
|
|
|
private: |
|
|
vector<Widget *> buttons; |
|
|
int m_selection; |
|
|
}; |
|
|
|
|
|
|
|
|
class LevelMenu; |
|
|
|
|
|
class LevelWidget : public Widget { |
|
|
public: |
|
|
LevelWidget(LevelPack *lp, int w, int h); |
|
|
bool manage (); |
|
|
int selected_level() const { return iselected; } |
|
|
|
|
|
// Widget interface. |
|
|
void draw(px::GC &gc, const px::Rect &r); |
|
|
|
|
|
void set_listener(ActionListener *al) { |
|
|
listener = al; |
|
|
} |
|
|
void trigger_action() { |
|
|
|
|
|
cache.clear(); |
|
|
if (listener) { |
|
|
listener->on_action(this); |
|
|
recalc_available(); |
|
|
} |
|
|
} |
|
|
|
|
|
void change_levelpack (LevelPack *lp); |
|
|
|
|
|
void page_up(); |
|
|
void page_down(); |
|
|
void start() { set_selected (0,0); } |
|
|
void end() { set_current(level_pack->size()-1); } |
|
|
|
|
|
void set_current(int newsel) { set_selected(ifirst, newsel); } |
|
|
void next_unsolved(); |
|
|
|
|
|
bool on_event(const SDL_Event &e); |
|
|
|
|
|
LevelMenu *get_menu(); |
|
|
|
|
|
int get_position() const { |
|
|
return (ifirst << 4) | (iselected-ifirst); |
|
|
} |
|
|
void set_position(int pos) { |
|
|
int new_first = pos >> 4; |
|
|
int off = pos&0xf; |
|
|
int new_sel = new_first+off; |
|
|
assert(off >= 0 && off < 12); // 12 levels per page |
|
|
set_selected(new_first, new_sel); |
|
|
} |
|
|
|
|
|
void show_text(const string& text); |
|
|
|
|
|
private: |
|
|
// Private functions. |
|
|
void scroll_up(int lines); |
|
|
void scroll_down(int lines); |
|
|
void set_selected (int newfirst, int newsel); |
|
|
void recalc_available (); |
|
|
|
|
|
// Event handling. |
|
|
bool handle_keydown (const SDL_Event *e); |
|
|
bool handle_mousedown (const SDL_Event *e); |
|
|
|
|
|
/* |
|
|
** Variables. |
|
|
*/ |
|
|
ImageCache cache; |
|
|
LevelPack *level_pack; // The current level pack |
|
|
LevelPreviewCache preview_cache; |
|
|
|
|
|
int ifirst; // Index of "upper left" level |
|
|
int iselected; // Index of selected level |
|
|
int max_available; // Index of the last available level (one can choose out of x unsolved levels) |
|
|
int width, height; |
|
|
vector<px::Rect> m_areas; // Screen areas occupied by level previews |
|
|
ActionListener *listener; |
|
|
}; |
|
|
|
|
|
class LevelMenu : public Menu { |
|
|
public: |
|
|
LevelMenu(LevelPack *lp, unsigned long pos); |
|
|
|
|
|
// rotate through levelpacks |
|
|
void next_levelpack() { |
|
|
unsigned next_pack = m_ilevelpack+1; |
|
|
if (next_pack == enigma::LevelPacks.size()) next_pack = 0; |
|
|
set_levelpack(next_pack); |
|
|
} |
|
|
void previous_levelpack() { |
|
|
unsigned prev_pack = m_ilevelpack; |
|
|
if (prev_pack == 0) prev_pack = enigma::LevelPacks.size()-1; |
|
|
else --prev_pack; |
|
|
set_levelpack(prev_pack); |
|
|
} |
|
|
|
|
|
int get_position() const { |
|
|
return (m_ilevelpack << 16) | (levelwidget->get_position() & 0xffff); |
|
|
} |
|
|
void set_position(int pos) { |
|
|
set_levelpack(pos >> 16); |
|
|
levelwidget->set_position(pos & 0xffff); |
|
|
} |
|
|
|
|
|
void show_text(const string& text) { |
|
|
shown_text = text; |
|
|
shown_text_ttl = 2.0; // show for two seconds |
|
|
} |
|
|
|
|
|
private: |
|
|
|
|
|
void update_info(); |
|
|
|
|
|
void set_levelpack (unsigned index); |
|
|
|
|
|
// Menu interface. |
|
|
void tick (double time); |
|
|
void draw_background(px::GC &gc); |
|
|
|
|
|
// Widget interface. |
|
|
bool on_event (const SDL_Event &e); |
|
|
|
|
|
// ActionListener interface. |
|
|
void on_action(Widget *w); |
|
|
|
|
|
// Variables. |
|
|
|
|
|
Widget *pgup, *pgdown, *start, *end, *unsolved; |
|
|
|
|
|
Widget *newgame; |
|
|
Widget *back; |
|
|
Widget *m_nextlp, *m_prevlp; // Next/previous level pack |
|
|
TextButton *m_lpbutton; // Current level pack |
|
|
Label *m_infobutton; |
|
|
Label *m_levelinfo, *m_levelinfo2; |
|
|
LevelWidget *levelwidget; |
|
|
LevelPack *level_pack; |
|
|
unsigned m_ilevelpack; |
|
|
string shown_text; // info text (disappears automatically) |
|
|
double shown_text_ttl; // rest duration for shown_text |
|
|
}; |
|
|
|
|
|
class MainMenu : public Menu { |
|
|
public: |
|
|
MainMenu(); |
|
|
private: |
|
|
// Menu interface |
|
|
void draw_background(px::GC &gc); |
|
|
|
|
|
// ActionListener interface. |
|
|
void on_action(Widget *w); |
|
|
|
|
|
// Private methods. |
|
|
void show_credits(); |
|
|
void show_help(); |
|
|
void show_text( const char *text[]); |
|
|
|
|
|
// Variables. |
|
|
Widget *m_startgame; |
|
|
Widget *leveled; |
|
|
Widget *manual; |
|
|
Widget *options; |
|
|
Widget *credits; |
|
|
Widget *quit; |
|
|
Widget *lpack; |
|
|
}; |
|
|
} |
|
|
|
|
43 |
|
|
44 |
//---------------------------------------------------------------------- |
//---------------------------------------------------------------------- |
45 |
// Level pack menu |
// Level pack menu |
444 |
} |
} |
445 |
|
|
446 |
|
|
447 |
// --------------------------------------------- |
//---------------------------------------- |
448 |
// Button representing an integer value |
// ValueButton implementation |
449 |
// --------------------------------------------- |
//---------------------------------------- |
450 |
namespace |
|
451 |
|
bool |
452 |
|
ValueButton::update_value(int old_value, int new_value) |
453 |
|
{ |
454 |
|
if (new_value<min_value) new_value = min_value; |
455 |
|
else if (new_value>max_value) new_value = max_value; |
456 |
|
|
457 |
|
if (new_value != old_value) { |
458 |
|
set_value(new_value); |
459 |
|
set_text(build_text(new_value)); |
460 |
|
return true; |
461 |
|
} |
462 |
|
return false; |
463 |
|
} |
464 |
|
|
465 |
|
bool |
466 |
|
ValueButton::on_event(const SDL_Event &e) |
467 |
{ |
{ |
468 |
class ValueButton: public TextButton { |
// handles button movement and |
469 |
int min_value; |
bool handled = PushButton::on_event(e); |
|
int max_value; |
|
470 |
|
|
471 |
bool update_value(int old_value, int new_value); |
switch (e.type) { |
472 |
|
case SDL_KEYDOWN: |
473 |
|
bool keyhandled = true; |
474 |
|
bool changed = false; |
475 |
|
|
476 |
public: |
switch (e.key.keysym.sym) { |
477 |
ValueButton(const std::string &t, int min_value_, int max_value_) |
case SDLK_LEFT: changed = inc_value(-1); break; |
478 |
: TextButton(t, this) |
case SDLK_RIGHT: changed = inc_value(1); break; |
479 |
, min_value(min_value_) |
default : |
480 |
, max_value(max_value_) |
keyhandled = false; |
481 |
{} |
break; |
|
|
|
|
virtual int get_value() const = 0; |
|
|
virtual void set_value(int value) = 0; |
|
|
virtual string build_text(int value) const = 0; |
|
|
|
|
|
bool inc_value(int offset) { |
|
|
int old_value = get_value(); |
|
|
return update_value(old_value, old_value+offset); |
|
|
} |
|
|
void update() { |
|
|
update_value(-1, get_value()); |
|
482 |
} |
} |
483 |
|
|
484 |
// Widget interface |
if (keyhandled) { |
485 |
virtual bool on_event(const SDL_Event &e); |
handled = true; |
486 |
virtual void on_action(Widget *w); |
sound::PlaySound(changed ? "menuswitch" : "menustop"); |
|
}; |
|
|
|
|
|
bool ValueButton::update_value(int old_value, int new_value) { |
|
|
if (new_value<min_value) new_value = min_value; |
|
|
else if (new_value>max_value) new_value = max_value; |
|
|
|
|
|
if (new_value != old_value) { |
|
|
set_value(new_value); |
|
|
set_text(build_text(new_value)); |
|
|
return true; |
|
|
} |
|
|
return false; |
|
|
} |
|
|
|
|
|
bool ValueButton::on_event(const SDL_Event &e) { |
|
|
// handles button movement and |
|
|
bool handled = PushButton::on_event(e); |
|
|
|
|
|
switch (e.type) { |
|
|
case SDL_KEYDOWN: |
|
|
bool keyhandled = true; |
|
|
bool changed = false; |
|
|
|
|
|
switch (e.key.keysym.sym) { |
|
|
case SDLK_LEFT: |
|
|
changed = inc_value(-1); |
|
|
break; |
|
|
case SDLK_RIGHT: |
|
|
changed = inc_value(1); |
|
|
break; |
|
|
default : |
|
|
keyhandled = false; |
|
|
break; |
|
|
} |
|
|
|
|
|
if (keyhandled) { |
|
|
handled = true; |
|
|
sound::PlaySound(changed ? "menuswitch" : "menustop"); |
|
|
} |
|
|
|
|
|
break; |
|
487 |
} |
} |
488 |
|
|
489 |
return handled; |
break; |
490 |
} |
} |
491 |
|
|
492 |
void ValueButton::on_action(Widget */*w*/) { |
return handled; |
493 |
if (!inc_value(1)) |
} |
|
update_value(get_value(), min_value); |
|
494 |
|
|
495 |
} |
void |
496 |
|
ValueButton::on_action(Widget */*w*/) |
497 |
|
{ |
498 |
|
if (!inc_value(1)) |
499 |
|
update_value(get_value(), min_value); |
500 |
} |
} |
501 |
|
|
502 |
|
|
750 |
right.add (new SoundVolumeButton); |
right.add (new SoundVolumeButton); |
751 |
right.add (new SoundSetButton); |
right.add (new SoundSetButton); |
752 |
right.add (new MusicVolumeButton); |
right.add (new MusicVolumeButton); |
753 |
right.add (new InGameMusicButton); |
// right.add (new InGameMusicButton); |
754 |
right.add (new StereoButton); |
right.add (new StereoButton); |
755 |
|
|
756 |
{ |
{ |
1038 |
{ |
{ |
1039 |
BuildVList b(this, Rect((640-150)/2,150,150,40), 5); |
BuildVList b(this, Rect((640-150)/2,150,150,40), 5); |
1040 |
m_startgame = b.add(new TextButton("Start Game", this)); |
m_startgame = b.add(new TextButton("Start Game", this)); |
1041 |
leveled = b.add(new TextButton("Editor", this)); |
// leveled = b.add(new TextButton("Editor", this)); |
1042 |
manual = b.add(new TextButton("Manual", this)); |
manual = b.add(new TextButton("Manual", this)); |
1043 |
options = b.add(new TextButton("Options", this)); |
options = b.add(new TextButton("Options", this)); |
1044 |
credits = b.add(new TextButton("Credits", this)); |
credits = b.add(new TextButton("Credits", this)); |
1154 |
"another. Just play the first levels in the \"Oxyd & Co\" group and you", |
"another. Just play the first levels in the \"Oxyd & Co\" group and you", |
1155 |
"will get the idea.", |
"will get the idea.", |
1156 |
"In some other levels, called \"meditation landscapes\" you have a", |
"In some other levels, called \"meditation landscapes\" you have a", |
1157 |
"somewhat different job: You control a couple of small white marbles", |
"different job: You control a couple of small white marbles", |
1158 |
"simultaneously and have to put each of them into a pit on the floor.", |
"simultaneously and have to put each of them into a pit on the floor.", |
1159 |
"", |
"", |
1160 |
"Moving around:", |
"Moving around:", |
1189 |
0 |
0 |
1190 |
}; |
}; |
1191 |
static const char *screen3[] = { |
static const char *screen3[] = { |
1192 |
"Something about stones:", |
"Stones:", |
1193 |
"", |
"", |
1194 |
"Most of the time the stones are nothing more than walls.", |
"Most stones are nothing more than walls.", |
1195 |
"Some special stones are movable when hit strong enought.", |
"Many stones are movable when hit strong enough.", |
1196 |
"The wooden stone will build new floor if moved into water, space or", |
"The wooden stone will build new floor if moved into water, space or", |
1197 |
"abyss.", |
"abyss.", |
1198 |
"Others can be destroyed using a hammer, dynamite or a laser.", |
"Others can be destroyed using a hammer, dynamite or a laser.", |
1199 |
"Doors can be opened using a switch or trigger hidden somewhere", |
"Doors can be opened using a switch or trigger hidden somewhere", |
1200 |
"around in the level.", |
"around in the level.", |
1201 |
"Some magic stones can be changed when hit using a magicwand.", |
"Some magic stones can be changed when hit using a magic wand.", |
1202 |
"And some depend on the color of your marble.", |
"And some depend on the color of your marble.", |
1203 |
"", |
"", |
1204 |
0 |
0 |