wesnoth/menu.cpp
Dave White 5fd5346808 Allow resolution to be configurable in preferences file...
...using xresolution and yresolution options
2003-09-18 12:47:17 +00:00

911 lines
24 KiB
C++

/* $Id$ */
/*
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#include "config.hpp"
#include "font.hpp"
#include "language.hpp"
#include "menu.hpp"
#include "playlevel.hpp"
#include "language.hpp"
#include "sdl_utils.hpp"
#include "widgets/button.hpp"
#include "widgets/textbox.hpp"
#include <iostream>
#include <numeric>
namespace gui {
void draw_dialog_frame(int x, int y, int w, int h, display& disp)
{
const int border_size = 6;
SDL_Surface* const scr = disp.video().getSurface();
const display::Pixel border_colour = SDL_MapRGB(scr->format,200,0,0);
draw_solid_tinted_rectangle(x-border_size,y-border_size,
w+border_size,h+border_size,0,0,0,0.6,scr);
draw_solid_tinted_rectangle(x,y,w+border_size,h+border_size,0,0,0,0.6,scr);
draw_rectangle(x-border_size,y-border_size,w+border_size,h+border_size,
border_colour,scr);
draw_rectangle(x,y,w+border_size,h+border_size,border_colour,scr);
SDL_Rect update = {x-border_size,y-border_size,
w+border_size*2+1,h+border_size*2+1};
disp.update_rect(update);
}
void draw_rectangle(int x, int y, int w, int h, short colour,
SDL_Surface* target)
{
if(x < 0 || y < 0 || x+w >= target->w || y+h >= target->h) {
std::cerr << "Rectangle has illegal co-ordinates: " << x << "," << y
<< "," << w << "," << h << "\n";
return;
}
short* top_left = reinterpret_cast<short*>(target->pixels) + target->w*y+x;
short* top_right = top_left + w;
short* bot_left = top_left + target->w*h;
short* bot_right = bot_left + w;
std::fill(top_left,top_right+1,colour);
std::fill(bot_left,bot_right+1,colour);
while(top_left != bot_left) {
*top_left = colour;
*top_right = colour;
top_left += target->w;
top_right += target->w;
}
}
void draw_solid_tinted_rectangle(int x, int y, int w, int h,
int r, int g, int b,
double alpha, SDL_Surface* target)
{
if(x < 0 || y < 0 || x+w >= target->w || y+h >= target->h) {
std::cerr << "Rectangle has illegal co-ordinates: " << x << "," << y
<< "," << w << "," << h << "\n";
return;
}
const SDL_PixelFormat* const fmt = target->format;
short* p = reinterpret_cast<short*>(target->pixels) + target->w*y + x;
while(h > 0) {
short* beg = p;
short* const end = p + w;
while(beg != end) {
const int cur_r = ((*beg&fmt->Rmask) >> fmt->Rshift) << fmt->Rloss;
const int cur_g = ((*beg&fmt->Gmask) >> fmt->Gshift) << fmt->Gloss;
const int cur_b = ((*beg&fmt->Bmask) >> fmt->Bshift) << fmt->Bloss;
const int new_r = int(double(cur_r)*(1.0-alpha) + double(r)*alpha);
const int new_g = int(double(cur_g)*(1.0-alpha) + double(g)*alpha);
const int new_b = int(double(cur_b)*(1.0-alpha) + double(b)*alpha);
*beg = ((new_r >> fmt->Rloss) << fmt->Rshift) |
((new_g >> fmt->Gloss) << fmt->Gshift) |
((new_b >> fmt->Bloss) << fmt->Bshift);
++beg;
}
p += target->w;
--h;
}
}
} //end namespace gui
namespace {
const int max_menu_items = 18;
const int menu_font_size = 16;
const int menu_cell_padding = 10;
class menu
{
display* display_;
int x_, y_;
std::vector<std::vector<std::string> > items_;
mutable std::vector<int> column_widths_;
scoped_sdl_surface buffer_;
int selected_;
bool click_selects_;
bool previous_button_;
bool drawn_;
mutable int height_;
mutable int width_;
mutable int first_item_on_screen_;
gui::button uparrow_, downarrow_;
const std::vector<int>& column_widths() const
{
if(column_widths_.empty()) {
for(int row = 0; row != items_.size(); ++row) {
for(int col = 0; col != items_[row].size(); ++col) {
static const SDL_Rect area =
{0,0,display_->x(),display_->y()};
const SDL_Rect res =
font::draw_text(NULL,area,menu_font_size,
font::NORMAL_COLOUR,items_[row][col],x_,y_);
if(col == column_widths_.size()) {
column_widths_.push_back(res.w + menu_cell_padding);
} else if(res.w > column_widths_[col] - menu_cell_padding) {
column_widths_[col] = res.w + menu_cell_padding;
}
}
}
}
return column_widths_;
}
void draw_item(int item) {
SDL_Rect rect = get_item_rect(item);
if(rect.w == 0)
return;
short* const dstptr = reinterpret_cast<short*>(
display_->video().getSurface()->pixels) +
rect.y*display_->x() + x_;
if(buffer_.get() != NULL) {
const int ypos = items_start()+(item-first_item_on_screen_)*rect.h;
SDL_Rect srcrect = {0,ypos,rect.w,rect.h};
SDL_BlitSurface(buffer_,&srcrect,
display_->video().getSurface(),&rect);
}
short* dst = dstptr;
gui::draw_solid_tinted_rectangle(x_,rect.y,width(),rect.h,
item == selected_ ? 150:0,0,0,
0.7,display_->video().getSurface());
SDL_Rect area = {0,0,display_->x(),display_->y()};
const std::vector<int>& widths = column_widths();
int xpos = rect.x;
for(int i = 0; i != items_[item].size(); ++i) {
font::draw_text(display_,area,menu_font_size,font::NORMAL_COLOUR,
items_[item][i],xpos,rect.y);
xpos += widths[i];
}
}
void draw() {
drawn_ = true;
for(int i = 0; i != items_.size(); ++i)
draw_item(i);
display_->video().update(x_,y_,width(),height());
}
int hit(int x, int y) const {
if(x > x_ && x < x_ + width() && y > y_ && y < y_ + height()){
for(int i = 0; i != items_.size(); ++i) {
const SDL_Rect& rect = get_item_rect(i);
if(y > rect.y && y < rect.y + rect.h)
return i;
}
}
return -1;
}
mutable std::map<int,SDL_Rect> itemRects_;
SDL_Rect get_item_rect(int item) const
{
const SDL_Rect empty_rect = {0,0,0,0};
if(item < first_item_on_screen_ ||
item >= first_item_on_screen_ + max_menu_items) {
return empty_rect;
}
const std::map<int,SDL_Rect>::const_iterator i = itemRects_.find(item);
if(i != itemRects_.end())
return i->second;
int y = y_ + items_start();
if(item != first_item_on_screen_) {
const SDL_Rect& prev = get_item_rect(item-1);
y = prev.y + prev.h;
}
static const SDL_Rect area = {0,0,display_->x(),display_->y()};
SDL_Rect res = font::draw_text(NULL,area,menu_font_size,
font::NORMAL_COLOUR,items_[item][0],x_,y);
res.w = width();
//only insert into the cache if the menu's co-ordinates have
//been initialized
if(x_ > 0 && y_ > 0)
itemRects_.insert(std::pair<int,SDL_Rect>(item,res));
return res;
}
int items_start() const
{
if(items_.size() > max_menu_items)
return uparrow_.height();
else
return 0;
}
int items_end() const
{
if(items_.size() > max_menu_items)
return height() - downarrow_.height();
else
return height();
}
int items_height() const
{
return items_end() - items_start();
}
public:
menu(display& disp, const std::vector<std::string>& items,
bool click_selects=false)
: display_(&disp), x_(0), y_(0), buffer_(NULL),
selected_(-1), click_selects_(click_selects),
previous_button_(true), drawn_(false), height_(-1), width_(-1),
first_item_on_screen_(0),
uparrow_(disp,"",gui::button::TYPE_PRESS,"uparrow"),
downarrow_(disp,"",gui::button::TYPE_PRESS,"downarrow")
{
for(std::vector<std::string>::const_iterator item = items.begin();
item != items.end(); ++item) {
items_.push_back(config::split(*item));
//make sure there is always at least one item
if(items_.back().empty())
items_.back().push_back(" ");
}
}
int height() const {
if(height_ == -1) {
const SDL_Rect area = { 0, 0, display_->x(), display_->y() };
height_ = 0;
for(int i = 0; i != items_.size() && i != max_menu_items; ++i) {
height_ += get_item_rect(i).h;
}
if(items_.size() > max_menu_items) {
height_ += uparrow_.height() + downarrow_.height();
}
}
return height_;
}
int width() const {
if(width_ == -1) {
const std::vector<int>& widths = column_widths();
width_ = std::accumulate(widths.begin(),widths.end(),0);
}
return width_;
}
int selection() const { return selected_; }
void set_loc(int x, int y) {
x_ = x;
y_ = y;
const int w = width();
SDL_Rect portion = {x_,y_,w,height()};
SDL_Surface* const screen = display_->video().getSurface();
buffer_.assign(get_surface_portion(screen, portion));
if(items_.size() > max_menu_items) {
uparrow_.set_x(x_);
uparrow_.set_y(y_);
downarrow_.set_x(x_);
downarrow_.set_y(y_+items_end());
}
}
int process(int x, int y, bool button,bool up_arrow,bool down_arrow,
bool page_up, bool page_down) {
if(items_.size() > max_menu_items) {
const bool up = uparrow_.process(x,y,button);
if(up && first_item_on_screen_ > 0) {
itemRects_.clear();
--first_item_on_screen_;
draw();
}
const bool down = downarrow_.process(x,y,button);
if(down && first_item_on_screen_ + max_menu_items < items_.size()) {
itemRects_.clear();
++first_item_on_screen_;
draw();
}
}
if(up_arrow && !click_selects_ && selected_ > 0) {
--selected_;
if(selected_ < first_item_on_screen_) {
itemRects_.clear();
first_item_on_screen_ = selected_;
}
draw();
}
if(down_arrow && !click_selects_ && selected_ < items_.size()-1) {
++selected_;
if(selected_ - first_item_on_screen_ == max_menu_items) {
itemRects_.clear();
++first_item_on_screen_;
}
draw();
}
if(page_up && !click_selects_) {
selected_ -= max_menu_items;
if(selected_ < 0)
selected_ = 0;
itemRects_.clear();
first_item_on_screen_ = selected_;
draw();
}
if(page_down && !click_selects_) {
selected_ += max_menu_items;
if(selected_ >= items_.size())
selected_ = items_.size()-1;
first_item_on_screen_ = selected_ - (max_menu_items-1);
if(first_item_on_screen_ < 0)
first_item_on_screen_ = 0;
itemRects_.clear();
draw();
}
const int starting_selected = selected_;
const int hit_item = hit(x,y);
if(click_selects_) {
selected_ = hit_item;
if(button && !previous_button_)
return selected_;
else {
if(!drawn_ || selected_ != starting_selected)
draw();
previous_button_ = button;
return -1;
}
}
if(button && hit_item != -1){
selected_ = hit_item;
}
if(selected_ == -1)
selected_ = 0;
if(selected_ != starting_selected)
draw();
return -1;
}
};
}
namespace gui
{
int show_dialog(display& disp, SDL_Surface* image,
const std::string& caption, const std::string& msg,
DIALOG_TYPE type,
const std::vector<std::string>* menu_items_ptr,
const std::vector<unit>* units_ptr,
const std::string& text_widget_label,
std::string* text_widget_text)
{
if(disp.update_locked())
return -1;
const std::vector<std::string>& menu_items =
(menu_items_ptr == NULL) ? std::vector<std::string>() : *menu_items_ptr;
const std::vector<unit>& units =
(units_ptr == NULL) ? std::vector<unit>() : *units_ptr;
static const int message_font_size = 16;
static const int caption_font_size = 18;
CVideo& screen = disp.video();
SDL_Surface* const scr = screen.getSurface();
SDL_Rect clipRect = { 0, 0, disp.x(), disp.y() };
const bool use_textbox = text_widget_text != NULL;
static const std::string default_text_string = "";
const unsigned int text_box_width = 350;
textbox text_widget(disp,text_box_width,
use_textbox ? *text_widget_text : default_text_string);
int text_widget_width = 0;
int text_widget_height = 0;
if(use_textbox) {
text_widget_width =
font::draw_text(NULL, clipRect, message_font_size,
font::NORMAL_COLOUR, text_widget_label, 0, 0, NULL).w +
text_widget.width();
text_widget_height = text_widget.height();
}
menu menu_(disp,menu_items,type == MESSAGE);
const int border_size = 6;
const short text_colour = 0xFFFF;
const short border_colour = 0xF000;
int nlines = 1;
int longest_line = 0;
int cur_line = 0;
const int max_line_length = 58;
std::string message = msg;
for(std::string::iterator message_it = message.begin();
message_it != message.end(); ++message_it) {
if(*message_it == ' ' && cur_line > max_line_length)
*message_it = '\n';
if(*message_it == '\n') {
if(cur_line > longest_line)
longest_line = cur_line;
cur_line = 0;
++nlines;
} else {
++cur_line;
}
}
SDL_Rect text_size = { 0, 0, 0, 0 };
if(!message.empty()) {
text_size = font::draw_text(NULL, clipRect, message_font_size,
font::NORMAL_COLOUR, message, 0, 0, NULL);
}
SDL_Rect caption_size = { 0, 0, 0, 0 };
if(!caption.empty()) {
caption_size = font::draw_text(NULL, clipRect, caption_font_size,
font::NORMAL_COLOUR,caption,0,0,NULL);
}
const std::string* button_list = NULL;
std::vector<button> buttons;
switch(type) {
case MESSAGE:
break;
case OK_ONLY: {
static const std::string thebuttons[] = { "ok_button", "" };
button_list = thebuttons;
break;
}
case YES_NO: {
static const std::string thebuttons[] = { "yes_button",
"no_button", ""};
button_list = thebuttons;
break;
}
case OK_CANCEL: {
static const std::string thebuttons[] = { "ok_button",
"cancel_button",""};
button_list = thebuttons;
break;
}
}
const int button_height_padding = 10;
int button_width_padding = 0;
int button_heights = 0;
int button_widths = 0;
if(button_list != NULL) {
try {
while(button_list->empty() == false) {
buttons.push_back(button(disp,string_table[*button_list]));
if(buttons.back().height() > button_heights)
button_heights = buttons.back().height();
button_widths += buttons.back().width();
button_width_padding += 5;
++button_list;
}
} catch(button::error&) {
std::cerr << "error initializing button!\n";
}
}
if(button_heights > 0) {
button_heights += button_height_padding;
}
if(cur_line > longest_line)
longest_line = cur_line;
const int left_padding = 10;
const int right_padding = 10;
const int image_h_padding = image != NULL ? 10 : 0;
const int top_padding = 10;
const int bottom_padding = 10;
const int padding_width = left_padding + right_padding + image_h_padding;
const int padding_height = top_padding + bottom_padding;
const int caption_width = caption_size.w;
const int image_width = image != NULL ? image->w : 0;
const int total_image_width = caption_width > image_width ?
caption_width : image_width;
const int image_height = image != NULL ? image->h : 0;
int text_width = text_size.w;
if(menu_.width() > text_width)
text_width = menu_.width();
int total_width = total_image_width + text_width +
padding_width;
if(button_widths + button_width_padding > total_width)
total_width = button_widths + button_width_padding;
if(text_widget_width+left_padding+right_padding > total_width)
total_width = text_widget_width+left_padding+right_padding;
const int total_height = (image_height+8 > text_size.h ?
image_height+8 : text_size.h) +
padding_height + button_heights + menu_.height() +
text_widget_height;
if(total_width > scr->w - 100 || total_height > scr->h - 100)
return false;
int xloc = scr->w/2 - total_width/2;
int yloc = scr->h/2 - total_height/2;
int unitx = 0;
int unity = 0;
//if we are showing a dialog with unit details, then we have
//to make more room for it
if(!units.empty()) {
xloc += scr->w/10;
unitx = xloc - 300;
if(unitx < 10)
unitx = 10;
unity = yloc;
}
//make sure that the dialog doesn't overlap the right part of the screen
if(xloc + total_width+border_size >= disp.mapx()-1) {
xloc = disp.mapx()-(total_width+border_size+2);
if(xloc < 0)
return -1;
}
const int button_hpadding = total_width - button_widths;
int button_offset = 0;
for(int button_num = 0; button_num != buttons.size(); ++button_num) {
const int padding_amount = button_hpadding/(buttons.size()+1);
buttons[button_num].set_x(xloc + padding_amount*(button_num+1) +
button_offset);
buttons[button_num].set_y(yloc + total_height - button_heights);
button_offset += buttons[button_num].width();
}
if(menu_.height() > 0)
menu_.set_loc(xloc+total_image_width+left_padding+image_h_padding,
yloc+top_padding+text_size.h);
draw_dialog_frame(xloc,yloc,total_width,total_height,disp);
if(image != NULL) {
const int x = xloc + left_padding;
const int y = yloc + top_padding;
disp.blit_surface(x,y,image);
int center_font = 0;
if(caption_size.w < image->w) {
center_font = image->w/2 - caption_size.w/2;
}
font::draw_text(&disp, clipRect, caption_font_size,
font::NORMAL_COLOUR, caption,
xloc+left_padding+center_font,
yloc+top_padding+image->h-6, NULL);
}
if(!units.empty()) {
const int unitw = 200;
const int unith = disp.y()/2;
draw_solid_tinted_rectangle(unitx,unity,unitw,unith,
0,0,0,1.0,scr);
draw_rectangle(unitx,unity,unitw,unith,border_colour,scr);
}
font::draw_text(&disp, clipRect, message_font_size,
font::NORMAL_COLOUR, message,
xloc+total_image_width+left_padding+image_h_padding,
yloc+top_padding);
if(use_textbox) {
const int image_h = image != NULL ? image->h : 0;
const int text_widget_y = yloc+top_padding+image_h-6+text_size.h;
text_widget.set_location(xloc + left_padding +
text_widget_width - text_widget.width(),
text_widget_y);
text_widget.draw();
font::draw_text(&disp, clipRect, message_font_size,
font::NORMAL_COLOUR, text_widget_label,
xloc + left_padding,text_widget_y);
}
screen.update(0,0,scr->w,scr->h);
CKey key;
bool left_button = true, right_button = true, key_down = true,
up_arrow = false, down_arrow = false,
page_up = false, page_down = false;
disp.invalidate_all();
int cur_selection = -1;
SDL_Rect unit_details_rect, unit_profile_rect;
unit_details_rect.w = 0;
unit_profile_rect.w = 0;
bool first_time = true;
for(;;) {
int mousex, mousey;
const int mouse_flags = SDL_GetMouseState(&mousex,&mousey);
const bool new_right_button = mouse_flags&SDL_BUTTON_RMASK;
const bool new_left_button = mouse_flags&SDL_BUTTON_LMASK;
const bool new_key_down = key[KEY_SPACE] || key[KEY_ENTER] ||
key[KEY_ESCAPE];
const bool new_up_arrow = key[KEY_UP];
const bool new_down_arrow = key[KEY_DOWN];
const bool new_page_up = key[SDLK_PAGEUP];
const bool new_page_down = key[SDLK_PAGEDOWN];
if(!key_down && key[KEY_ENTER] &&
(type == YES_NO || type == OK_CANCEL)) {
if(menu_.height() == 0) {
return 0;
} else {
return menu_.selection();
}
}
if(menu_.selection() != cur_selection || first_time) {
cur_selection = menu_.selection();
int selection = cur_selection;
if(first_time)
selection = 0;
if(selection >= 0 && selection < units.size()) {
SDL_Surface* const screen = disp.video().getSurface();
if(unit_details_rect.w > 0) {
SDL_FillRect(screen,&unit_details_rect,0);
disp.update_rect(unit_details_rect);
}
if(unit_profile_rect.w > 0) {
SDL_FillRect(screen,&unit_profile_rect,0);
disp.update_rect(unit_profile_rect);
}
disp.draw_unit_details(unitx+left_padding,
unity+top_padding, gamemap::location(), units[selection],
unit_details_rect, unit_profile_rect);
disp.update_display();
}
}
first_time = false;
if(menu_.height() > 0) {
const int res = menu_.process(mousex,mousey,new_left_button,
!up_arrow && new_up_arrow,
!down_arrow && new_down_arrow,
!page_up && new_page_up,
!page_down && new_page_down);
if(res != -1)
return res;
}
up_arrow = new_up_arrow;
down_arrow = new_down_arrow;
page_up = new_page_up;
page_down = new_page_down;
if(use_textbox) {
text_widget.process();
}
if(buttons.empty() && (new_left_button && !left_button ||
new_right_button && !right_button) ||
buttons.size() < 2 && new_key_down && !key_down &&
menu_.height() == 0)
break;
left_button = new_left_button;
right_button = new_right_button;
key_down = new_key_down;
for(std::vector<button>::iterator button_it = buttons.begin();
button_it != buttons.end(); ++button_it) {
if(button_it->process(mousex,mousey,left_button)) {
if(text_widget_text != NULL && use_textbox)
*text_widget_text = text_widget.text();
//if the menu is not used, then return the index of the
//button pressed, otherwise return the index of the menu
//item selected if the last button is not pressed, and
//cancel (-1) otherwise
if(menu_.height() == 0) {
return button_it - buttons.begin();
} else if(buttons.size() <= 1 ||
button_it - buttons.begin() != buttons.size()-1) {
return menu_.selection();
} else {
return -1;
}
}
}
SDL_PumpEvents();
}
return -1;
}
TITLE_RESULT show_title(display& screen)
{
SDL_Surface* const title_surface = screen.getImage("title.png",
display::UNSCALED);
if(title_surface == NULL) {
std::cerr << "Could not find title image 'title.png'\n";
return QUIT_GAME;
}
const int x = screen.x()/2 - title_surface->w/2;
const int y = 100;
screen.blit_surface(x,y,title_surface);
button tutorial_button(screen,string_table["tutorial_button"]);
button new_button(screen,string_table["campaign_button"]);
button load_button(screen,string_table["load_button"]);
button multi_button(screen,string_table["multiplayer_button"]);
button quit_button(screen,string_table["quit_button"]);
button language_button(screen,string_table["language_button"]);
tutorial_button.set_x(700);
new_button.set_x(700);
load_button.set_x(700);
multi_button.set_x(700);
quit_button.set_x(700);
language_button.set_x(700);
tutorial_button.set_y(200);
new_button.set_y(250);
load_button.set_y(300);
multi_button.set_y(350);
language_button.set_y(400);
quit_button.set_y(450);
bool right_button = true;
bool left_button = true;
tutorial_button.draw();
new_button.draw();
load_button.draw();
multi_button.draw();
quit_button.draw();
language_button.draw();
screen.video().update(0,0,screen.x(),screen.y());
CKey key;
for(;;) {
int mousex, mousey;
const int mouse_flags = SDL_GetMouseState(&mousex,&mousey);
const bool right_button = mouse_flags&SDL_BUTTON_RMASK;
const bool left_button = mouse_flags&SDL_BUTTON_LMASK;
if(tutorial_button.process(mousex,mousey,left_button))
return TUTORIAL;
if(new_button.process(mousex,mousey,left_button))
return NEW_CAMPAIGN;
if(load_button.process(mousex,mousey,left_button))
return LOAD_GAME;
if(multi_button.process(mousex,mousey,left_button))
return MULTIPLAYER;
if(quit_button.process(mousex,mousey,left_button))
return QUIT_GAME;
if(key[KEY_ESCAPE])
return QUIT_GAME;
if(language_button.process(mousex,mousey,left_button))
return CHANGE_LANGUAGE;
SDL_PumpEvents();
SDL_Delay(20);
}
return QUIT_GAME;
}
void check_quit(display& gui)
{
CKey key;
if(key[KEY_ESCAPE]) {
const int res = gui::show_dialog(gui,NULL,"",
string_table["quit_message"],gui::YES_NO);
if(res == 0) {
throw end_level_exception(QUIT);
}
}
}
} //end namespace gui