Completely refactored internal handling of credits and enabled new dialog

This removes the ugly string markup and makes the dialog parse the config directly.

This also temporarily disables the display of credits in the help browser.
This commit is contained in:
Charles Dang 2016-09-15 03:09:14 +11:00
parent 04a6384beb
commit 476027f239
7 changed files with 120 additions and 412 deletions

View file

@ -12,426 +12,92 @@
See the COPYING file for more details.
*/
/**
* @file
* Show screen with scrolling credits.
*/
#include "about.hpp"
#include "global.hpp" // for false_, bool_
#include "config.hpp" // for config, etc
#include "cursor.hpp" // for setter, CURSOR_TYPE::WAIT
#include "events.hpp" // for pump, raise_draw_event, etc
#include "font.hpp" // for NORMAL_COLOR, SIZE_XLARGE
#include "game_config.hpp" // for game_title_background
#include "gettext.hpp" // for _
#include "image.hpp" // for get_image
#include "key.hpp" // for CKey
#include "marked-up_text.hpp" // for draw_text, LARGE_TEXT, etc
#include "sdl/rect.hpp" // for create_rect
#include "sdl/utils.hpp" // for surface, sdl_blit, etc
#include "serialization/string_utils.hpp" // for split, etc
#include "show_dialog.hpp" // for dialog_frame, etc
#include "tstring.hpp" // for operator==
#include "video.hpp" // for update_rect, CVideo
#include "widgets/button.hpp" // for button
#include "gui/dialogs/end_credits.hpp"
#include <algorithm> // for max
#include <map> // for map, map<>::mapped_type
#include <ostream> // for operator<<, basic_ostream, etc
#include "sdl/alpha.hpp"
/**
* @namespace about
* Display credits %about all contributors.
*
* This module is used from the startup screen. \n
* When show_about() is called, a list of contributors
* to the game will be presented to the user.
*/
namespace about
{
static config about_list = config();
static std::map<std::string , std::string> images;
static std::string images_default;
/**
* Given a vector of strings, and a config representing an [about] section,
* add all the credits lines from the about section to the list of strings.
*/
static void add_lines(std::vector<std::string> &res, config const &c, bool split_multiline_headers) {
std::string title = c["title"];
if (!title.empty()) {
if(split_multiline_headers) {
// If the title is multi-line, we need to split it accordingly or we
// get slight scrolling glitches in the credits screen.
const std::vector<std::string>& lines = utils::split(c["title"], '\n');
bool first = true;
for(const std::string& line : lines) {
if(first) {
res.push_back("+" + line);
first = false;
} else {
// Don't convert other lines into headers or they get extra
// spacing on the credits screen.
res.push_back(line);
}
}
} else {
res.push_back("+" + title);
}
}
static config about_list = config();
static std::map<std::string , std::string> images;
static std::string images_default;
std::vector<std::string> lines = utils::split(c["text"], '\n');
for(std::string &line : lines)
{
if (line.size() > 1 && line[0] == '+')
line = "+ " + line.substr(1);
else
line = "- " + line;
if (!line.empty())
{
if (line[0] == '_')
line = translation::gettext(line.substr(1).c_str());
res.push_back(line);
}
}
for (const config &entry : c.child_range("entry")) {
res.push_back("- "+ entry["name"].str());
}
const config& get_about_config()
{
return about_list;
}
std::vector<std::string> get_text(const std::string &campaign, bool split_multiline_headers)
std::vector<std::string> get_background_images(const std::string& campaign)
{
std::vector< std::string > res;
config::child_itors about_entries = about_list.child_range("about");
if (!campaign.empty()) {
for (const config &about : about_entries) {
// just finished a particular campaign
if (campaign == about["id"]) {
add_lines(res, about, split_multiline_headers);
}
}
}
for (const config &about : about_entries) {
add_lines(res, about, split_multiline_headers);
}
return res;
}
void set_about(const config &cfg)
{
about_list.clear();
images.clear();
images_default = "";
for (const config &about : cfg.child_range("about"))
{
about_list.add_child("about", about);
const std::string &im = about["images"];
if (!images.empty())
{
if (images_default.empty())
images_default = im;
else
images_default += ',' + im;
}
}
for (const config &campaign : cfg.child_range("campaign"))
{
config::const_child_itors abouts = campaign.child_range("about");
if (abouts.empty()) continue;
config temp;
std::ostringstream text;
const std::string &id = campaign["id"];
temp["title"] = campaign["name"];
temp["id"] = id;
std::string campaign_images;
for (const config &about : abouts)
{
const std::string &subtitle = about["title"];
if (!subtitle.empty())
{
text << '+';
if (subtitle[0] == '_')
text << translation::gettext(subtitle.substr(1, subtitle.size() - 1).c_str());
else
text << subtitle;
text << '\n';
}
for (const std::string &line : utils::split(about["text"], '\n'))
{
text << " " << line << '\n';
}
for (const config &entry : about.child_range("entry"))
{
text << " " << entry["name"] << '\n';
}
const std::string &im = about["images"];
if (!im.empty())
{
if (campaign_images.empty())
campaign_images = im;
else
campaign_images += ',' + im;
}
}
images[id] = campaign_images;
temp["text"] = text.str();
about_list.add_child("about",temp);
}
}
/**
* Show credits with list of contributors.
*
* Names of people are shown scrolling up like in movie-credits.\n
* Uses map from wesnoth or campaign as background.
*/
void show_about(CVideo &video, const std::string &campaign)
{
std::unique_ptr<cursor::setter> cur(new cursor::setter(cursor::WAIT));
surface& screen = video.getSurface();
if (screen == nullptr) return;
// If the title is multi-line, we need to split it accordingly or we
// get slight scrolling glitches in the credits screen.
std::vector<std::string> text = about::get_text(campaign, true);
SDL_Rect screen_rect = sdl::create_rect(0, 0, screen->w, screen->h);
const surface_restorer restorer(&video, screen_rect);
cur.reset();
std::vector<std::string> image_list;
if(campaign.size() && !images[campaign].empty()){
if(!campaign.empty() && !images[campaign].empty()){
image_list = utils::parenthetical_split(images[campaign], ',');
} else{
image_list = utils::parenthetical_split(images_default, ',');
}
surface map_image, map_image_scaled;
return image_list;
}
// TODO: enable
//gui2::tend_credits::display(text, image_list, video);
void set_about(const config &cfg)
{
about_list.clear();
if(!image_list.empty()) {
map_image = image::get_image(image_list[0]);
} else {
image_list.push_back("");
}
images.clear();
images_default.clear();
if(!map_image){
image_list[0]=game_config::images::game_title_background;
map_image=image::get_image(image_list[0]);
}
for(const config& about : cfg.child_range("about")) {
about_list.add_child("about", about);
gui::button close(video,_("Close"));
close.set_location((screen->w/2)-(close.width()/2), screen->h - 30);
const int def_size = font::SIZE_XLARGE;
const SDL_Color def_color = font::NORMAL_COLOR;
//substitute in the correct control characters for '+' and '-'
std::string before_header(2, ' ');
before_header[0] = font::LARGE_TEXT;
for(unsigned i = 0; i < text.size(); ++i) {
std::string &s = text[i];
if (s.empty()) continue;
char &first = s[0];
if (first == '-')
first = font::SMALL_TEXT;
else if (first == '+') {
first = font::LARGE_TEXT;
text.insert(text.begin() + i, before_header);
++i;
const std::string& im = about["images"];
if(!im.empty()) {
if(images_default.empty()) {
images_default = im;
} else {
images_default += ',' + im;
}
}
}
text.insert(text.begin(), 10, before_header);
int startline = 0;
//TODO: use values proportional to screen ?
// distance from top of map image to top of scrolling text
const int top_margin = 60;
// distance from bottom of scrolling text to bottom of map image
const int bottom_margin = 40;
// distance from left of scrolling text to the frame border
const int text_left_padding = screen->w/32;
int offset = 0;
bool is_new_line = true;
int first_line_height = 0;
SDL_Rect frame_area;
// we use a dialog to contains the text. Strange idea but at least the style
// will be consistent with the titlescreen
gui::dialog_frame f(video, "", gui::dialog_frame::titlescreen_style, false);
// the text area's dimensions
SDL_Rect text_rect = { 0, 0, 0, 0 };
// we'll retain a copy to prevent SDL_blit to change its w and h
SDL_Rect text_rect_blit;
surface text_surf;
CKey key;
bool last_escape;
int image_count = 0;
int scroll_speed = 4; // scroll_speed*50 = speed of scroll in pixel per second
// Initially redraw all
bool redraw_mapimage = true;
bool update_dimensions = true;
int max_text_width = 0;
do {
last_escape = key[SDLK_ESCAPE] != 0;
// check to see if background image has changed
if(text.size() && (image_count <
((startline * static_cast<int>(image_list.size())) /
static_cast<int>(text.size())))){
image_count++;
surface temp=image::get_image(image_list[image_count]);
map_image=temp?temp:map_image;
redraw_mapimage = true;
for(const config& campaign : cfg.child_range("campaign")) {
if(!campaign.has_child("about")) {
continue;
}
if (update_dimensions) {
// rescale the background
map_image_scaled = scale_surface(map_image, screen->w, screen->h);
screen_rect = sdl::create_rect(0, 0, screen->w, screen->h);
redraw_mapimage = true;
const std::string& id = campaign["id"];
// update the frame
frame_area = sdl::create_rect(
screen->w * 3 / 32
, top_margin
, screen->w * 13 / 16
, screen->h - top_margin - bottom_margin);
config temp;
temp["title"] = campaign["name"];
temp["id"] = id;
text_rect = f.layout(frame_area).interior;
std::string campaign_images;
for(const config& about : campaign.child_range("about")) {
temp.add_child("about", about);
// update the text area
text_rect.x += text_left_padding;
text_rect.w -= text_left_padding;
text_rect_blit = text_rect;
text_surf = create_compatible_surface(screen, text_rect.w, text_rect.h);
SDL_SetAlpha(text_surf, SDL_RLEACCEL, SDL_ALPHA_OPAQUE);
// relocate the close button
close.set_location((screen->w/2)-(close.width()/2), screen->h - 30);
update_dimensions = false;
}
if (redraw_mapimage) {
// draw map to screen, thus erasing all text
sdl_blit(map_image_scaled, nullptr, screen, nullptr);
update_rect(screen_rect);
// redraw the dialog
f.draw_background();
f.draw_border();
// cache the dialog background (alpha blending + blurred map)
sdl_blit(screen, &text_rect, text_surf, nullptr);
redraw_mapimage = false;
} else {
// redraw the saved part of the dialog where text scrolled
// thus erasing all text
SDL_Rect modified = sdl::create_rect(0, 0, max_text_width, text_rect.h);
sdl_blit(text_surf, &modified, screen, &text_rect_blit);
update_rect(text_rect);
}
int y = text_rect.y - offset;
int line = startline;
max_text_width = 0;
{
// clip to keep text into the frame (thus the new code block)
clip_rect_setter set_clip_rect(screen, &text_rect);
const int line_spacing = 5;
do {
// draw the text (with ellipsis if needed)
// update the max_text_width for future cleaning
int w = font::draw_text(&video, text_rect, def_size, def_color,
text[line], text_rect.x, y).w;
max_text_width = std::max<int>(max_text_width, w);
// since the real drawing on screen is clipped,
// we do a dummy one to get the height of the not clipped line.
// (each time because special format characters may change it)
const int line_height = font::draw_text(nullptr, text_rect, def_size, def_color,
text[line], 0,0).h;
if(is_new_line) {
is_new_line = false;
first_line_height = line_height + line_spacing;
const std::string& im = about["images"];
if(!im.empty()) {
if(campaign_images.empty()) {
campaign_images = im;
} else {
campaign_images += ',' + im;
}
line++;
if(size_t(line) > text.size()-1)
line = 0;
y += line_height + line_spacing;
} while(y < text_rect.y + text_rect.h);
}
// performs the actual scrolling
offset += scroll_speed;
if (offset>=first_line_height) {
offset -= first_line_height;
is_new_line = true;
startline++;
if(size_t(startline) == text.size()){
startline = 0;
image_count = -1;
}
}
// handle events
if (key[SDLK_UP] && scroll_speed < 20) {
++scroll_speed;
}
if (key[SDLK_DOWN] && scroll_speed > 0) {
--scroll_speed;
}
if (screen->w != screen_rect.w || screen->h != screen_rect.h) {
update_dimensions = true;
}
images[id] = campaign_images;
events::pump();
events::raise_process_event();
events::raise_draw_event();
// flip screen and wait, so the text does not scroll too fast
video.flip();
CVideo::delay(20);
} while(!close.pressed() && (last_escape || !key[SDLK_ESCAPE]));
about_list.add_child("credits_group", temp);
}
}
} // end namespace about

View file

@ -17,7 +17,6 @@
#include "global.hpp"
class CVideo;
class config;
#include <vector>
@ -26,9 +25,17 @@ class config;
namespace about
{
void show_about(CVideo& video, const std::string &campaign = std::string());
/**
* General getter methods for the credits config and image lists by campaign id
*/
const config& get_about_config();
std::vector<std::string> get_background_images(const std::string& campaign);
/**
* Regenerates the credits config
*/
void set_about(const config& cfg);
std::vector<std::string> get_text(const std::string &campaign = std::string(), bool split_multiline_headers = false);
}

View file

@ -16,7 +16,6 @@
#include "global.hpp" // for false_, bool_
#include "game_errors.hpp"
#include "about.hpp" //for show_about
#include "commandline_options.hpp" // for commandline_options
#include "config.hpp" // for config, etc
#include "config_assign.hpp"
@ -30,6 +29,7 @@
#include "game_end_exceptions.hpp" // for LEVEL_RESULT, etc
#include "generators/map_generator.hpp" // for mapgen_exception
#include "gettext.hpp" // for _
#include "gui/dialogs/end_credits.hpp"
#include "gui/dialogs/language_selection.hpp" // for tlanguage_selection
#include "gui/dialogs/loadscreen.hpp"
#include "gui/dialogs/message.hpp" //for show error message
@ -955,7 +955,7 @@ void game_launcher::launch_game(RELOAD_GAME_DATA reload)
preferences::add_completed_campaign(state_.classification().campaign, state_.classification().difficulty);
the_end(video(), state_.classification().end_text, state_.classification().end_text_duration);
if(state_.classification().end_credits) {
about::show_about(video(),state_.classification().campaign);
gui2::tend_credits::display(video(), state_.classification().campaign);
}
}
} catch (savegame::load_game_exception &e) {

View file

@ -15,6 +15,8 @@
#include "gui/dialogs/end_credits.hpp"
#include "about.hpp"
#include "config.hpp"
#include "game_config.hpp"
#include "gui/auxiliary/find_widget.hpp"
#include "gui/core/timer.hpp"
@ -24,7 +26,6 @@
#include "gui/widgets/scroll_label.hpp"
#include "gui/widgets/settings.hpp"
#include "gui/widgets/window.hpp"
#include "formatter.hpp"
#include "marked-up_text.hpp"
#include "utils/functional.hpp"
@ -34,16 +35,13 @@ namespace gui2
REGISTER_DIALOG(end_credits)
tend_credits::tend_credits(const std::vector<std::string>& text, const std::vector<std::string>& backgrounds)
: text_(text)
, backgrounds_(backgrounds)
tend_credits::tend_credits(const std::string& campaign)
: focus_on_(campaign)
, backgrounds_()
, timer_id_()
, text_widget_(nullptr)
, scroll_speed_(100)
{
if(backgrounds_.empty()) {
backgrounds_.push_back(game_config::images::game_title_background);
}
}
tend_credits::~tend_credits()
@ -54,30 +52,60 @@ tend_credits::~tend_credits()
}
}
static void parse_about_tags(const config& cfg, std::stringstream& str)
{
for(const auto& about : cfg.child_range("about")) {
str << "<span size='x-large'>" << about["title"] << "</span>" << "\n";
for(const auto& entry : about.child_range("entry")) {
str << entry["name"] << "\n";
}
}
}
void tend_credits::pre_show(twindow& window)
{
// Delay a little before beginning the scrolling
add_timer(1000, [this](size_t) {
timer_id_ = add_timer(50, std::bind(&tend_credits::timer_callback, this), true);
add_timer(3000, [this](size_t) {
timer_id_ = add_timer(10, std::bind(&tend_credits::timer_callback, this), true);
last_scroll_ = SDL_GetTicks();
});
connect_signal_pre_key_press(window, std::bind(&tend_credits::key_press_callback, this, _3, _4, _5));
// TODO: apparently, multiple images are supported... need to implement along with scrolling
window.canvas()[0].set_variable("background_image", variant(backgrounds_[0]));
std::stringstream str;
std::stringstream focus_str;
// BIG FAT TODO: get rid of this hacky string crap once we drop the GUI1 version
for(const auto& line : text_) {
if(line[0] == '-') {
str << font::escape_text(line.substr(1)) << "\n";
} else if(line[0] == '+') {
str << "<span size='x-large'>" << font::escape_text(line.substr(1)) << "</span>" << "\n";
}
const config& credits_config = about::get_about_config();
// First, parse all the toplevel [about] tags
parse_about_tags(credits_config, str);
// Next, parse all the grouped [about] tags (usually by campaign)
for(const auto& group : credits_config.child_range("credits_group")) {
std::stringstream& group_stream = (group["id"] == focus_on_) ? focus_str : str;
group_stream << "<span size='xx-large'>" << group["title"] << "</span>" << "\n";
parse_about_tags(group, group_stream);
}
// TODO: this seems an inefficient way to place the focused group first
if(!focus_str.str().empty()) {
focus_str << str.rdbuf();
str.swap(focus_str);
}
// Get the appropriate background images
backgrounds_ = about::get_background_images(focus_on_);
if(backgrounds_.empty()) {
backgrounds_.push_back(game_config::images::game_title_background);
}
// TODO: implement showing all available images as the credits scroll
window.canvas()[0].set_variable("background_image", variant(backgrounds_[0]));
text_widget_ = find_widget<tscroll_label>(&window, "text", false, true);
text_widget_->set_use_markup(true);
@ -97,12 +125,17 @@ void tend_credits::timer_callback()
{
uint32_t now = SDL_GetTicks();
uint32_t missed_time = now - last_scroll_;
unsigned int cur_pos = text_widget_->get_vertical_scrollbar_item_position();
// Calculate how far the text should have scrolled by now
// The division by 1000 is to convert milliseconds to seconds.
unsigned int needed_dist = missed_time * scroll_speed_ / 1000;
text_widget_->set_vertical_scrollbar_item_position(cur_pos + needed_dist);
last_scroll_ = now;
if(text_widget_->vertical_scrollbar_at_end()) {
remove_timer(timer_id_);
}

View file

@ -20,6 +20,7 @@
#include "sdl/utils.hpp"
class config;
class display;
namespace gui2
@ -30,13 +31,13 @@ class tscroll_label;
class tend_credits : public tdialog
{
public:
tend_credits(const std::vector<std::string>& text, const std::vector<std::string>& backgrounds);
explicit tend_credits(const std::string& campaign);
~tend_credits();
static void display(const std::vector<std::string>& text, const std::vector<std::string>& backgrounds, CVideo& video)
static void display(CVideo& video, const std::string& campaign = "")
{
tend_credits(text, backgrounds).show(video);
tend_credits(campaign).show(video);
}
private:
@ -49,7 +50,7 @@ private:
void timer_callback();
void key_press_callback(bool&, bool&, const SDLKey key);
const std::vector<std::string>& text_;
const std::string& focus_on_;
std::vector<std::string> backgrounds_;
@ -57,7 +58,7 @@ private:
tscroll_label* text_widget_;
/// The speed of auto-scrolling, specified as px/s
// The speed of auto-scrolling, specified as px/s
int scroll_speed_;
uint32_t last_scroll_;

View file

@ -1012,13 +1012,14 @@ UNIT_DESCRIPTION_TYPE description_type(const unit_type &type)
std::string generate_about_text()
{
std::vector<std::string> about_lines = about::get_text();
/*std::vector<std::string> about_lines = about::get_text();
std::vector<std::string> res_lines;
std::transform(about_lines.begin(), about_lines.end(), std::back_inserter(res_lines),
about_text_formatter());
res_lines.erase(std::remove(res_lines.begin(), res_lines.end(), ""), res_lines.end());
std::string text = utils::join(res_lines, "\n");
return text;
return text;*/
return "";
}
std::string generate_contents_links(const std::string& section_name, config const *help_cfg)

View file

@ -14,7 +14,6 @@
#include "global.hpp"
#include "about.hpp"
#include "addon/manager.hpp"
#include "build_info.hpp"
#include "commandline_options.hpp" // for commandline_options, etc
@ -31,6 +30,7 @@
#include "game_launcher.hpp" // for game_launcher, etc
#include "gettext.hpp"
#include "gui/core/event/handler.hpp" // for tmanager
#include "gui/dialogs/end_credits.hpp"
#include "gui/dialogs/loadscreen.hpp"
#include "gui/dialogs/title_screen.hpp" // for ttitle_screen, etc
#include "gui/dialogs/message.hpp" // for show_error_message
@ -804,7 +804,7 @@ static int do_gameloop(const std::vector<std::string>& args)
game->start_editor();
break;
case gui2::ttitle_screen::SHOW_ABOUT:
about::show_about(game->video());
gui2::tend_credits::display(game->video());
break;
case gui2::ttitle_screen::LAUNCH_GAME:
game->launch_game(should_reload);