Converted Recall dialog to GUI2

This also includes  the appropriate expansions and design updates to the unit_preview_pane widget.
This commit is contained in:
Charles Dang 2016-08-02 09:13:07 +11:00
parent a515dd403a
commit 105fbe6bc6
11 changed files with 632 additions and 331 deletions

View file

@ -7,9 +7,9 @@
grow_factor = 0
[column]
border = "bottom"
border = "all"
border_size = 5
horizontal_alignment = "center"
horizontal_alignment = "left"
vertical_alignment = "center"
[image]
@ -20,30 +20,11 @@
[/column]
[/row]
[row]
grow_factor = 0
[column]
border = "bottom"
border_size = 5
horizontal_alignment = "center"
[button]
id = "type_profile"
definition = "default"
label = _ "Profile"
[/button]
[/column]
[/row]
[row]
grow_factor = 0
[column]
border = "bottom"
border = "all"
border_size = 5
vertical_grow = "true"
horizontal_grow = "true"
@ -61,6 +42,7 @@
grow_factor = 0
[column]
grow_factor = 0
horizontal_grow = "true"
[grid]
@ -69,7 +51,8 @@
grow_factor = 1
[column]
border = "right,bottom"
grow_factor = 0
border = "left,right"
border_size = 5
horizontal_alignment = "left"
@ -80,7 +63,8 @@
[/column]
[column]
border = "right,bottom"
grow_factor = 0
border = "left,right"
border_size = 5
horizontal_alignment = "left"
@ -91,7 +75,8 @@
[/column]
[column]
border = "bottom"
grow_factor = 0
border = "left,right"
border_size = 5
horizontal_alignment = "left"
@ -100,6 +85,21 @@
[/image]
[/column]
[column]
grow_factor = 1
border = "left,right"
border_size = 5
horizontal_alignment = "right"
[button]
id = "type_profile"
definition = "action_about"
label = _ "Profile"
[/button]
[/column]
[/row]
@ -115,6 +115,8 @@
[column]
vertical_grow = "true"
horizontal_alignment = "left"
border = "all"
border_size = 5
[label]
id = "type_details"
@ -289,8 +291,8 @@
border_size = 5
[label]
id = "type_details"
definition = "default"
id = "type_details_minimal"
definition = "default_small"
text_alignment = "right"
[/label]
@ -348,8 +350,8 @@
border_size = 5
[label]
id = "type_details"
definition = "default"
id = "type_details_minimal"
definition = "default_small"
text_alignment = "left"
[/label]

View file

@ -627,6 +627,8 @@
<Unit filename="../../src/gui/dialogs/unit_attack.hpp" />
<Unit filename="../../src/gui/dialogs/unit_create.cpp" />
<Unit filename="../../src/gui/dialogs/unit_create.hpp" />
<Unit filename="../../src/gui/dialogs/unit_recall.cpp" />
<Unit filename="../../src/gui/dialogs/unit_recall.hpp" />
<Unit filename="../../src/gui/dialogs/unit_recruit.cpp" />
<Unit filename="../../src/gui/dialogs/unit_recruit.hpp" />
<Unit filename="../../src/gui/dialogs/wml_error.cpp" />

View file

@ -843,6 +843,7 @@ set(wesnoth-main_SRC
gui/dialogs/transient_message.cpp
gui/dialogs/unit_attack.cpp
gui/dialogs/unit_create.cpp
gui/dialogs/unit_recall.cpp
gui/dialogs/unit_recruit.cpp
gui/dialogs/wml_error.cpp
gui/dialogs/wml_message.cpp

View file

@ -420,6 +420,7 @@ wesnoth_sources = Split("""
gui/dialogs/transient_message.cpp
gui/dialogs/unit_attack.cpp
gui/dialogs/unit_create.cpp
gui/dialogs/unit_recall.cpp
gui/dialogs/unit_recruit.cpp
gui/dialogs/wml_error.cpp
gui/dialogs/wml_message.cpp

View file

@ -39,7 +39,6 @@
#include "menu_events.hpp"
#include "mouse_handler_base.hpp"
#include "minimap.hpp"
#include "recall_list_manager.hpp"
#include "replay.hpp"
#include "replay_helper.hpp"
#include "resources.hpp"
@ -83,90 +82,6 @@ static lg::log_domain log_config("config");
namespace dialogs
{
namespace
{
class delete_recall_unit : public gui::dialog_button_action
{
public:
delete_recall_unit(display& disp, gui::filter_textbox& filter, const std::shared_ptr<std::vector<unit_const_ptr > >& units) : disp_(disp), filter_(filter), units_(units) {}
private:
gui::dialog_button_action::RESULT button_pressed(int menu_selection);
display& disp_;
gui::filter_textbox& filter_;
std::shared_ptr<std::vector<unit_const_ptr > > units_;
};
template<typename T> void dump(const T & units)
{
log_scope2(log_display, "dump()")
LOG_DP << "size: " << units.size() << "\n";
size_t idx = 0;
for (const unit_const_ptr & u_ptr : units) {
LOG_DP << "unit[" << (idx++) << "]: " << u_ptr->id() << " name = '" << u_ptr->name() << "'\n";
}
}
gui::dialog_button_action::RESULT delete_recall_unit::button_pressed(int menu_selection)
{
const size_t index = size_t(filter_.get_index(menu_selection));
LOG_DP << "units_:\n"; dump(*units_);
if(index < units_->size()) {
const unit_const_ptr & u_ptr = units_->at(index);
const unit & u = *u_ptr;
//If the unit is of level > 1, or is close to advancing,
//we warn the player about it
std::stringstream message;
if (u.loyal()) {
// TRANSLATORS: this string ends with a space
message << _("This unit is loyal and requires no upkeep. ") << (u.gender() == unit_race::MALE ? _("Do you really want to dismiss him?")
: _("Do you really want to dismiss her?"));
} else if(u.level() > 1) {
// TRANSLATORS: this string ends with a space
message << _("This unit is an experienced one, having advanced levels. ") << (u.gender() == unit_race::MALE ? _("Do you really want to dismiss him?")
: _("Do you really want to dismiss her?"));
} else if(u.experience() > u.max_experience()/2) {
// TRANSLATORS: this string ends with a space
message << _("This unit is close to advancing a level. ") << (u.gender() == unit_race::MALE ? _("Do you really want to dismiss him?")
: _("Do you really want to dismiss her?"));
}
if(!message.str().empty()) {
const int res = gui2::show_message(disp_.video(), _("Dismiss Unit"), message.str(), gui2::tmessage::yes_no_buttons);
if(res == gui2::twindow::CANCEL) {
return gui::CONTINUE_DIALOG;
}
}
// Remove the item from our dialog's list
units_->erase(units_->begin() + index);
// Remove the item from filter_textbox memory
filter_.delete_item(menu_selection);
LOG_DP << "Dismissing a unit, side = " << u.side() << " id = '" << u.id() << "'\n";
LOG_DP << "That side's recall list:\n";
dump((*resources::teams)[u.side() -1].recall_list());
// Find the unit in the recall list.
unit_ptr dismissed_unit = (*resources::teams)[u.side() -1].recall_list().find_if_matches_id(u.id());
assert(dismissed_unit);
// Record the dismissal, then delete the unit.
synced_context::run_and_throw("disband", replay_helper::get_disband(dismissed_unit->id()));
return gui::DELETE_ITEM;
} else {
return gui::CONTINUE_DIALOG;
}
}
} //anon namespace
int advance_unit_dialog(const map_location &loc)
{
unit_map::iterator u = resources::units->find(loc);
@ -427,146 +342,6 @@ void show_objectives(const std::string &scenarioname, const std::string &objecti
(objectives.empty() ? no_objectives : objectives), "", true);
}
#ifdef LOW_MEM
int recall_dialog(display& disp, const std::shared_ptr<std::vector< unit_const_ptr > > & units, int /*side*/, const std::string& title_suffix, const int team_recall_cost)
#else
int recall_dialog(display& disp, const std::shared_ptr<std::vector< unit_const_ptr > > & units, int side, const std::string& title_suffix, const int team_recall_cost)
#endif
{
std::vector<std::string> options, options_to_filter;
std::ostringstream heading;
heading << HEADING_PREFIX << COLUMN_SEPARATOR << _("Type")
<< COLUMN_SEPARATOR << _("Name")
<< COLUMN_SEPARATOR << _("Level^Lvl.")
<< COLUMN_SEPARATOR << _("XP");
heading << COLUMN_SEPARATOR << _("Traits");
gui::menu::basic_sorter sorter;
sorter.set_alpha_sort(1).set_alpha_sort(2);
sorter.set_level_sort(3,4).set_xp_sort(4).set_alpha_sort(5);
options.push_back(heading.str());
options_to_filter.push_back(options.back());
for (const unit_const_ptr & u : *units)
{
std::stringstream option, option_to_filter;
std::string name = u->name();
if (name.empty()) name = utils::unicode_em_dash;
option << IMAGE_PREFIX << u->absolute_image();
#ifndef LOW_MEM
option << "~RC(" << u->team_color() << '>'
<< team::get_side_color_index(side) << ')';
if(u->can_recruit()) {
option << "~BLIT(" << unit::leader_crown() << ")";
}
for(const std::string& overlay : u->overlays())
{
option << "~BLIT(" << overlay << ")";
}
#endif
option << COLUMN_SEPARATOR;
int cost = u->recall_cost();
if(cost < 0) {
cost = team_recall_cost;
}
option << u->type_name() << "\n";
if(cost > team_recall_cost) {
option << font::NORMAL_TEXT << "<255,0,0>";
}
else if(cost == team_recall_cost) {
option << font::NORMAL_TEXT;
}
else if(cost < team_recall_cost) {
option << font::NORMAL_TEXT << "<0,255,0>";
}
option << cost << " Gold" << COLUMN_SEPARATOR
<< name << COLUMN_SEPARATOR;
// Show units of level (0=gray, 1 normal, 2 bold, 2+ bold&wbright)
const int level = u->level();
if(level < 1) {
option << "<150,150,150>";
} else if(level == 1) {
option << font::NORMAL_TEXT;
} else if(level == 2) {
option << font::BOLD_TEXT;
} else if(level > 2 ) {
option << font::BOLD_TEXT << "<255,255,255>";
}
option << level << COLUMN_SEPARATOR;
option << font::color2markup(u->xp_color()) << u->experience() << "/";
if (u->can_advance())
option << u->max_experience();
else
option << "-";
option_to_filter << u->type_name() << " " << name << " " << u->level();
option << COLUMN_SEPARATOR;
for(const t_string& trait : u->trait_names()) {
option << trait << '\n';
option_to_filter << " " << trait;
}
options.push_back(option.str());
options_to_filter.push_back(option_to_filter.str());
}
gui::dialog rmenu(disp.video(), _("Recall") + title_suffix,
_("Select unit:") + std::string("\n"),
gui::OK_CANCEL, gui::dialog::default_style);
gui::menu::imgsel_style units_display_style(gui::menu::bluebg_style);
units_display_style.scale_images(font::relative_size(72), font::relative_size(72));
gui::menu* units_menu = new gui::menu(disp.video(), options, false, -1,
gui::dialog::max_menu_width, &sorter, &units_display_style, false);
rmenu.set_menu(units_menu);
gui::filter_textbox* filter = new gui::filter_textbox(disp.video(),
_("Filter: "), options, options_to_filter, 1, rmenu, 200);
rmenu.set_textbox(filter);
delete_recall_unit recall_deleter(disp, *filter, units);
gui::dialog_button_info delete_button(&recall_deleter,_("Dismiss Unit"));
rmenu.add_button(delete_button);
rmenu.add_button(new help::help_button(disp.video(), "recruit_and_recall"),
gui::dialog::BUTTON_HELP);
dialogs::units_list_preview_pane unit_preview(units, filter);
rmenu.add_pane(&unit_preview);
//sort by level
static int sort_by = 3;
static bool sort_reversed = false;
if(sort_by >= 0) {
rmenu.get_menu().sort_by(sort_by);
// "reclick" on the sorter to reverse the order
if(sort_reversed) {
rmenu.get_menu().sort_by(sort_by);
}
}
int res = rmenu.show();
res = filter->get_index(res);
sort_by = rmenu.get_menu().get_sort_by();
sort_reversed = rmenu.get_menu().get_sort_reversed();
return res;
}
namespace {
static const int unit_preview_border = 10;
}

View file

@ -50,8 +50,6 @@ void show_objectives(const std::string& scenarioname, const std::string &objecti
void show_unit_list(display& gui);
int recall_dialog(display& disp, const std::shared_ptr<std::vector<unit_const_ptr > > & units, int side, const std::string& title_suffix, const int team_recall_cost);
/** Show unit-stats in a side-pane to unit-list, recall-list, etc. */
class unit_preview_pane : public gui::preview_pane
{

View file

@ -0,0 +1,399 @@
/*
Copyright (C) 2016 by the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#define GETTEXT_DOMAIN "wesnoth-lib"
#include "gui/dialogs/unit_recall.hpp"
#include "gui/auxiliary/find_widget.hpp"
#include "gui/core/log.hpp"
#include "gui/dialogs/helper.hpp"
#include "gui/dialogs/message.hpp"
#ifdef GUI2_EXPERIMENTAL_LISTBOX
#include "gui/widgets/list.hpp"
#else
#include "gui/widgets/listbox.hpp"
#endif
#include "gui/widgets/settings.hpp"
#include "gui/widgets/button.hpp"
#include "gui/widgets/image.hpp"
#include "gui/widgets/label.hpp"
#include "gui/widgets/text_box.hpp"
#include "gui/widgets/toggle_button.hpp"
#include "gui/widgets/unit_preview_pane.hpp"
#include "gui/widgets/window.hpp"
#include "marked-up_text.hpp"
#include "help/help.hpp"
#include "gettext.hpp"
#include "replay_helper.hpp"
#include "resources.hpp"
#include "synced_context.hpp"
#include "team.hpp"
#include "units/types.hpp"
#include "units/unit.hpp"
#include "units/ptr.hpp"
#include "utils/functional.hpp"
static lg::log_domain log_display("display");
#define LOG_DP LOG_STREAM(info, log_display)
namespace gui2
{
REGISTER_DIALOG(unit_recall)
tunit_recall::tunit_recall(recalls_ptr_vector& recall_list, team& team)
: recall_list_(recall_list)
, team_(team)
, selected_index_()
, filter_options_()
, last_words_()
{
}
template<typename T>
static void dump_recall_list_to_console(const T& units)
{
log_scope2(log_display, "dump_recall_list_to_console()")
LOG_DP << "size: " << units.size() << "\n";
size_t idx = 0;
for(const unit_const_ptr& u_ptr : units) {
LOG_DP << "\tunit[" << (idx++) << "]: " << u_ptr->id() << " name = '" << u_ptr->name() << "'\n";
}
}
static std::string format_level_string(const int level)
{
std::string lvl = std::to_string(level);
if(level < 1) {
return "<span color='#969696'>" + lvl + "</span>";
} else if(level == 1) {
return lvl;
} else if(level == 2) {
return "<b>" + lvl + "</b>";
} else if(level > 2 ) {
return"<b><span color='#ffffff'>" + lvl + "</span></b>";
}
return lvl;
}
static std::string format_cost_string(int unit_recall_cost, const int team_recall_cost)
{
std::stringstream str;
if(unit_recall_cost < 0) {
unit_recall_cost = team_recall_cost;
}
if(unit_recall_cost > team_recall_cost) {
str << "<span color='#ff0000'>" << unit_recall_cost << "</span>";
} else if(unit_recall_cost == team_recall_cost) {
str << unit_recall_cost;
} else if(unit_recall_cost < team_recall_cost) {
str << "<span color='#00ff00'>" << unit_recall_cost << "</span>";
}
return str.str();
}
static std::string tstr_key(unit_const_ptr u, const t_string& (unit::* fcn)() const)
{
return (u.get()->*fcn)().str();
}
static std::string trait_key(unit_const_ptr u)
{
return !u->trait_names().empty() ? u->trait_names().front().str() : "";
}
template<typename Fnc>
void tunit_recall::init_sorting_option(std::vector<tgenerator_::torder_func>& order_funcs, Fnc filter_on)
{
order_funcs[0] = [this, filter_on](unsigned i1, unsigned i2) {
return filter_on((*recall_list_)[i1]) < filter_on((*recall_list_)[i2]);
};
order_funcs[1] = [this, filter_on](unsigned i1, unsigned i2) {
return filter_on((*recall_list_)[i1]) > filter_on((*recall_list_)[i2]);
};
}
static std::string get_title_suffix(int side_num)
{
if(!resources::teams || !resources::units) {
return "";
}
unit_map& units = *resources::units;
int controlled_recruiters = 0;
for(const auto& team : *resources::teams) {
if(team.is_local_human() && !team.recruits().empty() && units.find_leader(team.side()) !=units.end()) {
++controlled_recruiters;
}
}
std::stringstream msg;
if(controlled_recruiters >= 2) {
unit_map::const_iterator leader = resources::units->find_leader(side_num);
if(leader != resources::units->end() && !leader->name().empty()) {
msg << " (" << leader->name(); msg << ")";
}
}
return msg.str();
}
void tunit_recall::pre_show(twindow& window)
{
tlabel& title = find_widget<tlabel>(&window, "title", true);
title.set_label(title.label() + get_title_suffix(team_.side()));
ttext_box* filter
= find_widget<ttext_box>(&window, "filter_box", false, true);
filter->set_text_changed_callback(
std::bind(&tunit_recall::filter_text_changed, this, _1, _2));
window.keyboard_capture(filter);
tlistbox& list = find_widget<tlistbox>(&window, "recall_list", false);
#ifdef GUI2_EXPERIMENTAL_LISTBOX
connect_signal_notify_modified(*list,
std::bind(&tunit_recall::list_item_clicked,
*this, std::ref(window)));
#else
list.set_callback_value_change(
dialog_callback<tunit_recall, &tunit_recall::list_item_clicked>);
#endif
list.clear();
connect_signal_mouse_left_click(
find_widget<tbutton>(&window, "dismiss", false),
std::bind(&tunit_recall::dismiss_unit, this, std::ref(window)));
connect_signal_mouse_left_click(
find_widget<tbutton>(&window, "show_help", false),
std::bind(&tunit_recall::show_help, this, std::ref(window)));
for(const unit_const_ptr& unit : *recall_list_) {
std::map<std::string, string_map> row_data;
string_map column;
std::string mods
= "~RC(" + unit->team_color() + ">" + team::get_side_color_index(unit->side()) + ")";
if(unit->can_recruit()) {
mods += "~BLIT(" + unit::leader_crown() + ")";
}
for(const std::string& overlay : unit->overlays()) {
mods += "~BLIT(" + overlay + ")";
}
column["label"] = unit->absolute_image() + mods;
row_data.insert(std::make_pair("unit_image", column));
column["label"] = unit->type_name();
row_data.insert(std::make_pair("unit_type", column));
column["label"] = format_cost_string(unit->recall_cost(), team_.recall_cost());
column["use_markup"] = "true";
row_data.insert(std::make_pair("unit_recall_cost", column));
const std::string& name = !unit->name().empty() ? unit->name().str() : utils::unicode_en_dash;
column["label"] = name;
row_data.insert(std::make_pair("unit_name", column));
column["label"] = format_level_string(unit->level());
row_data.insert(std::make_pair("unit_level", column));
std::stringstream exp_str;
exp_str << font::span_color(unit->xp_color()) << unit->experience() << "/"
<< (unit->can_advance() ? std::to_string(unit->max_experience()) : utils::unicode_en_dash) << "</span>";
column["label"] = exp_str.str();
row_data.insert(std::make_pair("unit_experience", column));
// Since the table widgets use heavy formatting, we save a bare copy
// of certain options to filter on.
std::string filter_text = unit->type_name() + " " + name + " " + std::to_string(unit->level());
std::string traits;
for(const std::string& trait : unit->trait_names()) {
traits += (traits.empty() ? "" : "\n") + trait;
filter_text += " " + trait;
}
column["label"] = !traits.empty() ? traits : utils::unicode_en_dash;
row_data.insert(std::make_pair("unit_traits", column));
list.add_row(row_data);
filter_options_.push_back(filter_text);
}
std::vector<tgenerator_::torder_func> order_funcs(2);
init_sorting_option(order_funcs, std::bind(&tstr_key, _1, &unit::type_name));
list.set_column_order(0, order_funcs);
init_sorting_option(order_funcs, std::bind(&tstr_key, _1, &unit::name));
list.set_column_order(1, order_funcs);
init_sorting_option(order_funcs, std::bind(&unit::level, _1));
list.set_column_order(2, order_funcs);
init_sorting_option(order_funcs, std::bind(&unit::experience, _1));
list.set_column_order(3, order_funcs);
init_sorting_option(order_funcs, std::bind(&trait_key, _1));
list.set_column_order(4, order_funcs);
list_item_clicked(window);
}
void tunit_recall::dismiss_unit(twindow& window)
{
LOG_DP << "Recall list units:\n"; dump_recall_list_to_console(*recall_list_);
tlistbox& list = find_widget<tlistbox>(&window, "recall_list", false);
const int index = list.get_selected_row();
const unit& u = *recall_list_->at(index);
// If the unit is of level > 1, or is close to advancing, we warn the player about it
std::stringstream message;
if(u.loyal()) {
message << _("This unit is loyal and requires no upkeep.") << " " << (u.gender() == unit_race::MALE
? _("Do you really want to dismiss him?")
: _("Do you really want to dismiss her?"));
} else if(u.level() > 1) {
message << _("This unit is an experienced one, having advanced levels.") << " " << (u.gender() == unit_race::MALE
? _("Do you really want to dismiss him?")
: _("Do you really want to dismiss her?"));
} else if(u.experience() > u.max_experience()/2) {
message << _("This unit is close to advancing a level.") << " " << (u.gender() == unit_race::MALE
? _("Do you really want to dismiss him?")
: _("Do you really want to dismiss her?"));
}
if(!message.str().empty()) {
const int res = gui2::show_message(window.video(), _("Dismiss Unit"), message.str(), gui2::tmessage::yes_no_buttons);
if(res != gui2::twindow::OK) {
return;
}
}
*recall_list_->erase(recall_list_->begin() + index);
// Remove the entry from the dialog list
list.remove_row(index);
list_item_clicked(window);
// Remove the entry from the filter list
filter_options_.erase(filter_options_.begin() + index);
assert(filter_options_.size() == list.get_item_count());
LOG_DP << "Dismissing a unit, side = " << u.side() << ", id = '" << u.id() << "'\n";
LOG_DP << "That side's recall list:\n";
dump_recall_list_to_console(team_.recall_list());
// Find the unit in the recall list.
unit_ptr dismissed_unit = team_.recall_list().find_if_matches_id(u.id());
assert(dismissed_unit);
// Record the dismissal, then delete the unit.
synced_context::run_and_throw("disband", replay_helper::get_disband(dismissed_unit->id()));
// Close the dialog if all units are dismissed
if(list.get_item_count() == 0) {
window.set_retval(twindow::CANCEL);
}
}
void tunit_recall::show_help(twindow& window)
{
help::show_help(window.video(), "recruit_and_recall");
}
void tunit_recall::list_item_clicked(twindow& window)
{
const int selected_row
= find_widget<tlistbox>(&window, "recall_list", false).get_selected_row();
if(selected_row == -1) {
return;
}
find_widget<tunit_preview_pane>(&window, "unit_details", false)
.set_displayed_unit(recall_list_->at(selected_row).get());
}
void tunit_recall::post_show(twindow& window)
{
if(get_retval() == twindow::OK) {
selected_index_ = find_widget<tlistbox>(&window, "recall_list", false)
.get_selected_row();
}
}
void tunit_recall::filter_text_changed(ttext_* textbox, const std::string& text)
{
twindow& window = *textbox->get_window();
tlistbox& list = find_widget<tlistbox>(&window, "recall_list", false);
const std::vector<std::string> words = utils::split(text, ' ');
if(words == last_words_)
return;
last_words_ = words;
std::vector<bool> show_items(list.get_item_count(), true);
if(!text.empty()) {
for(unsigned int i = 0; i < list.get_item_count(); i++) {
bool found = false;
for(const auto & word : words) {
found = std::search(filter_options_[i].begin(),
filter_options_[i].end(),
word.begin(),
word.end(),
chars_equal_insensitive)
!= filter_options_[i].end();
if(!found) {
// one word doesn't match, we don't reach words.end()
break;
}
}
show_items[i] = found;
}
}
list.set_row_shown(show_items);
}
}

View file

@ -0,0 +1,78 @@
/*
Copyright (C) 2016 by the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#ifndef GUI_DIALOGS_UNIT_RECALL_HPP_INCLUDED
#define GUI_DIALOGS_UNIT_RECALL_HPP_INCLUDED
#include "gui/dialogs/dialog.hpp"
#include "gui/widgets/group.hpp"
#include "gui/widgets/generator.hpp"
#include "units/race.hpp"
#include "units/ptr.hpp"
#include <memory>
#include <string>
#include <vector>
class team;
class unit_type;
namespace gui2
{
class ttext_;
class tunit_recall : public tdialog
{
typedef std::shared_ptr<std::vector<unit_const_ptr> > recalls_ptr_vector;
public:
tunit_recall(recalls_ptr_vector& recall_list, team& team);
int get_selected_index() const
{
return selected_index_;
}
private:
recalls_ptr_vector& recall_list_;
team& team_;
int selected_index_;
std::vector<std::string> filter_options_;
std::vector<std::string> last_words_;
template<typename T>
void init_sorting_option(std::vector<tgenerator_::torder_func>& order_funcs, T filter_on);
/** Callbacks */
void list_item_clicked(twindow& window);
void filter_text_changed(ttext_* textbox, const std::string& text);
void dismiss_unit(twindow& window);
void show_help(twindow& window);
/** Inherited from tdialog, implemented by REGISTER_DIALOG. */
virtual const std::string& window_id() const;
/** Inherited from tdialog. */
void pre_show(twindow& window);
/** Inherited from tdialog. */
void post_show(twindow& window);
};
} // namespace gui2
#endif /* ! GUI_DIALOGS_UNIT_RECALL_HPP_INCLUDED */

View file

@ -25,13 +25,13 @@
#include "gui/widgets/window.hpp"
#include "formula/string_utils.hpp"
#include "game_config.hpp"
#include "gettext.hpp"
#include "help/help.hpp"
#include "marked-up_text.hpp"
#include "play_controller.hpp"
#include "resources.hpp"
#include "team.hpp"
#include "units/attack_type.hpp"
#include "units/types.hpp"
#include "units/unit.hpp"
@ -47,14 +47,15 @@ REGISTER_WIDGET(unit_preview_pane)
void tunit_preview_pane::finalize_setup()
{
// Icons
icon_type_ = find_widget<timage>(this, "type_image" , false, false);
icon_race_ = find_widget<timage>(this, "type_race" , false, false);
icon_alignment_ = find_widget<timage>(this, "type_alignment", false, false);
icon_type_ = find_widget<timage>(this, "type_image" , false, false);
icon_race_ = find_widget<timage>(this, "type_race" , false, false);
icon_alignment_ = find_widget<timage>(this, "type_alignment", false, false);
// Labels
label_name_ = find_widget<tlabel>(this, "type_name" , false, false);
label_level_ = find_widget<tlabel>(this, "type_level" , false, false);
label_details_ = find_widget<tlabel>(this, "type_details", false, false);
label_name_ = find_widget<tlabel>(this, "type_name" , false, false);
label_level_ = find_widget<tlabel>(this, "type_level" , false, false);
label_details_ = find_widget<tlabel>(this, "type_details", false, false);
label_details_minimal_ = find_widget<tlabel>(this, "type_details_minimal", false, false);
// Profile button
button_profile_ = find_widget<tbutton>(this, "type_profile", false, false);
@ -65,6 +66,33 @@ void tunit_preview_pane::finalize_setup()
}
}
/*
* Both unit and unit_type use the same format (vector of attack_types) for their
* attack data, meaning we can keep this as a helper function.
*/
void tunit_preview_pane::print_attack_details(const std::vector<attack_type>& attacks, std::stringstream& str)
{
str << "<b>" << _("Attacks") << "</b>" << "\n";
for(const auto& a : attacks) {
str << "<span color='#f5e6c1'>" << a.damage()
<< font::weapon_numbers_sep << a.num_attacks() << " " << a.name() << "</span>" << "\n";
str << "<span color='#a69275'>" << " " << a.range()
<< font::weapon_details_sep << a.type() << "</span>" << "\n";
const std::string special = a.weapon_specials();
if (!special.empty()) {
str << "<span color='#a69275'>" << " " << special << "</span>" << "\n";
}
const std::string accuracy_parry = a.accuracy_parry_description();
if(!accuracy_parry.empty()) {
str << "<span color='#a69275'>" << " " << accuracy_parry << "</span>" << "\n";
}
}
}
void tunit_preview_pane::set_displayed_type(const unit_type* type)
{
// Sets the current type id for the profile button callback to use
@ -122,14 +150,11 @@ void tunit_preview_pane::set_displayed_type(const unit_type* type)
str << "<b>" << _("MP: ") << "</b>"
<< type->movement() << "\n";
str << " \n";
str << "\n";
// Print trait details
bool has_traits = false;
std::stringstream t_str;
for(const auto& tr : type->possible_traits())
{
for(const auto& tr : type->possible_traits()) {
if(tr["availability"] != "musthave") continue;
const std::string gender_string =
@ -143,50 +168,28 @@ void tunit_preview_pane::set_displayed_type(const unit_type* type)
if(!name.empty()) {
t_str << " " << name << "\n";
}
has_traits = true;
}
if(has_traits) {
if(!t_str.str().empty()) {
str << "<b>" << _("Traits") << "</b>" << "\n";
str << t_str.str();
str << " \n";
str << "\n";
}
// Print ability details
if(!type->abilities().empty()) {
str << "<b>" << _("Abilities") << "</b>" << "\n";
for(const auto& ab : type->abilities())
{
for(const auto& ab : type->abilities()) {
str << " " << ab << "\n";
}
str << " \n";
str << "\n";
}
// Print attack details
if(!type->attacks().empty()) {
str << "<b>" << _("Attacks") << "</b>" << "\n";
for(const auto& a : type->attacks())
{
str << "<span color='#f5e6c1'>" << a.damage()
<< font::weapon_numbers_sep << a.num_attacks() << " " << a.name() << "</span>" << "\n";
str << "<span color='#a69275'>" << " " << a.range()
<< font::weapon_details_sep << a.type() << "</span>" << "\n";
const std::string special = a.weapon_specials();
if (!special.empty()) {
str << "<span color='#a69275'>" << " " << special << "</span>" << "\n";
}
const std::string accuracy_parry = a.accuracy_parry_description();
if(!accuracy_parry.empty()) {
str << "<span color='#a69275'>" << " " << accuracy_parry << "</span>" << "\n";
}
}
print_attack_details(type->attacks(), str);
}
label_details_->set_label(str.str());
@ -217,7 +220,14 @@ void tunit_preview_pane::set_displayed_unit(const unit* unit)
}
if(label_name_) {
label_name_->set_label("<big>" + unit->type_name() + "</big>");
std::string name;
if(!unit->name().empty()) {
name = "<span size='large'>" + unit->name() + "</span>" + "\n" + "<small><span color='#a69275'>" + unit->type_name() + "</span></small>";
} else {
name = "<span size='large'>" + unit->type_name() + "</span>\n";
}
label_name_->set_label(name);
label_name_->set_use_markup(true);
}
@ -245,31 +255,19 @@ void tunit_preview_pane::set_displayed_unit(const unit* unit)
unit->gender()));
}
// For this, we want to display certain info in the details label if the corresponding
// widgets aren't present in the definiton.
if(label_details_) {
if(label_details_minimal_) {
std::stringstream str;
str << "<small>";
if(!label_name_) {
const std::string name = "<span size='large'>" + (!unit->name().empty() ? unit->name() : " ") + "</span>";
str << name << "\n";
}
const std::string name = "<span size='large'>" + (!unit->name().empty() ? unit->name() : " ") + "</span>";
str << name << "\n";
if(!icon_type_) {
str << "<span color='#f5e6c1'>" << unit->type_name() << "</span>" << "\n";
}
str << "<span color='#f5e6c1'>" << unit->type_name() << "</span>" << "\n";
if(!label_level_) {
str << "Lvl " << unit->level() << "\n";
}
str << "Lvl " << unit->level() << "\n";
if(!icon_alignment_) {
str << unit->alignment() << "\n";
}
str << unit->alignment() << "\n";
std::string traits;
for(const std::string& trait : unit->trait_names()) {
traits += (traits.empty() ? "" : ", ") + trait;
}
@ -286,14 +284,50 @@ void tunit_preview_pane::set_displayed_unit(const unit* unit)
str << font::span_color(unit->xp_color())
<< _("XP: ") << unit->experience() << "/" << unit->max_experience() << "</span>";
// TODO: enable
//str << "\n"
// << _("MP: ") << unit->movement_left() << "/" << unit->total_movement();
label_details_minimal_->set_label(str.str());
label_details_minimal_->set_use_markup(true);
}
if(label_details_) {
std::stringstream str;
str << "<small>";
str << "<b>" << _("HP: ") << "</b>"
<<font::span_color(unit->hp_color()) << unit->hitpoints() << "/" << unit->max_hitpoints() << "</span>" << " | ";
str << "<b>" << _("XP: ") << "</b>"
<< font::span_color(unit->xp_color()) << unit->experience() << "/" << unit->max_experience() << "</span>" << " | ";
str << "<b>" << _("MP: ") << "</b>"
<< unit->movement_left() << "/" << unit->total_movement();
str << "</small>";
str << "\n\n";
// TODO: add abilty and attack printouts. Currently the only usecase of a unit display
// (the attack dialog) doesn't need these.
// Print trait details
if(!unit->trait_names().empty()) {
str << "<b>" << _("Traits") << "</b>" << "\n";
for(const auto& trait : unit->trait_names()) {
str << " " << trait << "\n";
}
str << "\n";
}
if(!unit->get_ability_list().empty()) {
str << "<b>" << _("Abilities") << "</b>" << "\n";
for(const auto& ab : unit->get_ability_list()) {
str << " " << ab << "\n";
}
str << "\n";
}
if(!unit->attacks().empty()) {
print_attack_details(unit->attacks(), str);
}
label_details_->set_label(str.str());
label_details_->set_use_markup(true);

View file

@ -18,6 +18,7 @@
#include <string>
class attack_type;
class unit;
class unit_type;
@ -49,6 +50,7 @@ public:
, label_name_(nullptr)
, label_level_(nullptr)
, label_details_(nullptr)
, label_details_minimal_(nullptr)
, button_profile_(nullptr)
, image_mods_()
{
@ -86,18 +88,21 @@ protected:
private:
std::string current_type_;
timage* icon_type_;
timage* icon_race_;
timage* icon_alignment_;
timage* icon_type_;
timage* icon_race_;
timage* icon_alignment_;
tlabel* label_name_;
tlabel* label_level_;
tlabel* label_details_;
tlabel* label_name_;
tlabel* label_level_;
tlabel* label_details_;
tlabel* label_details_minimal_;
tbutton* button_profile_;
std::string image_mods_;
void print_attack_details(const std::vector<attack_type>& attacks, std::stringstream& str);
enum tstate {
ENABLED
};

View file

@ -52,6 +52,7 @@
#include "gui/dialogs/simple_item_selector.hpp"
#include "gui/dialogs/edit_text.hpp"
#include "gui/dialogs/unit_create.hpp"
#include "gui/dialogs/unit_recall.hpp"
#include "gui/dialogs/unit_recruit.hpp"
#include "gui/widgets/settings.hpp"
#include "gui/widgets/window.hpp"
@ -640,16 +641,22 @@ void menu_handler::recall(int side_num, const map_location &last_hex)
return;
}
int res = dialogs::recall_dialog(*gui_, recall_list_team, side_num, get_title_suffix(side_num), current_team.recall_cost());
int unit_cost = current_team.recall_cost();
if (res < 0) {
gui2::tunit_recall dlg(recall_list_team, current_team);
dlg.show(gui_->video());
if(dlg.get_retval() != gui2::twindow::OK) {
return;
}
int res = dlg.get_selected_index();
int unit_cost = current_team.recall_cost();
// we need to check if unit has a specific recall cost
// if it does we use it elsewise we use the team.recall_cost()
// the magic number -1 is what it gets set to if the unit doesn't
// have a special recall_cost of its own.
else if(recall_list_team->at(res)->recall_cost() > -1) {
if(recall_list_team->at(res)->recall_cost() > -1) {
unit_cost = recall_list_team->at(res)->recall_cost();
}
@ -686,8 +693,7 @@ void menu_handler::recall(int side_num, const map_location &last_hex)
true,
synced_context::ignore_error_function);
if(!success)
{
if(!success) {
ERR_NG << "menu_handler::recall(): Unit does not exist in the recall list." << std::endl;
}
}