Storyscreens now use Pango for rendering text.

This makes Pango markup available in storyscreens.
This commit is contained in:
Ignacio R. Morelle 2009-07-29 09:36:38 +00:00
parent 978cdc7b94
commit b05e2fe198
5 changed files with 485 additions and 366 deletions

View file

@ -20,6 +20,7 @@ Version 1.7.2+svn:
* WML Engine:
* return 0 for length of arrays inside nonexistent containers (bug #13734)
* [end_turn] now waits for the event to finish before ending the turn
* storyscreens use Pango markup now
* Miscellaneous and bugfixes:
* 'Recruit' and 'recall' don't appear in context menus unless the
leader is on a keep. (bug #13856 and #13855)

View file

@ -127,8 +127,8 @@ part::part(const vconfig &part_cfg)
, show_title_()
, text_()
, text_title_()
, text_block_loc_(text_block_loc_)
, title_alignment_(title_alignment_)
, text_block_loc_(part::BOTTOM)
, title_alignment_(part::LEFT)
, music_()
, floating_images_()
{

View file

@ -46,29 +46,65 @@ public:
surface image; /**< Surface, scaled if required. */
};
/**
* Default constructor.
*/
floating_image();
floating_image(const config& cfg);
floating_image(const floating_image& fi);
void assign(const floating_image& fi);
/**
* WML-based constructor.
* @param cfg Object corresponding to a [image] block's contents from
* a [part] node.
*/
floating_image(const config& cfg);
/**
* Copy constructor.
*/
floating_image(const floating_image& fi);
floating_image& operator=(const floating_image& fi) {
assign(fi);
return *this;
}
/** Returns the referential X coordinate of the image. */
int ref_x() const { return x_; }
/** Returns the referential Y coordinate of the image. */
int ref_y() const { return y_; }
/**
* Returns the referential X coordinate of the image.
* The actual (corrected) value is determined at render time - see get_render_input().
*/
int ref_x() const {
return x_;
}
/**
* Returns the referential Y coordinate of the image.
* The actual (corrected) value is determined at render time - see get_render_input().
*/
int ref_y() const {
return y_;
}
/**
* Whether the image should be automatically scaled as much as
* the storyscreen background is.
*/
bool autoscale() const { return autoscaled_; }
/**
* Whether the image coordinates specify the location of its
* center (true) or top-left corner (false).
*/
bool centered() const { return centered_; }
/** Delay before displaying, in milliseconds. */
/**
* Delay before displaying, in milliseconds.
*/
int display_delay() const { return delay_; }
/** Render. */
/**
* Gets a render_input object for use by the rendering code after applying
* any geometric transformations required.
*/
render_input get_render_input(double scale, SDL_Rect& dst_rect) const;
private:
@ -77,6 +113,9 @@ private:
int delay_;
bool autoscaled_;
bool centered_;
/** Copy constructor and operator=() implementation details. */
void assign(const floating_image& fi);
};
/**
@ -114,24 +153,69 @@ public:
QUIT /**< Quit game and go back to main menu. */
};
/**
* Constructs a storyscreen part from a managed WML node.
* @param part_cfg Node object which should correspond to a [part] block's contents.
*/
part(const vconfig &part_cfg);
bool scale_background() const { return scale_background_; }
const std::string& background() const { return background_file_; }
/** Whether the background image should be scaled to fill the screen or not. */
bool scale_background() const {
return scale_background_;
}
bool show_title() const { return show_title_; }
const std::string& text() const { return text_; }
const std::string& title() const { return text_title_; }
const std::string& music() const { return music_; }
/** Path to the background image. */
const std::string& background() const {
return background_file_;
}
void set_text(const std::string& text) { text_ = text; }
void set_title(const std::string& title) { text_title_ = title; }
/** Whether the story screen title should be displayed or not. */
bool show_title() const {
return show_title_;
}
/** Retrieves the story text itself. */
const std::string& text() const {
return text_;
}
/** Changes the story text. */
void set_text(const std::string& text) {
text_ = text;
}
/** Retrieves the story screen title. */
const std::string& title() const {
return text_title_;
}
/** Changes the story screen title. */
void set_title(const std::string& title) {
text_title_ = title;
}
/** Retrieves the background music. */
const std::string& music() const {
return music_;
}
/** Retrieves the area of the screen on which the story text is displayed. */
TEXT_BLOCK_LOCATION text_block_location() const {
return text_block_loc_;
}
/** Retrieves the alignment of the title text against the screen. */
TITLE_ALIGNMENT title_block_alignment() const {
return title_alignment_;
}
/** Retrieve any associated floating images for this story screen. */
const std::vector<floating_image> get_floating_images() const {
return floating_images_;
}
private:
// Default constructor. Not used. Should never be used.
part();
/** Takes care of initializing and branching properties. */

View file

@ -40,11 +40,34 @@
#include "stub.hpp"
static lg::log_domain log_engine("engine");
#define ERR_NG LOG_STREAM(err, log_engine)
#define ERR_NG LOG_STREAM(err, log_engine)
#define WARN_NG LOG_STREAM(warn, log_engine)
#define LOG_NG LOG_STREAM(info, log_engine)
namespace {
int const storybox_padding = 10; // px
double const storyshadow_opacity = 0.5;
int const storyshadow_r = 0;
int const storyshadow_g = 0;
int const storyshadow_b = 0;
int const titlebox_padding = 20; // px
int const titleshadow_padding = 5; // px
double const titleshadow_opacity = 0.5;
int const titleshadow_r = 0;
int const titleshadow_g = 0;
int const titleshadow_b = 0;
int const titlebox_font_size = 20; // pt?
Uint32 const titlebox_font_color = 0xFFFFFFFF;
Uint32 const storybox_font_color = 0xDDDDDDFF;
#ifndef LOW_MEM
namespace {
std::string const textbox_border_path = "dialogs/translucent54-border-top.png";
// Hard-coded path to a suitable (tileable) pic for the storytxt box border.
std::string const storybox_top_border_path = "dialogs/translucent54-border-top.png";
std::string const storybox_bottom_border_path = "dialogs/translucent54-border-bottom.png";
void blur_area(CVideo& video, int y, int h)
{
@ -53,30 +76,8 @@ namespace {
blur = blur_surface(blur, 1, false);
video.blit_surface(0, y, blur);
}
#if 0
/* do not remove yet */
void fade_in_helper(CVideo& video, surface surf, int x, int y)
{
const SDL_Rect target_area = { x, y, surf->w, surf->h };
surface s = cut_surface(video.getSurface(), backup_area);
ll
for(size_t n = 0; n < 255; n += 5) {
if(n)
video.blit_surface(x, y, backup);
// TODO
update_rect(target_area);
events::pump();
events::raise_process_event();
events::raise_draw_event();
disp.flip();
disp.delay(10);
}
}
#endif
}
#endif /* ! LOW_MEM */
namespace storyscreen {
@ -95,12 +96,19 @@ part_ui::part_ui(part& p, display& disp, gui::button& next_button, gui::button&
, has_background_(false)
, text_x_(200)
, text_y_(400)
, buttons_x_()
, buttons_y_()
, buttons_x_(0)
, buttons_y_(0)
{
this->prepare_background();
this->prepare_geometry();
this->prepare_floating_images();
}
void part_ui::prepare_background()
{
// Build background surface
if(p_.background_file_.empty() != true) {
background_.assign( image::get_image(p.background_file_) );
background_.assign( image::get_image(p_.background_file_) );
}
has_background_ = !background_.null();
if(background_.null() || background_->w * background_-> h == 0) {
@ -114,13 +122,21 @@ part_ui::part_ui(part& p, display& disp, gui::button& next_button, gui::button&
background_ =
scale_surface(background_, static_cast<int>(background_->w*scale_factor_), static_cast<int>(background_->h*scale_factor_));
ASSERT_LOG(background_.null() == false, "Ouch: storyscreen part background got NULL");
ASSERT_LOG(background_.null() == false, "Oops: storyscreen part background got NULL");
}
void part_ui::prepare_geometry()
{
base_rect_.x = (video_.getx() - background_->w) / 2;
base_rect_.y = (video_.gety() - background_->h) / 2;
base_rect_.w = background_->w;
base_rect_.h = background_->h;
int next_button_loc_x, next_button_loc_y;
int skip_button_loc_x, skip_button_loc_y;
next_button_loc_x = next_button_loc_y = 0;
skip_button_loc_x = skip_button_loc_y = 0;
#ifdef USE_TINY_GUI
// Use the whole screen for text on tinygui
text_x_ = 10;
@ -130,324 +146,41 @@ part_ui::part_ui(part& p, display& disp, gui::button& next_button, gui::button&
next_button_.set_location(buttons_x_, buttons_y_ - 20);
skip_button_.set_location(buttons_x_, buttons_y_);
#else
text_x_ = 200;
text_y_ = video_.gety() - 200;
buttons_x_ = video_.getx() - 200 - 40;
buttons_y_ = video_.gety() - 40;
#else // elif !defined(USE_TINY_GUI)
// The horizontal locations are always the same
text_x_ = 200;
buttons_x_ = video_.getx() - 200 - 40;
switch(p_.text_block_location())
{
case part::TOP:
text_y_ = 0;
buttons_y_ = 40;
break;
case part::MIDDLE:
text_y_ = video_.gety() / 3;
buttons_y_ = video_.gety() / 2 + 15;
break;
default: // part::BOTTOM
text_y_ = video_.gety() - 200;
buttons_y_ = video_.gety() - 40;
break;
}
next_button_.set_location(buttons_x_, buttons_y_ - 30);
skip_button_.set_location(buttons_x_, buttons_y_);
#endif
}
void part_ui::prepare_floating_images()
{
// Build floating image surfaces
foreach(const floating_image& fi, p_.floating_images_) {
imgs_.push_back( fi.get_render_input(scale_factor_, base_rect_) );
}
}
void part_ui::render_text_box()
{
const bool rtl = current_language_rtl();
const int max_width = next_button_.location().x - 10 - text_x_;
const std::string storytxt =
font::word_wrap_text(p_.text_, font::SIZE_PLUS, max_width);
utils::utf8_iterator itor(storytxt);
bool skip = false, last_key = true;
int update_y = 0, update_h = 0;
// Draw the text box
if(storytxt.empty() != true)
{
// this should kill the tiniest flickering caused
// by the buttons being hidden and unhidden in this scope.
update_locker locker(video_);
const SDL_Rect total_size = font::draw_text(
NULL, screen_area(), font::SIZE_PLUS,
font::NORMAL_COLOUR, storytxt, 0, 0
);
next_button_.hide();
skip_button_.hide();
if(text_y_ + 20 + total_size.h > screen_area().h) {
text_y_ = screen_area().h > total_size.h + 1 ? screen_area().h - total_size.h - 21 : 0;
}
update_y = text_y_;
update_h = screen_area().h - text_y_;
#ifndef LOW_MEM
blur_area(video_, update_y, update_h);
#endif
draw_solid_tinted_rectangle(
0, text_y_, screen_area().w, screen_area().h-text_y_,
0, 0, 0, 0.5, video_.getSurface()
);
// Draw a nice border
if(has_background_) {
// FIXME: perhaps hard-coding the image path isn't a really
// good idea - it must not be forgotten if someone decides to switch
// the image directories around.
surface top_border = image::get_image("dialogs/translucent54-border-top.png");
top_border = scale_surface_blended(top_border, screen_area().w, top_border->h);
update_y = text_y_ - top_border->h;
update_h += top_border->h;
#ifndef LOW_MEM
blur_area(video_, update_y, top_border->h);
#endif
video_.blit_surface(0, text_y_ - top_border->h, top_border);
}
// Make buttons aware of the changes in the background
next_button_.set_location(next_button_.location());
next_button_.hide(false);
skip_button_.set_location(skip_button_.location());
skip_button_.hide(false);
}
if(imgs_.empty()) {
update_whole_screen();
} else if(update_h > 0) {
update_rect(0,update_y,screen_area().w,update_h);
}
if(rtl) {
text_x_ += max_width;
}
#ifdef USE_TINY_GUI
int xpos = text_x_, ypos = text_y_ + 10;
#else
int xpos = text_x_, ypos = text_y_ + 20;
#endif
// The maximum position that text can reach before wrapping
size_t height = 0;
while(true) {
if(itor != utils::utf8_iterator::end(storytxt)) {
if(*itor == '\n') {
xpos = text_x_;
ypos += height;
++itor;
}
// Output the character
/** @todo FIXME: this is broken: it does not take kerning into account. */
std::string tmp;
tmp.append(itor.substr().first, itor.substr().second);
if(rtl) {
xpos -= font::line_width(tmp, font::SIZE_PLUS);
}
const SDL_Rect rect = font::draw_text(
&video_, screen_area(), font::SIZE_PLUS,
font::NORMAL_COLOUR, tmp, xpos, ypos,
false
);
if(rect.h > height)
height = rect.h;
if(!rtl)
xpos += rect.w;
update_rect(rect);
++itor;
if(itor == utils::utf8_iterator::end(storytxt)) {
skip = true;
}
}
const bool keydown = keys_[SDLK_SPACE] || keys_[SDLK_RETURN] || keys_[SDLK_KP_ENTER];
if((keydown && !last_key) || next_button_.pressed()) {
if(skip == true || itor == utils::utf8_iterator::end(storytxt)) {
ret_ = NEXT;
break;
} else {
skip = true;
}
}
last_key = keydown;
if(keys_[SDLK_ESCAPE] || skip_button_.pressed()) {
ret_ = SKIP;
return;
}
events::pump();
events::raise_process_event();
events::raise_draw_event();
disp_.flip();
if(!skip || itor == utils::utf8_iterator::end(storytxt)) {
disp_.delay(20);
}
}
draw_solid_tinted_rectangle(
0, 0, video_.getx(), video_.gety(), 0, 0, 0,
1.0, video_.getSurface()
);
}
void part_ui::render_text_box_with_pango()
{
const int max_width = next_button_.location().x - 10 - text_x_;
const int padding_x = 10;
const int max_height = screen_area().h - padding_x;
const std::string& storytxt = p_.text_;
bool skip = false, last_key = true;
int update_y = 0, update_h = 0;
surface textbox_surf = NULL;
// draw the text box
if(!storytxt.empty())
{
update_locker lock(video_);
font::ttext t;
next_button_.hide();
skip_button_.hide();
/*back_button_.hide();*/
t.set_text(storytxt, true)
.set_font_style(font::ttext::STYLE_NORMAL)
/*.set_font_size(14)*/
.set_maximum_width(max_width)
.set_maximum_height(max_height);
textbox_surf = t.render();
if(text_y_ + 20 + textbox_surf->h > screen_area().h) {
text_y_ = screen_area().h > textbox_surf->h + 1 ? screen_area().h - textbox_surf->h - 21 : 0;
}
update_y = text_y_;
update_h = screen_area().h - text_y_;
//const SDL_Rect textbox_rect = { text_x_, text_y_, textbox_surf->w, textbox_surf->h };
#ifndef LOW_MEM
blur_area(video_, update_y, update_h);
#endif
draw_solid_tinted_rectangle(
0, text_y_, screen_area().w, screen_area().h-text_y_,
0, 0, 0, 0.5, video_.getSurface()
);
#ifndef LOW_MEM
// Draw a nice border
if(has_background_) {
// FIXME: perhaps hard-coding the image path isn't a really
// good idea - it must not be forgotten if someone decides to switch
// the image directories around.
surface top_border = image::get_image(textbox_border_path);
top_border = scale_surface_blended(top_border, screen_area().w, top_border->h);
update_y = text_y_ - top_border->h;
update_h += top_border->h;
blur_area(video_, update_y, top_border->h);
video_.blit_surface(0, text_y_ - top_border->h, top_border);
}
#endif
// Make GUI 1 buttons aware of the changes in the background
next_button_.set_location(next_button_.location());
next_button_.hide(false);
skip_button_.set_location(skip_button_.location());
skip_button_.hide(false);
}
if(imgs_.empty()) {
update_whole_screen();
} else if(update_h > 0) {
update_rect(0,update_y,screen_area().w,update_h);
}
const bool rtl = current_language_rtl();
if(rtl) {
text_x_ += max_width;
}
#ifdef USE_TINY_GUI
//int xpos = text_x_, ypos = text_y_ + 10;
#else
//int xpos = text_x_, ypos = text_y_ + 20;
#endif
// The maximum position that text can reach before wrapping
//size_t height = 0;
// FIXME: line height
// const int line_height = 10;
if(!textbox_surf.null()) {
video_.blit_surface(text_x_, text_y_+padding_x, textbox_surf);
}
while(true)
{
const bool keydown = keys_[SDLK_SPACE] || keys_[SDLK_RETURN] || keys_[SDLK_KP_ENTER];
if((keydown && !last_key) || next_button_.pressed()) {
if(skip == true /*|| itor == utils::utf8_iterator::end(storytxt)*/) {
ret_ = NEXT;
break;
} else {
skip = true;
}
}
last_key = keydown;
if(keys_[SDLK_ESCAPE] || skip_button_.pressed()) {
ret_ = SKIP;
return;
}
events::pump();
events::raise_process_event();
events::raise_draw_event();
disp_.flip();
if(!skip /*|| itor == utils::utf8_iterator::end(storytxt)*/) {
disp_.delay(20);
}
}
draw_solid_tinted_rectangle(
0, 0, video_.getx(), video_.gety(), 0, 0, 0,
1.0, video_.getSurface()
);
}
void part_ui::render_title_box()
{
// Text color
const int r = 0, g = 0, b = 0;
const SDL_Rect area = { 0, 0, video_.getx(), video_.gety() };
const SDL_Rect text_shadow_rect = font::line_size(p_.text_title_, font::SIZE_XLARGE);
draw_solid_tinted_rectangle(
base_rect_.x + 15, base_rect_.y + 15,
text_shadow_rect.w + 10, text_shadow_rect.h + 10, r, g, b, 0.5, video_.getSurface()
);
update_rect(font::draw_text(
&video_, area, font::SIZE_XLARGE, font::BIGMAP_COLOUR,
p_.text_title_, base_rect_.x + 20, base_rect_.y + 20
));
}
void part_ui::render_background()
{
draw_solid_tinted_rectangle(
@ -518,6 +251,276 @@ bool part_ui::render_floating_images()
return true;
}
void part_ui::render_title_box()
{
const std::string& titletxt = p_.title();
if(titletxt.empty()) {
return;
}
int titlebox_x, titlebox_y, titlebox_max_w, titlebox_max_h;
// We later correct these according to the storytext box location.
// The text box is always aligned according to the base_rect_
// (effective background area) at the end.
titlebox_x = titlebox_padding;
titlebox_max_w = base_rect_.w - 2*titlebox_padding;
titlebox_y = titlebox_padding;
titlebox_max_h = base_rect_.h - 2*titlebox_padding;
font::ttext t;
t.set_text(titletxt, true)
.set_font_style(font::ttext::STYLE_NORMAL)
.set_font_size(titlebox_font_size)
.set_foreground_colour(titlebox_font_color)
.set_maximum_width(titlebox_max_w)
.set_maximum_height(titlebox_max_h);
surface txtsurf = t.render();
if(txtsurf.null()) {
ERR_NG << "storyscreen titlebox rendering resulted in a null surface\n";
return;
}
const int titlebox_w = txtsurf->w;
const int titlebox_h = txtsurf->h;
// TODO location correction
draw_solid_tinted_rectangle(
base_rect_.x + titlebox_x - titleshadow_padding,
base_rect_.y + titlebox_y - titleshadow_padding,
titlebox_w + 2*titleshadow_padding,
titlebox_h + 2*titleshadow_padding,
titleshadow_r, titleshadow_g, titleshadow_b,
titleshadow_opacity,
video_.getSurface()
);
video_.blit_surface(base_rect_.x + titlebox_x, base_rect_.y + titlebox_y, txtsurf);
update_rect(
static_cast<size_t>(std::max(0, base_rect_.x + titlebox_x)),
static_cast<size_t>(std::max(0, base_rect_.y + titlebox_y)),
static_cast<size_t>(std::max(0, titlebox_w)),
static_cast<size_t>(std::max(0, titlebox_h))
);
}
#ifdef LOW_MEM
void part_ui::render_story_box_borders(SDL_Rect& /*update_area*/)
{}
#else
void part_ui::render_story_box_borders(SDL_Rect& update_area)
{
const part::TEXT_BLOCK_LOCATION tbl = p_.text_block_location();
if(has_background_) {
surface border_top = NULL;
surface border_bottom = NULL;
if(tbl == part::BOTTOM || tbl == part::MIDDLE) {
border_top = image::get_image(storybox_top_border_path);
}
if(tbl == part::TOP || tbl == part::MIDDLE) {
border_bottom = image::get_image(storybox_bottom_border_path);
}
//
// If one of those are null at this point, it means that either we
// don't need that border pic, or it is missing (in such case get_image()
// would report).
//
if(border_top.null() != true) {
if((border_top = scale_surface_blended(border_top, screen_area().w, border_top->h)).null()) {
WARN_NG << "storyscreen got a null top border surface after rescaling\n";
}
else {
update_area.y -= border_top->h;
update_area.h += border_top->h;
blur_area(video_, update_area.y, border_top->h);
video_.blit_surface(0, update_area.y, border_top);
}
}
if(border_bottom.null() != true) {
if((border_bottom = scale_surface_blended(border_bottom, screen_area().w, border_bottom->h)).null()) {
WARN_NG << "storyscreen got a null bottom border surface after rescaling\n";
}
else {
blur_area(video_, update_area.h, border_bottom->h);
video_.blit_surface(0, update_area.y+update_area.h, border_bottom);
update_area.h += border_bottom->h;
}
}
}
}
#endif
void part_ui::render_story_box()
{
const std::string& storytxt = p_.text();
if(storytxt.empty()) {
wait_for_input();
return;
}
const part::TEXT_BLOCK_LOCATION tbl = p_.text_block_location();
const int max_width = buttons_x_ - storybox_padding - text_x_;
const int max_height = screen_area().h - storybox_padding;
bool skip = false, last_key = true;
font::ttext t;
t.set_text(p_.text(), true)
.set_font_style(font::ttext::STYLE_NORMAL)
.set_foreground_colour(storybox_font_color)
.set_maximum_width(max_width)
.set_maximum_height(max_height);
surface txtsurf = t.render();
if(txtsurf.null()) {
ERR_NG << "storyscreen text area rendering resulted in a null surface\n";
return;
}
int fix_text_y = text_y_;
if(fix_text_y + storybox_padding + txtsurf->h > screen_area().h && tbl != part::TOP) {
fix_text_y =
(screen_area().h > txtsurf->h + 1) ?
(std::max(0, screen_area().h - txtsurf->h - (storybox_padding+1))) :
(0);
}
int fix_text_h;
switch(tbl) {
case part::TOP:
fix_text_h = std::max(txtsurf->h + 2*storybox_padding, screen_area().h/4);
break;
case part::MIDDLE:
fix_text_h = std::max(txtsurf->h + 2*storybox_padding, screen_area().h/3);
break;
default:
fix_text_h = screen_area().h - fix_text_y;
break;
}
SDL_Rect update_area = { 0, fix_text_y, screen_area().w, fix_text_h };
/* do */ {
// this should kill the tiniest flickering caused
// by the buttons being hidden and unhidden in this scope.
update_locker locker(video_);
//back_button_.hide();
next_button_.hide();
skip_button_.hide();
//quit_button_.hide();
#ifndef LOW_MEM
blur_area(video_, fix_text_y, fix_text_h);
#endif
draw_solid_tinted_rectangle(
0, fix_text_y, screen_area().w, fix_text_h,
storyshadow_r, storyshadow_g, storyshadow_b,
storyshadow_opacity,
video_.getSurface()
);
render_story_box_borders(update_area); // no-op if LOW_MEM is defined
// Make GUI1 buttons aware of background modifications
next_button_.set_location(next_button_.location());
next_button_.hide(false);
skip_button_.set_location(skip_button_.location());
skip_button_.hide(false);
}
if(imgs_.empty()) {
update_whole_screen();
} else if(update_area.h > 0) {
update_rect(update_area);
}
// Time to do some fucking visual effect.
const int scan_height = 1, scan_width = txtsurf->w;
SDL_Rect scan = { 0, 0, scan_width, scan_height };
SDL_Rect dstrect = { text_x_, 0, scan_width, scan_height };
surface scan_dst = video_.getSurface();
bool scan_finished = false;
while(true) {
if(!(scan_finished = scan.y >= txtsurf->h)) {
//dstrect.x = text_x_;
dstrect.y = fix_text_y + scan.y + storybox_padding;
// NOTE: ::blit_surface() screws up with antialiasing and hinting when
// on backgroundless (e.g. black) screens; ttext::draw()
// uses it nonetheless, no idea why...
// Here we'll use CVideo::blit_surface() instead.
video_.blit_surface(dstrect.x, dstrect.y, txtsurf, &scan);
update_rect(dstrect);
++scan.y;
}
const bool keydown = keys_[SDLK_SPACE] || keys_[SDLK_RETURN] || keys_[SDLK_KP_ENTER];
if((keydown && !last_key) || next_button_.pressed()) {
if(skip == true || scan_finished) {
ret_ = NEXT;
break;
} else {
skip = true;
}
}
last_key = keydown;
if(keys_[SDLK_ESCAPE] || skip_button_.pressed()) {
ret_ = SKIP;
return;
}
events::pump();
events::raise_process_event();
events::raise_draw_event();
disp_.flip();
if(!skip || scan_finished) {
disp_.delay(20);
}
}
draw_solid_tinted_rectangle(
0, 0, video_.getx(), video_.gety(), 0, 0, 0,
1.0, video_.getSurface()
);
}
void part_ui::wait_for_input()
{
bool last_key = true;
while(true) {
const bool keydown = keys_[SDLK_SPACE] || keys_[SDLK_RETURN] || keys_[SDLK_KP_ENTER];
if((keydown && !last_key) || next_button_.pressed()) {
ret_ = NEXT;
break;
}
last_key = keydown;
if(keys_[SDLK_ESCAPE] || skip_button_.pressed()) {
ret_ = SKIP;
return;
}
events::pump();
events::raise_process_event();
events::raise_draw_event();
disp_.flip();
}
}
part_ui::RESULT part_ui::show()
{
if(p_.music_.empty() != true) {
@ -537,12 +540,7 @@ part_ui::RESULT part_ui::show()
}
try {
if(get_new_storyscreen_status()) {
render_text_box_with_pango();
}
else {
render_text_box();
}
render_story_box();
}
catch(utils::invalid_utf8_exception const&) {
ERR_NG << "invalid UTF-8 sequence in story text, skipping part...\n";

View file

@ -42,22 +42,45 @@ namespace storyscreen {
class part_ui
{
public:
enum RESULT { NEXT, BACK, SKIP, QUIT };
/** Storyscreen result. */
enum RESULT {
NEXT, /**< The user pressed the go-next button. */
BACK, /**< The user pressed the go-back button (TODO: not implemented). */
SKIP, /**< The user pressed the skip button. */
QUIT /**< The user pressed the quit button (TODO: not implemented). */
};
/**
* Constructor.
* @param p A storyscreen::part with the required information and parameters.
* @param disp Display.
* @param next_button Next button. Shouldn't be destroyed before the part_ui object.
* @param skip_button Skip button. Shouldn't be destroyed before the part_ui object.
*/
part_ui(part& p, display& disp, gui::button& next_button, gui::button& skip_button);
/**
* Render and display the storyscreen, process and return user input.
*/
RESULT show();
private:
part& p_;
display& disp_;
CVideo& video_;
CKey keys_;
CVideo& video_; // convenience, it's currently obtained from disp_
CKey keys_; // convenience
//gui::button& back_button_;
gui::button& next_button_;
gui::button& skip_button_;
//gui::button& quit_button_;
RESULT ret_;
double scale_factor_;
// The background is aspect-corrected when scaled, so the image doesn't
// look distorted. base_rect_ has the actual area occupied by the background.
SDL_Rect base_rect_;
surface background_;
@ -66,11 +89,24 @@ private:
int text_x_, text_y_, buttons_x_, buttons_y_;
/** Constructor implementation details. */
void prepare_background();
/** Constructor implementation details. */
void prepare_geometry();
/** Constructor implementation details. */
void prepare_floating_images();
void render_background();
void render_title_box();
void render_story_box();
void render_story_box_borders(SDL_Rect&);
/**
* Renders all floating images in sequence.
* @return 'true' if the user interrupted the operation; 'false' otherwise.
*/
bool render_floating_images();
void render_text_box();
void render_text_box_with_pango();
void wait_for_input();
};
} // end namespace storyscreen