new buttons and logic to navigate story screen,

...allowing back an forth, patch #1587 by icelus
This commit is contained in:
Jérémy Rosen 2010-06-07 18:22:49 +00:00
parent e6566fa8dd
commit cc5330c08c
9 changed files with 306 additions and 69 deletions

View file

@ -120,6 +120,7 @@ Version 1.9.0-svn:
* Clicking on some elements of sidebar now open the related help page
* Display weapon stats in recruit/recall dialog the same way as in sidebar
* Accuracy/parry have its own line and tooltip in sidebar.
* Add first, last, play and back buttons to storyscreens
* Units:
* Giant Rat moved from DiD to core.
* WML Engine:

View file

@ -107,6 +107,8 @@ public:
child_iterator &operator++() { ++i_; return *this; }
child_iterator operator++(int) { return child_iterator(i_++); }
child_iterator &operator--() { --i_; return *this; }
child_iterator operator--(int) { return child_iterator(i_--); }
config &operator*() const { return **i_; }
config *operator->() const { return &**i_; }
@ -132,6 +134,8 @@ public:
const_child_iterator &operator++() { ++i_; return *this; }
const_child_iterator operator++(int) { return const_child_iterator(i_++); }
const_child_iterator &operator--() { --i_; return *this; }
const_child_iterator operator--(int) { return const_child_iterator(i_--); }
const config &operator*() const { return **i_; }
const config *operator->() const { return &**i_; }

View file

@ -290,9 +290,11 @@ LEVEL_RESULT playsingle_controller::play_scenario(
sound::commit_music_changes();
if(!skip_replay) {
foreach (const config &s, story) {
show_storyscreen(*gui_, vconfig(s, true), level_["name"]);
storyscreen::STORY_RESULT result = show_story(*gui_, level_["name"], story);
if(result == storyscreen::QUIT) {
return QUIT;
}
assert(result == storyscreen::NEXT);
}
gui_->labels().read(level_);

View file

@ -42,12 +42,15 @@ static lg::log_domain log_engine("engine");
namespace storyscreen {
controller::controller(display& disp, const vconfig& data, const std::string& scenario_name)
controller::controller(display& disp, const vconfig& data, const std::string& scenario_name,
int segment_index, int total_segments)
: disp_(disp)
, disp_resize_lock_()
, evt_context_()
, data_(data)
, scenario_name_(scenario_name)
, segment_index_(segment_index)
, total_segments_(total_segments)
, parts_()
{
ASSERT_LOG(resources::state_of_game != NULL, "Ouch: gamestate is NULL when initializing storyscreen controller");
@ -120,17 +123,18 @@ void controller::resolve_wml(const vconfig& cfg)
}
}
void controller::show()
STORY_RESULT controller::show(START_POSITION startpos)
{
if(parts_.empty()) {
LOG_NG << "no storyscreen parts to show\n";
return;
return NEXT;
}
gui::button next_button(disp_.video(),_("Next") + std::string(" >"));
gui::button skip_button(disp_.video(),_("Skip"));
// TODO:
// gui::button back_button(disp_.video(),std::string("< ")+_("Next"));
gui::button first_button(disp_.video(),_("First") + std::string(" <<"));
gui::button last_button (disp_.video(),std::string(">> ") + _("Last"));
gui::button back_button (disp_.video(),std::string("< ")+ _("Back"));
gui::button next_button (disp_.video(),_("Next") + std::string(" >"));
gui::button play_button (disp_.video(),_("Play") + std::string(" >"));
// Build renderer cache unless built for a low-memory environment;
// caching the scaled backgrounds can take over a decent amount of memory.
@ -138,39 +142,83 @@ void controller::show()
std::vector< render_pointer_type > uis_;
foreach(part_pointer_type p, parts_) {
ASSERT_LOG( p != NULL, "Ouch: hit NULL storyscreen part in collection" );
render_pointer_type const rpt(new part_ui(*p, disp_, next_button, skip_button));
render_pointer_type const rpt(new part_ui(*p, disp_, next_button, back_button, first_button, last_button, play_button));
uis_.push_back(rpt);
}
#endif
size_t k = 0;
switch(startpos) {
case START_BEGINNING:
break;
case START_END:
k = parts_.size() -1;
break;
default:
assert(false);
break;
}
while(k < parts_.size()) {
#ifndef LOW_MEM
render_reference_type render_interface = *uis_[k];
#else
render_value_type render_interface(*parts_[k], disp_, next_button, skip_button);
render_value_type render_interface(*parts_[k], disp_, next_button, back_button, first_button, last_button, play_button);
#endif
LOG_NG << "displaying storyscreen part " << k+1 << " of " << parts_.size() << '\n';
const bool first_page = (segment_index_ == 0) && (k == 0);
const bool last_page = (segment_index_ == total_segments_ - 1) && (k == parts_.size() - 1);
first_button.hide(first_page);
back_button.hide(first_page);
last_button.hide(last_page);
next_button.hide(false);
play_button.hide(!last_page);
switch(render_interface.show()) {
case part_ui::NEXT:
++k;
break;
case part_ui::BACK:
// If we are at the first page we can't go back.
if(k > 0) {
--k;
}
else if(segment_index_ > 0) {
return BACK;
}
break;
case part_ui::SKIP:
k = parts_.size();
case part_ui::FIRST:
if(segment_index_ == 0) {
// this is the first segment
k = 0;
}
else {
// we want to rewind all the way
// to the last segment
return FIRST;
}
break;
case part_ui::LAST:
if(segment_index_ == total_segments_ - 1) {
// not at the end of this segment
k = parts_.size() - 1;
}
else {
// we want to fast forward all the way
// to the last segment
return LAST;
}
break;
case part_ui::QUIT:
return QUIT;
default:
throw quit();
assert(false);
return QUIT;
}
}
return NEXT;
}
} // end namespace storyscreen

View file

@ -21,6 +21,7 @@
#define STORYSCREEN_CONTROLLER_HPP_INCLUDED
#include "events.hpp"
#include "interface.hpp"
#include "variable.hpp"
#include "video.hpp"
@ -39,12 +40,13 @@ class floating_image;
class controller
{
public:
controller(display& disp, const vconfig& data, const std::string& scenario_name);
controller(display& disp, const vconfig& data, const std::string& scenario_name,
int segment_index, int total_segments);
/**
* Display all story screen parts in a first..last sequence.
*/
void show();
STORY_RESULT show(START_POSITION startpos=START_BEGINNING);
private:
typedef boost::shared_ptr< part > part_pointer_type;
@ -67,14 +69,14 @@ private:
vconfig data_;
std::string scenario_name_;
int segment_index_;
int total_segments_;
// The part cache.
std::vector< part_pointer_type > parts_;
public:
struct no_parts {};
struct quit {};
};
} // end namespace storyscreen

View file

@ -45,22 +45,81 @@ namespace {
config& partcfg = append_to_cfg.add_child("story").add_child("part");
partcfg["text_align"] = "centered";
}
int count_segments(const config::const_child_itors &story)
{
config::const_child_iterator itor = story.first;
int count = 0;
while(itor != story.second) {
++itor;
++count;
}
return count;
}
} // end anonymous namespace
void show_storyscreen(display& disp, const vconfig& story_cfg, const std::string& scenario_name)
storyscreen::STORY_RESULT show_story(display& disp,
const std::string& scenario_name,
const config::const_child_itors &story) {
const int total_segments = count_segments(story);
int segment_count = 0;
config::const_child_iterator itor = story.first;
storyscreen::START_POSITION startpos = storyscreen::START_BEGINNING;
while(itor != story.second) {
storyscreen::STORY_RESULT result = show_storyscreen(disp, vconfig(*itor, true), scenario_name,
startpos, segment_count, total_segments);
switch(result) {
case storyscreen::NEXT:
if(itor != story.second) {
++itor;
++segment_count;
startpos = storyscreen::START_BEGINNING;
}
break;
case storyscreen::BACK:
if(itor != story.first) {
--itor;
--segment_count;
startpos = storyscreen::START_END;
}
break;
case storyscreen::LAST:
itor = story.second;
--itor;
segment_count = total_segments - 1;
startpos = storyscreen::START_END;
break;
case storyscreen::FIRST:
itor = story.first;
segment_count = 0;
startpos = storyscreen::START_BEGINNING;
break;
case storyscreen::QUIT:
return storyscreen::QUIT;
default:
assert(false);
itor = story.second;
break;
}
}
return storyscreen::NEXT;
}
storyscreen::STORY_RESULT show_storyscreen(display& disp, const vconfig& story_cfg,
const std::string& scenario_name,
storyscreen::START_POSITION startpos,
int segment_index, int total_segments)
{
LOG_NG << "entering storyscreen procedure...\n";
storyscreen::controller ctl(disp, story_cfg, scenario_name);
storyscreen::controller ctl(disp, story_cfg, scenario_name, segment_index, total_segments);
try {
ctl.show();
} catch(storyscreen::controller::quit const&) {
LOG_NG << "leaving storyscreen for titlescreen...\n";
STUB();
}
storyscreen::STORY_RESULT ret = ctl.show(startpos);
LOG_NG << "leaving storyscreen procedure...\n";
return ret;
}
void show_endscreen(display& /*disp*/, const t_string& /*text*/, unsigned int /*duration*/)

View file

@ -20,15 +20,33 @@
#ifndef STORYSCREEN_HPP_INCLUDED
#define STORYSCREEN_HPP_INCLUDED
class config;
#include <string>
#include "config.hpp"
class vconfig;
class display;
class t_string;
#include <string>
namespace storyscreen {
enum STORY_RESULT {
NEXT,
BACK,
FIRST,
LAST,
QUIT
};
enum START_POSITION {
START_BEGINNING,
START_END
};
} /* storyscreen namespace */
/**
* Function to show an introduction sequence using story WML.
* Function to show an introduction sequence segment using story WML.
* The WML config data (story_cfg) has a format similar to:
* @code
* [part]
@ -41,9 +59,19 @@ class t_string;
* storyline,and 'img' is a background image. Each part of the sequence will
* be displayed in turn, with the user able to go to the next part, or skip
* it entirely.
* @return is NEXT if the segment played to the end, BACK if the segment played to the beginning,
* FIRST if a skip to the first segment is requested, LAST if a skip to the last segment is requested,
* and QUIT if the story was quit
*/
void show_storyscreen(display& disp, const vconfig& story_cfg, const std::string& scenario_name);
storyscreen::STORY_RESULT show_storyscreen(display& disp, const vconfig& story_cfg,
const std::string& scenario_name,
storyscreen::START_POSITION startpos,
int segment_index, int total_segments);
/**
* Function to show a complete introduction sequence using story WML (calls show_storyscreen, see there for format).
* @return is NEXT if the story played to the end and QUIT if the story was quit
*/
storyscreen::STORY_RESULT show_story(display& disp, const std::string& scenario_name, const config::const_child_itors &story);
/**
* Displays a simple fading screen with any user-provided text.
* Used after the end of single-player campaigns.

View file

@ -81,13 +81,19 @@ namespace {
namespace storyscreen {
part_ui::part_ui(part& p, display& disp, gui::button& next_button, gui::button& skip_button)
part_ui::part_ui(part& p, display& disp,
gui::button& next_button, gui::button& back_button,
gui::button& first_button, gui::button& last_button,
gui::button& play_button)
: p_(p)
, disp_(disp)
, video_(disp.video())
, keys_()
, next_button_(next_button)
, skip_button_(skip_button)
, back_button_(back_button)
, first_button_(first_button)
, last_button_(last_button)
, play_button_(play_button)
, ret_(NEXT)
, scale_factor_(1.0)
, base_rect_()
@ -139,8 +145,11 @@ void part_ui::prepare_geometry()
buttons_x_ = video_.getx() - 50;
buttons_y_ = base_rect_.y + base_rect_.h - 20;
next_button_.set_location(buttons_x_, buttons_y_ - 20);
skip_button_.set_location(buttons_x_, buttons_y_);
next_button_.set_location(buttons_x_, buttons_y_ - 60);
back_button_.set_location(buttons_x_, buttons_y_ - 40);
first_button_.set_location(buttons_x_, buttons_y_ - 20);
last_button_.set_location(buttons_x_, buttons_y_);
play_button_.set_location(buttons_x_, buttons_y_);
#else // elif !defined(USE_TINY_GUI)
@ -163,12 +172,18 @@ void part_ui::prepare_geometry()
buttons_y_ = video_.gety() - 40;
break;
}
next_button_.set_location(buttons_x_, buttons_y_ - 30);
skip_button_.set_location(buttons_x_, buttons_y_);
next_button_.set_location(buttons_x_, buttons_y_ - 90);
back_button_.set_location(buttons_x_, buttons_y_ - 60);
first_button_.set_location(buttons_x_, buttons_y_ - 30);
last_button_.set_location(buttons_x_, buttons_y_);
play_button_.set_location(buttons_x_, buttons_y_);
#endif
next_button_.set_volatile(true);
skip_button_.set_volatile(true);
play_button_.set_volatile(true);
back_button_.set_volatile(true);
first_button_.set_volatile(true);
last_button_.set_volatile(true);
}
void part_ui::prepare_floating_images()
@ -194,6 +209,7 @@ bool part_ui::render_floating_images()
update_whole_screen();
bool skip = false;
bool last_key = true;
size_t fi_n = 0;
foreach(floating_image::render_input& ri, imgs_) {
@ -206,14 +222,30 @@ bool part_ui::render_floating_images()
if(skip == false) {
for(unsigned i = 0; i != 50; ++i) {
if(keys_[SDLK_ESCAPE] || skip_button_.pressed()) {
ret_ = SKIP;
const bool next_keydown = keys_[SDLK_SPACE] || keys_[SDLK_RETURN] || keys_[SDLK_KP_ENTER] || keys_[SDLK_RIGHT];
const bool play_keydown = keys_[SDLK_ESCAPE] && !play_button_.hidden();
const bool last_keydown = (keys_[SDLK_ESCAPE] && play_button_.hidden()) || keys_[SDLK_END] || keys_[SDLK_DOWN];
const bool first_keydown = keys_[SDLK_HOME] || keys_[SDLK_UP];
const bool back_keydown = keys_[SDLK_BACKSPACE] || keys_[SDLK_LEFT];
if((last_keydown && !last_key) || last_button_.pressed()) {
ret_ = LAST;
return false;
}
else if(next_button_.pressed()) {
else if((first_keydown && !last_key) || first_button_.pressed()) {
ret_ = FIRST;
return false;
}
else if(((next_keydown || play_keydown) && !last_key) || next_button_.pressed() || play_button_.pressed()) {
ret_ = NEXT;
return false;
}
else if((back_keydown && !last_key) || back_button_.pressed()) {
ret_ = BACK;
return false;
}
last_key = next_keydown || play_keydown || last_keydown || first_keydown || back_keydown;
disp_.delay(fi.display_delay() / 50);
@ -238,7 +270,10 @@ bool part_ui::render_floating_images()
}
}
if(keys_[SDLK_ESCAPE] || next_button_.pressed() || skip_button_.pressed()) {
if(keys_[SDLK_ESCAPE] ||
next_button_.pressed() || back_button_.pressed() ||
first_button_.pressed() || last_button_.pressed() ||
play_button_.pressed()) {
skip = true;
++fi_n;
continue;
@ -372,6 +407,8 @@ void part_ui::render_story_box_borders(SDL_Rect& update_area)
void part_ui::render_story_box()
{
LOG_NG << "ENTER part_ui()::render_story_box()\n";
const std::string& storytxt = p_.text();
if(storytxt.empty()) {
wait_for_input();
@ -429,10 +466,17 @@ void part_ui::render_story_box()
// by the buttons being hidden and unhidden in this scope.
update_locker locker(video_);
//back_button_.hide();
const bool next_hidden = next_button_.hidden();
const bool back_hidden = back_button_.hidden();
const bool play_hidden = play_button_.hidden();
const bool first_hidden = first_button_.hidden();
const bool last_hidden = last_button_.hidden();
next_button_.hide();
skip_button_.hide();
//quit_button_.hide();
back_button_.hide();
first_button_.hide();
last_button_.hide();
play_button_.hide();
#ifndef LOW_MEM
blur_area(video_, fix_text_y, fix_text_h);
@ -449,9 +493,15 @@ void part_ui::render_story_box()
// 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);
next_button_.hide(next_hidden);
back_button_.set_location(back_button_.location());
back_button_.hide(back_hidden);
first_button_.set_location(first_button_.location());
first_button_.hide(first_hidden);
last_button_.set_location(last_button_.location());
last_button_.hide(last_hidden);
play_button_.set_location(play_button_.location());
play_button_.hide(play_hidden);
}
if(imgs_.empty()) {
@ -478,10 +528,15 @@ void part_ui::render_story_box()
update_rect(dstrect);
++scan.y;
}
next_button_.hide(next_button_.hidden() || (scan_finished && last_button_.hidden()));
const bool keydown = keys_[SDLK_SPACE] || keys_[SDLK_RETURN] || keys_[SDLK_KP_ENTER];
const bool next_keydown = keys_[SDLK_SPACE] || keys_[SDLK_RETURN] || keys_[SDLK_KP_ENTER] || keys_[SDLK_RIGHT];
const bool back_keydown = keys_[SDLK_BACKSPACE] || keys_[SDLK_LEFT];
const bool play_keydown = keys_[SDLK_ESCAPE] && !play_button_.hidden();
const bool last_keydown = (keys_[SDLK_ESCAPE] && play_button_.hidden()) || keys_[SDLK_END] || keys_[SDLK_DOWN];
const bool first_keydown = keys_[SDLK_HOME] || keys_[SDLK_UP];
if((keydown && !last_key) || next_button_.pressed()) {
if((next_keydown && !last_key) || next_button_.pressed()) {
if(skip == true || scan_finished) {
ret_ = NEXT;
break;
@ -490,13 +545,28 @@ void part_ui::render_story_box()
}
}
last_key = keydown;
if((play_keydown && !last_key) || play_button_.pressed()) {
ret_ = NEXT;
break;
}
if(keys_[SDLK_ESCAPE] || skip_button_.pressed()) {
ret_ = SKIP;
if((back_keydown && !last_key) || back_button_.pressed()) {
ret_ = BACK;
break;
}
if((last_keydown && !last_key) || last_button_.pressed()) {
ret_ = LAST;
return;
}
if((first_keydown && !last_key) || first_button_.pressed()) {
ret_ = FIRST;
return;
}
last_key = next_keydown || back_keydown || play_keydown || last_keydown || first_keydown;
events::pump();
events::raise_process_event();
events::raise_draw_event();
@ -515,22 +585,40 @@ void part_ui::render_story_box()
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];
LOG_NG << "ENTER part_ui()::wait_for_input()\n";
if((keydown && !last_key) || next_button_.pressed()) {
bool last_key = true;
next_button_.hide(next_button_.hidden() || last_button_.hidden());
while(true) {
const bool next_keydown = keys_[SDLK_SPACE] || keys_[SDLK_RETURN] || keys_[SDLK_KP_ENTER] || keys_[SDLK_RIGHT];
const bool play_keydown = keys_[SDLK_ESCAPE] && !play_button_.hidden();
const bool last_keydown = (keys_[SDLK_ESCAPE] && play_button_.hidden()) || keys_[SDLK_END] || keys_[SDLK_DOWN];
const bool first_keydown = keys_[SDLK_HOME] || keys_[SDLK_UP];
const bool back_keydown = keys_[SDLK_BACKSPACE] || keys_[SDLK_LEFT];
if(((next_keydown || play_keydown) && !last_key) || next_button_.pressed() || play_button_.pressed()) {
ret_ = NEXT;
break;
}
if((back_keydown && !last_key) || back_button_.pressed()) {
ret_ = BACK;
break;
}
last_key = keydown;
if(keys_[SDLK_ESCAPE] || skip_button_.pressed()) {
ret_ = SKIP;
if((last_keydown && !last_key) || last_button_.pressed()) {
ret_ = LAST;
return;
}
if((first_keydown && !last_key) || first_button_.pressed()) {
ret_ = FIRST;
return;
}
last_key = next_keydown || back_keydown || first_keydown || last_keydown || play_keydown;
events::pump();
events::raise_process_event();
events::raise_draw_event();

View file

@ -45,9 +45,10 @@ public:
/** 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). */
BACK, /**< The user pressed the go-back button. */
FIRST, /**< The user pressed the go-first button. */
LAST, /**< The user pressed the go-last button. */
QUIT /**< The user selected quit. */
};
/**
@ -57,7 +58,10 @@ public:
* @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);
part_ui(part& p, display& disp,
gui::button& next_button, gui::button& back_button,
gui::button& first_button, gui::button& last_button,
gui::button& play_button);
/**
* Render and display the storyscreen, process and return user input.
@ -70,10 +74,11 @@ private:
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_;
gui::button& back_button_;
gui::button& first_button_;
gui::button& last_button_;
gui::button& play_button_;
RESULT ret_;