storyscreen/render.cpp

Go to the documentation of this file.
00001 /* $Id: render.cpp 53082 2012-02-16 20:10:38Z shadowmaster $ */
00002 /*
00003    Copyright (C) 2003 - 2012 by David White <dave@whitevine.net>
00004    Copyright (C) 2009 - 2012 by Ignacio R. Morelle <shadowm2006@gmail.com>
00005    Part of the Battle for Wesnoth Project http://www.wesnoth.org/
00006 
00007    This program is free software; you can redistribute it and/or modify
00008    it under the terms of the GNU General Public License as published by
00009    the Free Software Foundation; either version 2 of the License, or
00010    (at your option) any later version.
00011    This program is distributed in the hope that it will be useful,
00012    but WITHOUT ANY WARRANTY.
00013 
00014    See the COPYING file for more details.
00015 */
00016 
00017 /**
00018  * @file
00019  * Storyscreen parts renderer.
00020  * @todo Translate relevant parts to GUI2.
00021  */
00022 
00023 #include "global.hpp"
00024 #include "asserts.hpp"
00025 #include "foreach.hpp"
00026 #include "log.hpp"
00027 #include "storyscreen/part.hpp"
00028 #include "storyscreen/render.hpp"
00029 
00030 #include "display.hpp"
00031 #include "image.hpp"
00032 #include "language.hpp"
00033 #include "sound.hpp"
00034 #include "text.hpp"
00035 #include "video.hpp"
00036 
00037 static lg::log_domain log_engine("engine");
00038 #define ERR_NG  LOG_STREAM(err,  log_engine)
00039 #define WARN_NG LOG_STREAM(warn, log_engine)
00040 #define LOG_NG  LOG_STREAM(info, log_engine)
00041 
00042 
00043 namespace {
00044     int const storybox_padding = 10; // px
00045     double const storyshadow_opacity = 0.5;
00046     int const storyshadow_r = 0;
00047     int const storyshadow_g = 0;
00048     int const storyshadow_b = 0;
00049 
00050     int const titlebox_padding = 20; // px
00051     int const titleshadow_padding = 5; // px
00052     double const titleshadow_opacity = 0.5;
00053     int const titleshadow_r = 0;
00054     int const titleshadow_g = 0;
00055     int const titleshadow_b = 0;
00056 
00057     int const titlebox_font_size = 20; // pt?
00058     int const storybox_font_size = 14; // pt?
00059 
00060     Uint32 const titlebox_font_color = 0xFFFFFFFF;
00061     Uint32 const storybox_font_color = 0xDDDDDDFF;
00062 
00063 #ifndef LOW_MEM
00064     // Hard-coded path to a suitable (tileable) pic for the storytxt box border.
00065     std::string const storybox_top_border_path = "dialogs/translucent54-border-top.png";
00066     std::string const storybox_bottom_border_path = "dialogs/translucent54-border-bottom.png";
00067 
00068     void blur_area(CVideo& video, int y, int h)
00069     {
00070         SDL_Rect blur_rect = create_rect(0, y, screen_area().w, h);
00071         surface blur = get_surface_portion(video.getSurface(), blur_rect);
00072         blur = blur_surface(blur, 1, false);
00073         video.blit_surface(0, y, blur);
00074     }
00075 #endif
00076 }
00077 
00078 namespace storyscreen {
00079 
00080 part_ui::part_ui(part &p, display &disp, gui::button &next_button,
00081     gui::button &back_button, gui::button&play_button)
00082     : p_(p)
00083     , disp_(disp)
00084     , video_(disp.video())
00085     , keys_()
00086     , next_button_(next_button)
00087     , back_button_(back_button)
00088     , play_button_(play_button)
00089     , ret_(NEXT), skip_(false), last_key_(false)
00090     , scale_factor_(1.0)
00091     , base_rect_()
00092     , background_(NULL)
00093     , imgs_()
00094     , has_background_(false)
00095     , text_x_(200)
00096     , text_y_(400)
00097     , buttons_x_(0)
00098     , buttons_y_(0)
00099 {
00100     this->prepare_background();
00101     this->prepare_geometry();
00102     this->prepare_floating_images();
00103 }
00104 
00105 void part_ui::prepare_background()
00106 {
00107     // Build background surface
00108     if(p_.background().empty() != true) {
00109         background_.assign( image::get_image(p_.background()) );
00110     }
00111     has_background_ = !background_.null();
00112     if(background_.null() || background_->w * background_-> h == 0) {
00113         background_.assign( create_neutral_surface(video_.getx(), video_.gety()) );
00114     }
00115 
00116     const double xscale = 1.0 * video_.getx() / background_->w;
00117     const double yscale = 1.0 * video_.gety() / background_->h;
00118     scale_factor_ = p_.scale_background() ? std::min<double>(xscale,yscale) : 1.0;
00119 
00120     background_ =
00121         scale_surface(background_, static_cast<int>(background_->w*scale_factor_), static_cast<int>(background_->h*scale_factor_));
00122 
00123     ASSERT_LOG(background_.null() == false, "Oops: storyscreen part background got NULL");
00124 }
00125 
00126 void part_ui::prepare_geometry()
00127 {
00128     base_rect_.x = (video_.getx() - background_->w) / 2;
00129     base_rect_.y = (video_.gety() - background_->h) / 2;
00130     base_rect_.w = background_->w;
00131     base_rect_.h = background_->h;
00132 
00133     if(video_.getx() <= 800) {
00134         text_x_ = 10;
00135         buttons_x_ = video_.getx() - 100 - 20;
00136     }
00137     else {
00138         text_x_ = 100 - 40;
00139         buttons_x_ = video_.getx() - 100 - 40;
00140     }
00141 
00142     switch(p_.story_text_location())
00143     {
00144     case part::BLOCK_TOP:
00145         text_y_ = 0;
00146         buttons_y_ = 40;
00147         break;
00148     case part::BLOCK_MIDDLE:
00149         text_y_ = video_.gety() / 3;
00150         buttons_y_ = video_.gety() / 2 + 15;
00151         break;
00152     default: // part::BLOCK_BOTTOM
00153         text_y_ = video_.gety() - 200;
00154         buttons_y_ = video_.gety() - 40;
00155         break;
00156     }
00157 
00158     back_button_.set_location(buttons_x_, buttons_y_ - 30);
00159     next_button_.set_location(buttons_x_ + play_button_.width() - next_button_.width(), buttons_y_ - 30);
00160     play_button_.set_location(buttons_x_, buttons_y_);
00161 
00162     next_button_.set_volatile(true);
00163     play_button_.set_volatile(true);
00164     back_button_.set_volatile(true);
00165 }
00166 
00167 void part_ui::prepare_floating_images()
00168 {
00169     // Build floating image surfaces
00170     foreach(const floating_image& fi, p_.get_floating_images()) {
00171         imgs_.push_back( fi.get_render_input(scale_factor_, base_rect_) );
00172     }
00173 }
00174 
00175 void part_ui::render_background()
00176 {
00177     draw_solid_tinted_rectangle(
00178         0, 0, video_.getx(), video_.gety(), 0, 0, 0, 1.0,
00179         video_.getSurface()
00180     );
00181     sdl_blit(background_, NULL, video_.getSurface(), &base_rect_);
00182 }
00183 
00184 bool part_ui::render_floating_images()
00185 {
00186     events::raise_draw_event();
00187     update_whole_screen();
00188 
00189     skip_ = false;
00190     last_key_ = true;
00191 
00192     size_t fi_n = 0;
00193     foreach(floating_image::render_input& ri, imgs_) {
00194         const floating_image& fi = p_.get_floating_images()[fi_n];
00195 
00196         if(!ri.image.null()) {
00197             sdl_blit(ri.image, NULL, video_.getSurface(), &ri.rect);
00198             update_rect(ri.rect);
00199         }
00200 
00201         if (!skip_)
00202         {
00203             int delay = fi.display_delay(), delay_step = 20;
00204             for (int i = 0; i != (delay + delay_step - 1) / delay_step; ++i)
00205             {
00206                 if (handle_interface()) return false;
00207                 if (skip_) break;
00208                 disp_.delay(std::min<int>(delay_step, delay - i * delay_step));
00209             }
00210         }
00211 
00212         ++fi_n;
00213     }
00214 
00215     return true;
00216 }
00217 
00218 void part_ui::render_title_box()
00219 {
00220     const std::string& titletxt = p_.title();
00221     if(titletxt.empty()) {
00222         return;
00223     }
00224 
00225     int titlebox_x, titlebox_y, titlebox_max_w, titlebox_max_h;
00226     // We later correct these according to the storytext box location.
00227     // The text box is always aligned according to the base_rect_
00228     // (effective background area) at the end.
00229     titlebox_x = titlebox_padding;
00230     titlebox_max_w = base_rect_.w - 2*titlebox_padding;
00231     titlebox_y = titlebox_padding;
00232     titlebox_max_h = base_rect_.h - 2*titlebox_padding;
00233 
00234     font::ttext t;
00235     if(!t.set_text(titletxt, true)) {
00236         ERR_NG << "Text: Invalid markup in '"
00237                 << titletxt << "' rendered as is.\n";
00238         t.set_text(titletxt, false);
00239     }
00240 
00241     t.set_font_style(font::ttext::STYLE_NORMAL)
00242          .set_font_size(titlebox_font_size)
00243          .set_foreground_color(titlebox_font_color)
00244          .set_maximum_width(titlebox_max_w)
00245          .set_maximum_height(titlebox_max_h);
00246     surface txtsurf = t.render();
00247 
00248     if(txtsurf.null()) {
00249         ERR_NG << "storyscreen titlebox rendering resulted in a null surface\n";
00250         return;
00251     }
00252 
00253     const int titlebox_w = txtsurf->w;
00254     const int titlebox_h = txtsurf->h;
00255 
00256     switch(p_.title_text_alignment()) {
00257     case part::TEXT_CENTERED:
00258         titlebox_x = base_rect_.w / 2 - titlebox_w / 2 - titlebox_padding;
00259         break;
00260     case part::TEXT_RIGHT:
00261         titlebox_x = base_rect_.w - titlebox_padding - titlebox_w;
00262         break;
00263     default:
00264         break; // already set before
00265     }
00266 
00267     draw_solid_tinted_rectangle(
00268         base_rect_.x + titlebox_x - titleshadow_padding,
00269         base_rect_.y + titlebox_y - titleshadow_padding,
00270         titlebox_w + 2*titleshadow_padding,
00271         titlebox_h + 2*titleshadow_padding,
00272         titleshadow_r, titleshadow_g, titleshadow_b,
00273         titleshadow_opacity,
00274         video_.getSurface()
00275     );
00276 
00277     video_.blit_surface(base_rect_.x + titlebox_x, base_rect_.y + titlebox_y, txtsurf);
00278 
00279     update_rect(
00280         static_cast<size_t>(std::max(0, base_rect_.x + titlebox_x)),
00281         static_cast<size_t>(std::max(0, base_rect_.y + titlebox_y)),
00282         static_cast<size_t>(std::max(0, titlebox_w)),
00283         static_cast<size_t>(std::max(0, titlebox_h))
00284     );
00285 }
00286 
00287 #ifdef LOW_MEM
00288 void part_ui::render_story_box_borders(SDL_Rect& /*update_area*/)
00289 {}
00290 #else
00291 void part_ui::render_story_box_borders(SDL_Rect& update_area)
00292 {
00293     const part::BLOCK_LOCATION tbl = p_.story_text_location();
00294 
00295     if(has_background_) {
00296         surface border_top = NULL;
00297         surface border_bottom = NULL;
00298 
00299         if(tbl == part::BLOCK_BOTTOM || tbl == part::BLOCK_MIDDLE) {
00300             border_top = image::get_image(storybox_top_border_path);
00301         }
00302 
00303         if(tbl == part::BLOCK_TOP || tbl == part::BLOCK_MIDDLE) {
00304             border_bottom = image::get_image(storybox_bottom_border_path);
00305         }
00306 
00307         //
00308         // If one of those are null at this point, it means that either we
00309         // don't need that border pic, or it is missing (in such case get_image()
00310         // would report).
00311         //
00312 
00313         if(border_top.null() != true) {
00314             if((border_top = scale_surface(border_top, screen_area().w, border_top->h)).null()) {
00315                 WARN_NG << "storyscreen got a null top border surface after rescaling\n";
00316             }
00317             else {
00318                 update_area.y -= border_top->h;
00319                 update_area.h += border_top->h;
00320                 blur_area(video_, update_area.y, border_top->h);
00321                 video_.blit_surface(0, update_area.y, border_top);
00322             }
00323         }
00324 
00325         if(border_bottom.null() != true) {
00326             if((border_bottom = scale_surface(border_bottom, screen_area().w, border_bottom->h)).null()) {
00327                 WARN_NG << "storyscreen got a null bottom border surface after rescaling\n";
00328             }
00329             else {
00330                 blur_area(video_, update_area.h, border_bottom->h);
00331                 video_.blit_surface(0, update_area.y+update_area.h, border_bottom);
00332                 update_area.h += border_bottom->h;
00333             }
00334         }
00335     }
00336 }
00337 #endif
00338 
00339 void part_ui::render_story_box()
00340 {
00341     LOG_NG << "ENTER part_ui()::render_story_box()\n";
00342 
00343     const std::string& storytxt = p_.text();
00344     if(storytxt.empty()) {
00345         update_whole_screen();
00346         wait_for_input();
00347         return;
00348     }
00349 
00350     const part::BLOCK_LOCATION tbl = p_.story_text_location();
00351     const int max_width = buttons_x_ - storybox_padding - text_x_;
00352     const int max_height = screen_area().h - storybox_padding;
00353 
00354     skip_ = false;
00355     last_key_ = true;
00356 
00357     font::ttext t;
00358     if(!t.set_text(p_.text(), true)) {
00359         ERR_NG << "Text: Invalid markup in '"
00360                 << p_.text() << "' rendered as is.\n";
00361         t.set_text(p_.text(), false);
00362     }
00363     t.set_font_style(font::ttext::STYLE_NORMAL)
00364          .set_font_size(storybox_font_size)
00365          .set_foreground_color(storybox_font_color)
00366          .set_maximum_width(max_width)
00367          .set_maximum_height(max_height);
00368     surface txtsurf = t.render();
00369 
00370     if(txtsurf.null()) {
00371         ERR_NG << "storyscreen text area rendering resulted in a null surface\n";
00372         return;
00373     }
00374 
00375     int fix_text_y = text_y_;
00376     if(fix_text_y + storybox_padding + txtsurf->h > screen_area().h && tbl != part::BLOCK_TOP) {
00377         fix_text_y =
00378             (screen_area().h > txtsurf->h + 1) ?
00379             (std::max(0, screen_area().h - txtsurf->h - (storybox_padding+1))) :
00380             (0);
00381     }
00382     int fix_text_h;
00383     switch(tbl) {
00384     case part::BLOCK_TOP:
00385         fix_text_h = std::max(txtsurf->h + 2*storybox_padding, screen_area().h/4);
00386         break;
00387     case part::BLOCK_MIDDLE:
00388         fix_text_h = std::max(txtsurf->h + 2*storybox_padding, screen_area().h/3);
00389         break;
00390     default:
00391         fix_text_h = screen_area().h - fix_text_y;
00392         break;
00393     }
00394 
00395     SDL_Rect update_area = create_rect(0
00396             , fix_text_y
00397             , screen_area().w
00398             , fix_text_h);
00399 
00400     /* do */ {
00401         // this should kill the tiniest flickering caused
00402         // by the buttons being hidden and unhidden in this scope.
00403         update_locker locker(video_);
00404 
00405         next_button_.hide();
00406         back_button_.hide();
00407         play_button_.hide();
00408 
00409 #ifndef LOW_MEM
00410         blur_area(video_, fix_text_y, fix_text_h);
00411 #endif
00412 
00413         draw_solid_tinted_rectangle(
00414             0, fix_text_y, screen_area().w, fix_text_h,
00415             storyshadow_r, storyshadow_g, storyshadow_b,
00416             storyshadow_opacity,
00417             video_.getSurface()
00418         );
00419 
00420         render_story_box_borders(update_area); // no-op if LOW_MEM is defined
00421 
00422         next_button_.hide(false);
00423         back_button_.hide(false);
00424         play_button_.hide(false);
00425     }
00426 
00427     if(imgs_.empty()) {
00428         update_whole_screen();
00429     } else if(update_area.h > 0) {
00430         update_rect(update_area);
00431     }
00432 
00433     // Time to do some fucking visual effect.
00434     const int scan_height = 1, scan_width = txtsurf->w;
00435     SDL_Rect scan = create_rect(0, 0, scan_width, scan_height);
00436     SDL_Rect dstrect = create_rect(text_x_, 0, scan_width, scan_height);
00437     surface scan_dst = video_.getSurface();
00438     bool scan_finished = false;
00439     while(true) {
00440         scan_finished = scan.y >= txtsurf->h;
00441         if (!scan_finished)
00442         {
00443             //dstrect.x = text_x_;
00444             dstrect.y = fix_text_y + scan.y + storybox_padding;
00445             // NOTE: ::blit_surface() screws up with antialiasing and hinting when
00446             //       on backgroundless (e.g. black) screens; ttext::draw()
00447             //       uses it nonetheless, no idea why...
00448             //       Here we'll use CVideo::blit_surface() instead.
00449             video_.blit_surface(dstrect.x, dstrect.y, txtsurf, &scan);
00450             update_rect(dstrect);
00451             ++scan.y;
00452         }
00453         else skip_ = true;
00454 
00455         if (handle_interface()) break;
00456 
00457         if (!skip_ || scan_finished) {
00458             disp_.delay(20);
00459         }
00460     }
00461 
00462     draw_solid_tinted_rectangle(
00463         0, 0, video_.getx(), video_.gety(), 0, 0, 0,
00464         1.0, video_.getSurface()
00465     );
00466 }
00467 
00468 void part_ui::wait_for_input()
00469 {
00470     LOG_NG << "ENTER part_ui()::wait_for_input()\n";
00471 
00472     last_key_ = true;
00473     skip_ = true;
00474     while (!handle_interface()) {
00475         disp_.delay(20);
00476     }
00477 }
00478 
00479 bool part_ui::handle_interface()
00480 {
00481     bool result = false;
00482 
00483     bool next_keydown = keys_[SDLK_SPACE] || keys_[SDLK_RETURN] ||
00484         keys_[SDLK_KP_ENTER] || keys_[SDLK_RIGHT];
00485     bool back_keydown = keys_[SDLK_BACKSPACE] || keys_[SDLK_LEFT];
00486     bool play_keydown = keys_[SDLK_ESCAPE];
00487 
00488     if ((next_keydown && !last_key_) || next_button_.pressed())
00489     {
00490         next_button_.release();
00491         if (skip_) {
00492             ret_ = NEXT;
00493             result = true;
00494         } else {
00495             skip_ = true;
00496         }
00497     }
00498 
00499     if ((play_keydown && !last_key_) || play_button_.pressed()) {
00500         play_button_.release();
00501         ret_ = QUIT;
00502         result = true;
00503     }
00504 
00505     if ((back_keydown && !last_key_) || back_button_.pressed()) {
00506         back_button_.release();
00507         ret_ = BACK;
00508         result = true;
00509     }
00510 
00511     last_key_ = next_keydown || back_keydown || play_keydown;
00512 
00513     events::pump();
00514     events::raise_process_event();
00515     events::raise_draw_event();
00516     disp_.flip();
00517 
00518     return result;
00519 }
00520 
00521 part_ui::RESULT part_ui::show()
00522 {
00523     if(p_.music().empty() != true) {
00524         sound::play_music_repeatedly(p_.music());
00525     }
00526 
00527     if(p_.sound().empty() != true) {
00528         sound::play_sound(p_.sound());
00529     }
00530 
00531     render_background();
00532 
00533     if(p_.show_title()) {
00534         render_title_box();
00535     }
00536 
00537     if(!imgs_.empty()) {
00538         if(!render_floating_images()) {
00539             return ret_;
00540         }
00541     }
00542 
00543     try {
00544         render_story_box();
00545     }
00546     catch(utils::invalid_utf8_exception const&) {
00547         ERR_NG << "invalid UTF-8 sequence in story text, skipping part...\n";
00548     }
00549 
00550     return ret_;
00551 }
00552 
00553 } // end namespace storyscreen
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines

Generated by doxygen 1.7.1 on Fri May 25 2012 01:03:10 for The Battle for Wesnoth
Gna! | Forum | Wiki | CIA | devdocs