New game creation dialog

This commit is contained in:
Justin Zaun 2003-11-15 23:38:07 +00:00
parent d7a6a862d7
commit 97689acdd0
9 changed files with 473 additions and 217 deletions

View file

@ -171,8 +171,14 @@ SDL_Rect draw_text_line(display* gui, const SDL_Rect& area, int size,
}
SDL_Rect dest;
dest.x = x;
dest.y = y;
if(x!=-1)
dest.x = x;
else
dest.x = (area.w/2)-(surface->w/2);
if(y!=-1)
dest.y = y;
else
dest.y = (area.h/2)-(surface->h/2);
dest.w = surface->w;
dest.h = surface->h;

View file

@ -1,6 +1,8 @@
#include "game_config.hpp"
#include "image.hpp"
#include "display.hpp"
#include "sdl_utils.hpp"
#include "util.hpp"
#include "SDL_image.h"
@ -250,5 +252,42 @@ SDL_Surface* get_image_dim(const std::string& filename, size_t x, size_t y)
return surf;
}
SDL_Surface* getMinimap(CVideo& video, int w, int h, gamemap& map_)
{
SDL_Surface* const surface = video.getSurface();
}
SDL_Surface *minimap_ = SDL_CreateRGBSurface(SDL_SWSURFACE,
map_.x(),map_.y(),
surface->format->BitsPerPixel,
surface->format->Rmask,
surface->format->Gmask,
surface->format->Bmask,
surface->format->Amask);
if(minimap_ == NULL)
return NULL;
const int xpad = is_odd(minimap_->w);
surface_lock lock(minimap_);
short* data = lock.pixels();
for(int y = 0; y != map_.y(); ++y) {
for(int x = 0; x != map_.x(); ++x) {
*data = map_.get_terrain_info(map_[x][y]).get_rgb().
format(surface->format);
++data;
}
data += xpad;
}
if(minimap_->w != w || minimap_->h != h) {
SDL_Surface* const surf = minimap_;
minimap_ = scale_surface(surf,w,h);
SDL_FreeSurface(surf);
}
return minimap_;
}
}

View file

@ -1,10 +1,10 @@
#ifndef IMAGE_HPP_INCLUDED
#define IMAGE_HPP_INCLUDED
#include "SDL.h"
#include "video.hpp"
#include "map.hpp"
#include "SDL.h"
#include <string>
//this module manages the cache of images. With an image name, you can get
@ -51,6 +51,8 @@ namespace image {
//if you later try to get the same image using get_image() the image will
//have the dimensions specified here.
SDL_Surface* get_image_dim(const std::string& filename, size_t x, size_t y);
SDL_Surface* getMinimap(CVideo& video, int w, int h, gamemap& map_);
}
#endif

View file

@ -11,8 +11,11 @@
See the COPYING file for more details.
*/
#include "events.hpp"
#include "font.hpp"
#include "language.hpp"
#include "log.hpp"
#include "image.hpp"
#include "multiplayer.hpp"
#include "multiplayer_client.hpp"
#include "network.hpp"
@ -20,6 +23,10 @@
#include "preferences.hpp"
#include "replay.hpp"
#include "show_dialog.hpp"
#include "widgets/textbox.hpp"
#include "widgets/button.hpp"
#include "widgets/menu.hpp"
#include "widgets/slider.hpp"
#include <iostream>
#include <sstream>
@ -240,9 +247,11 @@ bool accept_network_connections(display& disp, config& players)
}
void play_multiplayer(display& disp, game_data& units_data, config cfg,
int play_multiplayer(display& disp, game_data& units_data, config cfg,
game_state& state, bool server)
{
SDL_Rect rect;
char buf[100];
log_scope("play multiplayer");
//ensure we send a close game message to the server when we are done
@ -252,6 +261,33 @@ void play_multiplayer(display& disp, game_data& units_data, config cfg,
//later allow configuration of amount of gold
state.gold = 100;
// Dialog width and height
int width=600;
int height=290;
int cur_selection = -1;
int cur_villagegold = 2;
int new_villagegold = 2;
int cur_turns = 50;
int new_turns = 50;
gui::draw_dialog_frame((disp.x()-width)/2, (disp.y()-height)/2,
width, height, disp);
//Title
font::draw_text(&disp,disp.screen_area(),24,font::NORMAL_COLOUR,
"Create Game",-1,(disp.y()-height)/2+5);
//Name Entry
font::draw_text(&disp,disp.screen_area(),12,font::GOOD_COLOUR,
"Name of game:",(disp.x()-width)/2+10,(disp.y()-height)/2+38);
gui::textbox name_entry(disp,width-20);
name_entry.set_location((disp.x()-width)/2+10,(disp.y()-height)/2+55);
//Maps
font::draw_text(&disp,disp.screen_area(),12,font::GOOD_COLOUR,
"Map to play:",(disp.x()-width)/2+(int)(width*0.4),
(disp.y()-height)/2+83);
std::vector<std::string> options;
std::vector<config*>& levels = cfg.children["multiplayer"];
std::map<int,std::string> res_to_id;
@ -267,211 +303,370 @@ void play_multiplayer(display& disp, game_data& units_data, config cfg,
}
options.push_back("Load game...");
gui::menu maps_menu(disp,options);
maps_menu.set_loc((disp.x()-width)/2+(int)(width*0.4),(disp.y()-height)/2+100);
int res = gui::show_dialog(disp,NULL,"",
string_table["choose_scenario"],gui::OK_CANCEL,
&options);
if(res == -1)
return;
//Game Turns
rect.x = (disp.x()-width)/2+(int)(width*0.4)+maps_menu.width()+19;
rect.y = (disp.y()-height)/2+83;
rect.w = ((disp.x()-width)/2+width)-((disp.x()-width)/2+(int)(width*0.4)+maps_menu.width()+19)-10;
rect.h = 12;
SDL_Surface* village_bg=get_surface_portion(disp.video().getSurface(), rect);
font::draw_text(&disp,disp.screen_area(),12,font::GOOD_COLOUR,
"Turns: 50",rect.x,rect.y);
rect.y = (disp.y()-height)/2+100;
rect.h = name_entry.width();
gui::slider turns_slider(disp,rect,0.38);
//Village Gold
rect.x = (disp.x()-width)/2+(int)(width*0.4)+maps_menu.width()+19;
rect.y = (disp.y()-height)/2+130;
rect.w = ((disp.x()-width)/2+width)-((disp.x()-width)/2+(int)(width*0.4)+maps_menu.width()+19)-10;
rect.h = 12;
//SDL_Surface* village_bg=get_surface_portion(disp.video().getSurface(), rect);
font::draw_text(&disp,disp.screen_area(),12,font::GOOD_COLOUR,
"Village Gold: 2",rect.x,rect.y);
rect.y = (disp.y()-height)/2+147;
rect.h = name_entry.width();
gui::slider villagegold_slider(disp,rect,0.1);
//FOG fo war
gui::button fog_game(disp,"Fog Of War",gui::button::TYPE_CHECK);
fog_game.set_check(true);
fog_game.set_xy(rect.x+6,rect.y+30);
fog_game.draw();
//Shroud
gui::button shroud_game(disp,"Shroud",gui::button::TYPE_CHECK);
shroud_game.set_check(false);
shroud_game.set_xy(rect.x+6,rect.y+60);
shroud_game.draw();
//Buttons
gui::button cancel_game(disp,string_table["cancel_button"]);
gui::button launch_game(disp,string_table["ok_button"]);
launch_game.set_xy((disp.x()/2)-launch_game.width()*2-19,(disp.y()-height)/2+height-29);
cancel_game.set_xy((disp.x()/2)+cancel_game.width()+19,(disp.y()-height)/2+height-29);
update_whole_screen();
CKey key;
config* level_ptr = NULL;
config loaded_level;
if(size_t(res) == options.size()-1) {
const std::vector<std::string>& games = get_saves_list();
if(games.empty()) {
gui::show_dialog(disp,NULL,
string_table["no_saves_heading"],
string_table["no_saves_message"],
gui::OK_ONLY);
return;
}
for(;;) {
int mousex, mousey;
const int mouse_flags = SDL_GetMouseState(&mousex,&mousey);
const bool left_button = mouse_flags&SDL_BUTTON_LMASK;
const int res = gui::show_dialog(disp,NULL,
string_table["load_game_heading"],
string_table["load_game_message"],
gui::OK_CANCEL, &games);
if(res == -1)
return;
maps_menu.process(mousex,mousey,left_button,
key[SDLK_UP],key[SDLK_DOWN],
key[SDLK_PAGEUP],key[SDLK_PAGEDOWN]);
load_game(units_data,games[res],state);
if(cancel_game.process(mousex,mousey,left_button))
return -1;
if(state.campaign_type != "multiplayer") {
gui::show_dialog(disp,NULL,"",
"This is not a multiplayer save",gui::OK_ONLY);
return;
}
if(launch_game.process(mousex,mousey,left_button)) {
if(name_entry.text().empty() == false) {
// Send Initial information
config response;
config create_game;
create_game["name"] = name_entry.text();
response.children["create_game"].push_back(
new config(create_game));
network::send_data(response);
if(state.version != game_config::version) {
const int res = gui::show_dialog(disp,NULL,"",
string_table["version_save_message"],
gui::YES_NO);
if(res == 1)
return;
}
loaded_level = state.starting_pos;
level_ptr = &loaded_level;
// Setup the game
int res = maps_menu.selection();
//make all sides untaken
for(config::child_itors i = level_ptr->child_range("side");
i.first != i.second; ++i.first) {
(**i.first)["taken"] = "";
}
if(res == -1)
break;
recorder = replay(state.replay_data);
//add the replay data under the level data so clients can
//receive it
level_ptr->children["replay"].clear();
level_ptr->add_child("replay") = state.replay_data;
} else {
level_ptr = levels[res];
}
assert(level_ptr != NULL);
config& level = *level_ptr;
state.label = level.values["name"];
state.scenario = res_to_id[res];
std::vector<config*>& sides = level.children["side"];
std::vector<config*>& possible_sides = cfg.children["multiplayer_side"];
if(sides.empty() || possible_sides.empty()) {
std::cerr << "no multiplayer sides found\n";
return;
}
for(std::vector<config*>::iterator sd = sides.begin();
sd != sides.end(); ++sd) {
if((*sd)->values["name"].empty())
(*sd)->values["name"] = possible_sides.front()->values["name"];
if((*sd)->values["type"].empty())
(*sd)->values["type"] = possible_sides.front()->values["type"];
if((*sd)->values["recruit"].empty())
(*sd)->values["recruit"]=possible_sides.front()->values["recruit"];
if((*sd)->values["music"].empty())
(*sd)->values["music"]=possible_sides.front()->values["music"];
if((*sd)->values["recruitment_pattern"].empty())
(*sd)->values["recruitment_pattern"] =
possible_sides.front()->values["recruitment_pattern"];
if((*sd)->values["description"].empty())
(*sd)->values["description"] = preferences::login();
}
res = 0;
while(size_t(res) != sides.size()) {
std::vector<std::string> sides_list;
for(std::vector<config*>::iterator sd = sides.begin();
sd != sides.end(); ++sd) {
std::stringstream details;
details << (*sd)->values["side"] << ","
<< (*sd)->values["name"] << ",";
const std::string& controller = (*sd)->values["controller"];
if(controller == "human")
details << string_table["human_controlled"];
else if(controller == "network")
details << string_table["network_controlled"];
else
details << string_table["ai_controlled"];
sides_list.push_back(details.str());
}
sides_list.push_back(string_table["start_game"]);
res = gui::show_dialog(disp,NULL,"",string_table["configure_sides"],
gui::MESSAGE,&sides_list);
if(size_t(res) < sides.size()) {
std::vector<std::string> choices;
for(int n = 0; n != 3; ++n) {
for(std::vector<config*>::iterator i = possible_sides.begin();
i != possible_sides.end(); ++i) {
std::stringstream choice;
choice << (*i)->values["name"] << " - ";
switch(n) {
case 0: choice << string_table["human_controlled"];
break;
case 1: choice << string_table["ai_controlled"];
break;
case 2: choice << string_table["network_controlled"];
break;
default: assert(false);
config loaded_level;
if(size_t(res) == options.size()-1) {
const std::vector<std::string>& games = get_saves_list();
if(games.empty()) {
gui::show_dialog(disp,NULL,
string_table["no_saves_heading"],
string_table["no_saves_message"],
gui::OK_ONLY);
break;
}
choices.push_back(choice.str());
}
}
int result = gui::show_dialog(disp,NULL,"",
string_table["choose_side"],
gui::MESSAGE,&choices);
if(result >= 0) {
std::string controller = "network";
if(result < int(choices.size())/3) {
controller = "human";
sides[res]->values["description"] = preferences::login();
} else if(result < int(choices.size()/3)*2) {
controller = "ai";
result -= choices.size()/3;
sides[res]->values["description"] =
string_table["ai_controlled"];
const int res = gui::show_dialog(disp,NULL,
string_table["load_game_heading"],
string_table["load_game_message"],
gui::OK_CANCEL, &games);
if(res == -1)
break;
load_game(units_data,games[res],state);
if(state.campaign_type != "multiplayer") {
gui::show_dialog(disp,NULL,"",
"This is not a multiplayer save",gui::OK_ONLY);
break;
}
if(state.version != game_config::version) {
const int res = gui::show_dialog(disp,NULL,"",
string_table["version_save_message"],
gui::YES_NO);
if(res == 1)
break;
}
loaded_level = state.starting_pos;
level_ptr = &loaded_level;
//make all sides untaken
for(config::child_itors i = level_ptr->child_range("side");
i.first != i.second; ++i.first) {
(**i.first)["taken"] = "";
}
recorder = replay(state.replay_data);
//add the replay data under the level data so clients can
//receive it
level_ptr->children["replay"].clear();
level_ptr->add_child("replay") = state.replay_data;
} else {
controller = "network";
result -= (choices.size()/3)*2;
level_ptr = levels[res];
}
sides[res]->values["controller"] = controller;
assert(level_ptr != NULL);
assert(result < int(possible_sides.size()));
config& level = *level_ptr;
state.label = level.values["name"];
std::map<std::string,std::string>& values =
possible_sides[result]->values;
sides[res]->values["name"] = values["name"];
sides[res]->values["type"] = values["type"];
sides[res]->values["recruit"] = values["recruit"];
sides[res]->values["recruitment_pattern"] =
values["recruitment_pattern"];
sides[res]->values["music"] = values["music"];
state.scenario = res_to_id[res];
std::vector<config*>& sides = level.children["side"];
std::vector<config*>& possible_sides = cfg.children["multiplayer_side"];
if(sides.empty() || possible_sides.empty()) {
std::cerr << "no multiplayer sides found\n";
return -1;
}
for(std::vector<config*>::iterator sd = sides.begin();
sd != sides.end(); ++sd) {
if((*sd)->values["name"].empty())
(*sd)->values["name"] = possible_sides.front()->values["name"];
if((*sd)->values["type"].empty())
(*sd)->values["type"] = possible_sides.front()->values["type"];
if((*sd)->values["recruit"].empty())
(*sd)->values["recruit"]=possible_sides.front()->values["recruit"];
if((*sd)->values["music"].empty())
(*sd)->values["music"]=possible_sides.front()->values["music"];
if((*sd)->values["recruitment_pattern"].empty())
(*sd)->values["recruitment_pattern"] =
possible_sides.front()->values["recruitment_pattern"];
if((*sd)->values["description"].empty())
(*sd)->values["description"] = preferences::login();
}
res = 0;
while(size_t(res) != sides.size()) {
std::vector<std::string> sides_list;
for(std::vector<config*>::iterator sd = sides.begin();
sd != sides.end(); ++sd) {
std::stringstream details;
details << (*sd)->values["side"] << ","
<< (*sd)->values["name"] << ",";
const std::string& controller = (*sd)->values["controller"];
if(controller == "human")
details << string_table["human_controlled"];
else if(controller == "network")
details << string_table["network_controlled"];
else
details << string_table["ai_controlled"];
sides_list.push_back(details.str());
}
sides_list.push_back(string_table["start_game"]);
res = gui::show_dialog(disp,NULL,"",string_table["configure_sides"],
gui::MESSAGE,&sides_list);
if(size_t(res) < sides.size()) {
std::vector<std::string> choices;
for(int n = 0; n != 3; ++n) {
for(std::vector<config*>::iterator i = possible_sides.begin();
i != possible_sides.end(); ++i) {
std::stringstream choice;
choice << (*i)->values["name"] << " - ";
switch(n) {
case 0: choice << string_table["human_controlled"];
break;
case 1: choice << string_table["ai_controlled"];
break;
case 2: choice << string_table["network_controlled"];
break;
default: assert(false);
}
choices.push_back(choice.str());
}
}
int result = gui::show_dialog(disp,NULL,"",
string_table["choose_side"],
gui::MESSAGE,&choices);
if(result >= 0) {
std::string controller = "network";
if(result < int(choices.size())/3) {
controller = "human";
sides[res]->values["description"] = preferences::login();
} else if(result < int(choices.size()/3)*2) {
controller = "ai";
result -= choices.size()/3;
sides[res]->values["description"] = "";
} else {
controller = "network";
result -= (choices.size()/3)*2;
}
sides[res]->values["controller"] = controller;
assert(result < int(possible_sides.size()));
std::map<std::string,std::string>& values =
possible_sides[result]->values;
sides[res]->values["name"] = values["name"];
sides[res]->values["type"] = values["type"];
sides[res]->values["recruit"] = values["recruit"];
sides[res]->values["recruitment_pattern"] =
values["recruitment_pattern"];
sides[res]->values["music"] = values["music"];
}
}
}
const network::manager net_manager;
const network::server_manager server_man(15000,server);
const bool network_state = accept_network_connections(disp,level);
if(network_state == false)
return -1;
state.starting_pos = level;
recorder.set_save_info(state);
if(!recorder.empty()) {
const int res = gui::show_dialog(disp,NULL,
"", string_table["replay_game_message"],
gui::YES_NO);
//if yes, then show the replay, otherwise
//skip showing the replay
if(res == 0) {
recorder.set_skip(0);
} else {
std::cerr << "skipping...\n";
recorder.set_skip(-1);
}
}
//any replay data isn't meant to hang around under the level,
//it was just there to tell clients about the replay data
level.children["replay"].clear();
std::vector<config*> story;
play_level(units_data,cfg,&level,disp.video(),state,story);
recorder.clear();
return -1;
} else {
rect.x=(disp.x()-width)/2;
rect.y=(disp.y()-height)/2;
rect.w=width;
rect.h=height;
SDL_Surface* dialog_bg=get_surface_portion(disp.video().getSurface(), rect);
gui::show_dialog(disp,NULL,"",
"You must enter a name.",gui::OK_ONLY);
SDL_BlitSurface(dialog_bg, NULL, disp.video().getSurface(), &rect);
SDL_FreeSurface(dialog_bg);
update_whole_screen();
}
}
}
const network::manager net_manager;
const network::server_manager server_man(15000,server);
const bool network_state = accept_network_connections(disp,level);
if(network_state == false)
return;
state.starting_pos = level;
recorder.set_save_info(state);
if(!recorder.empty()) {
const int res = gui::show_dialog(disp,NULL,
"", string_table["replay_game_message"],
gui::YES_NO);
//if yes, then show the replay, otherwise
//skip showing the replay
if(res == 0) {
recorder.set_skip(0);
} else {
std::cerr << "skipping...\n";
recorder.set_skip(-1);
if(fog_game.process(mousex,mousey,left_button))
{
}
if(shroud_game.process(mousex,mousey,left_button))
{
}
name_entry.process();
//Game turns are 20 to 99
//FIXME: Should never be a - number, but it is sometimes
int check_turns=20+(int)(79*turns_slider.process(mousex,mousey,left_button));
if(abs(check_turns) == check_turns)
new_turns=check_turns;
if(new_turns != cur_turns) {
cur_turns = new_turns;
rect.x = (disp.x()-width)/2+(int)(width*0.4)+maps_menu.width()+19;
rect.y = (disp.y()-height)/2+83;
rect.w = ((disp.x()-width)/2+width)-((disp.x()-width)/2+(int)(width*0.4)+maps_menu.width()+19)-10;
rect.h = 12;
SDL_BlitSurface(village_bg, NULL, disp.video().getSurface(), &rect);
sprintf(buf,"Turns: %d", cur_turns);
font::draw_text(&disp,disp.screen_area(),12,font::GOOD_COLOUR,
buf,rect.x,rect.y);
update_rect(rect);
}
//Villages can produce between 1 and 10 gold a turn
//FIXME: Should never be a - number, but it is sometimes
int check_villagegold=1+(int)(9*villagegold_slider.process(mousex,mousey,left_button));
if(abs(check_villagegold) == check_villagegold)
new_villagegold=check_villagegold;
if(new_villagegold != cur_villagegold) {
cur_villagegold = new_villagegold;
rect.x = (disp.x()-width)/2+(int)(width*0.4)+maps_menu.width()+19;
rect.y = (disp.y()-height)/2+130;
rect.w = ((disp.x()-width)/2+width)-((disp.x()-width)/2+(int)(width*0.4)+maps_menu.width()+19)-10;
rect.h = 12;
SDL_BlitSurface(village_bg, NULL, disp.video().getSurface(), &rect);
sprintf(buf,"Village Gold: %d", cur_villagegold);
font::draw_text(&disp,disp.screen_area(),12,font::GOOD_COLOUR,
buf,rect.x,rect.y);
update_rect(rect);
}
if(maps_menu.selection() != cur_selection) {
cur_selection = maps_menu.selection();
if(size_t(maps_menu.selection()) != options.size()-1) {
level_ptr = levels[maps_menu.selection()];
gamemap map(cfg,read_file("data/maps/" + level_ptr->values["map"]));
SDL_Surface *mini = image::getMinimap(disp.video(),
175,175, map);
rect.x = ((disp.x()-width)/2+10)+20;
rect.y = (disp.y()-height)/2+80;
rect.w = 175;
rect.h = 175;
SDL_BlitSurface(mini, NULL, disp.video().getSurface(), &rect);
SDL_FreeSurface(mini);
update_rect(rect);
}else{
//TODO: Load some other non-minimap
// image, ie a floppy disk
}
}
events::pump();
disp.video().flip();
SDL_Delay(20);
}
//any replay data isn't meant to hang around under the level,
//it was just there to tell clients about the replay data
level.children["replay"].clear();
std::vector<config*> story;
play_level(units_data,cfg,&level,disp.video(),state,story);
recorder.clear();
return -1;
}

View file

@ -30,7 +30,7 @@ struct network_game_manager {
//game will accept connections from foreign hosts. Otherwise it'll
//just use existing network connections for players, or the game
//is an entirely local game
void play_multiplayer(display& disp, game_data& units_data,
int play_multiplayer(display& disp, game_data& units_data,
config cfg, game_state& state, bool server=true);
#endif

View file

@ -231,17 +231,22 @@ void play_multiplayer_client(display& disp, game_data& units_data, config& cfg,
const config* const gamelist = data.child("gamelist");
if(gamelist != NULL) {
config game_data = data;
const lobby::RESULT res = lobby::enter(disp,game_data);
switch(res) {
case lobby::QUIT: {
return;
}
case lobby::CREATE: {
play_multiplayer(disp,units_data,cfg,state,false);
continue;
}
case lobby::JOIN: {
break;
int status = -1;
while(status == -1) {
const lobby::RESULT res = lobby::enter(disp,game_data);
switch(res) {
case lobby::QUIT: {
status = 1;
return;
}
case lobby::CREATE: {
status = play_multiplayer(disp,units_data,cfg,state,false);
break;
}
case lobby::JOIN: {
status = 1;
break;
}
}
}

View file

@ -128,20 +128,7 @@ RESULT enter(display& disp, config& game_data)
}
if(new_game.process(mousex,mousey,left_button)) {
std::string name;
const int res = gui::show_dialog(disp,NULL,"","Name your game:",
gui::OK_CANCEL,NULL,NULL,"Name:",&name);
if(res == 0 && !name.empty()) {
config response;
config create_game;
create_game["name"] = name;
response.children["create_game"].push_back(
new config(create_game));
network::send_data(response);
return CREATE;
}
update_whole_screen();
return CREATE;
break;
}

View file

@ -35,6 +35,16 @@ textbox::textbox(display& disp, int width, const std::string& text)
"ABCD",0,0).h;
}
int textbox::x() const
{
return x_;
}
int textbox::y() const
{
return y_;
}
int textbox::height() const
{
return height_;
@ -50,6 +60,12 @@ const std::string& textbox::text() const
return text_;
}
void textbox::set_text(std::string text)
{
text_ = text;
cursor_ = text_.size();
}
void textbox::clear()
{
text_ = "";
@ -118,6 +134,9 @@ void textbox::draw() const
void textbox::handle_event(const SDL_Event& event)
{
int mousex, mousey;
SDL_GetMouseState(&mousex,&mousey);
if(event.type != SDL_KEYDOWN || !focus_)
return;

View file

@ -49,9 +49,12 @@ class textbox : public events::handler
public:
textbox(display& disp, int width, const std::string& text="");
int x() const;
int y() const;
int height() const;
int width() const;
const std::string& text() const;
void set_text(std::string text);
void clear();
void draw() const;