diff --git a/doc/design/gui2.tex b/doc/design/gui2.tex index de7c31ba511..28271eb8268 100644 --- a/doc/design/gui2.tex +++ b/doc/design/gui2.tex @@ -587,7 +587,7 @@ which all widgets already exist this doesn't matter and you can directly dive into generating the dialogue. \section{Creating the widget} -\label{creating_the_widget} +\label{sec:creating_the_widget} The widget is normally split in 7 different files. Three .cpp/.hpp files and one WML file. Usually I start to create the new files by simply copy pasting the old @@ -828,22 +828,146 @@ it was done in one fell swoop. This completes the simple progress bar widget. Of course we haven't tested it yet since there's no dialogue to test it in. So let's implement a dialogue. -\begin{comment} \section{Creating the window} +\label{sec:creating_the_window} The window is normally split in 3 files. One .cpp/.hpp file and one WML file. -The dialogue used is the load progress dialogue, this is not the greatest -example dialogue, so this section may later use another dialogue as example. +The dialogue used is the unit attack dialogue, at least an initial version. This +version barely does what it needs to do, and the in game version will be more +polished. This simple version is nice as example here since it's not too large. + +Now we start to explain what the various files do, first a general overview of +the files after which I dive more into the implementation of the file. + +\begin{description} + +\item[src/gui/dialogs/unit\_attack.*pp] + These files contain the code of the dialogue. This code `binds' the WML + code and allows the C++ code to show the dialogue show the right data at the + right place. In several cases this also allows the designer of the WML file + to decide which widget s/he picks. + +\begin{description} +\item[hpp] Listing \ref{unit_attack.hpp} contains the sample code. + \begin{description} + \item[Line \ref{unit_attack.hpp:class}] + Defines the class itself, which normally inherits from tdialog. + Sometimes a class inherits from another class, but that class then +derives from tdialog, for example the wml\_dialogs have a common base and +separate classes for left and right. + + \item[Line \ref{unit_attack.hpp:constructor}] + Dialogues are often created and then shown once before being destroyed, +therefore the constructor often takes all parameters needed. Since the show +function shouldn't be overloaded the only other alternative would be member +functions to set the members. Note when keeping `references' to widgets these +member must be pointers and set to NULL in the constructor, this due to the +simple fact that the widgets aren't created at this point. Also keep in mind +when +the dialogue is shown twice (using the \emph{same} object) the widgets won't be +the same. The window owning the widgets is destroyed after being shown and +a new one created upon the next call to show. + + \item[Line \ref{unit_attack.hpp:settersgetters}] + As said before there are often no setters, since all is set in the +contructor. Most members also remain hidden since the caller has the +information. Only members that change are `exported'. The most common example in +a class with a listbox, the last selected item is made public, since this is +often used as choice the user made. + + \item[Line \ref{unit_attack.hpp:window_id}] + This function is part of the window registration and only needs to be +declared in the header, the definition will be provided by a macro. + + \item[Line \ref{unit_attack.hpp:pre_show}] + This function is called after the window is created but before being +shown. This is the point to set the members that point to a widget. As said +before every time the dialogue is shown a newly created window is shown, so +setting the pointers can be unconditional. This is also the place to fill the +widgets with the proper content, if needed. Eg filling the languages in the +language selection dialogue. + + \item[Line \ref{unit_attack.hpp:post_show}] + Called after the dialogue is shown, this allows you to update certain +statuses. E.g. if the dialogue is closed with the OK button set the selected +language to the newly selected language. You can also update it unconditionally +and let the caller test the status. + + \item[Line \ref{unit_attack.hpp:member}] + At this point the list of private members start. + + \end{description} + +\item[cpp] Listing \ref{unit_attack.cpp} contains the sample code. Note some +parts are already described more verbose in the header and thus not mentioned +here. + \begin{description} + \item[Line \ref{unit_attack.cpp:textdomain}] + Like written for the widget, every file is inside the `wesnoth-lib' text +domain. + + \item[Line \ref{unit_attack.cpp:wiki}] + Directly after the includes (inside the gui2 namespace) the wiki +documentation starts. + + + \item[Line \ref{unit_attack.cpp:register}] + After the wiki documentation the registration code follows, it defines +the window\_id function and does some other registration parts. + + \item[Line \ref{unit_attack.cpp:pre_show}] + The pre\_show is already mentioned in the header file and it the one +used here uses several static helper functions to do it's job. + + \end{description} + +\end{description} + +\item[src/gui/dialogs/unit\_attack.cfg] + This file contains the WML that defines the widget. It should honour the +restrains set in the wiki comment like \ref{unit_attack.cpp:wiki}. These +constrains set the minimum set of needed widgets and their types, and mentions +the optional widgets. How the widgets are placed and whether only the optional +ones are set is up to the designer of the dialog. The gui toolkit wiki has more +information on the subject. Still some parts are worth mentioning. + + \begin{description} + \item[Line \ref{unit_attack.cfg:textdomain}] + Every file in the library is inside the ``wesnoth-lib'' text domain. + + \item[Line \ref{unit_attack.cfg:defines}] + With larger dialogs I often design them on paper and then divide the +dialog in several sections. These sections often are turned into defines in +order to keep the main dialog rather simple. WML tends to be verbose and deeply +nested, and the GUI's also tend to have a lot of nested items, these powers +combined result in deeply nested structure, where one can easily lose the way. +Also not that all local macros are prefixed with \_GUI, this to avoid clashes +with other macros. The BIG in the names are just because I want to make a +different definition of high resolution screens (this will be the first screen +using that feature, but that will be added later). + + \item[Line \ref{unit_attack.cfg:window}] + Not much to tell about this part, since it's all documented in the wiki. +But do note that due to the defines above the grid in the window itself is +rather simple. + + \item[Line \ref{unit_attack.cfg:undefines}] + The local macros are no longer needed so undefine them, I normally +undefine them in the oposite order of the definitions, I'm quite sure that's not +actually needed, but it feels the right way\texttrademark. + + \end{description} + +\end{description} -\end{comment} \appendix \chapter{Files for creating the widget} -This chapter contains the files created in \S~\ref{creating_the_widget}, these +This chapter contains the files created in \S~\ref{sec:creating_the_widget}, these files aren't the real files added, but a slightly modified version; The copyright headers are stripped to avoid taking up useless space. Some extra comments are added to make referencing possible. This also means the files here @@ -891,5 +1015,28 @@ below. , label=progress_bar.cpp] {gui2/progress_bar.cpp} + +\chapter{Files for creating the window} + +This chapter like the previous one contains listings of files, this time for +\S~\ref{sec:creating_the_window}. About the same is true for these files, except +we know the originals will change in the future. + +\lstinputlisting[style=C++ + , caption={src/gui/dialogs/unit\_attack.hpp} + , label=unit_attack.hpp] + {gui2/unit_attack.hpp} + +\lstinputlisting[style=C++ + , caption={src/gui/dialogs/unit\_attack.cpp} + , label=unit_attack.cpp] + {gui2/unit_attack.cpp} + +\lstinputlisting[style=WML + , caption={src/gui/dialogs/unit\_attack.cfg} + , label=unit_attack.cfg] + {gui2/unit_attack.cfg} + + \end{document} diff --git a/doc/design/gui2/unit_attack.cfg b/doc/design/gui2/unit_attack.cfg new file mode 100644 index 00000000000..eddbe48e5c6 --- /dev/null +++ b/doc/design/gui2/unit_attack.cfg @@ -0,0 +1,299 @@ +#textdomain wesnoth-lib /*@ \label{unit_attack.cfg:textdomain} @*/ +### +### Definition of the window select which unit to attack +### + +#define _GUI_BIG_ATTACKER_PANEL /*@ \label{unit_attack.cfg:defines} @*/ +[grid] + id = "attacker" + linked_group = "unit" + + [row] + + [column] + border = "all" + border_size = 5 + + [image] + id = "attacker_portrait" + [/image] + + [/column] + + [column] + grow_factor = 1 + horizontal_grow = "true" + + border = "all" + border_size = 5 + + [label] + id = "attacker_name" + definition = "alignment" + [/label] + + [/column] + + [/row] + +[/grid] +#enddef + +#define _GUI_BIG_DEFENDER_PANEL +[grid] + id = "defender" + linked_group = "unit" + + [row] + + [column] + grow_factor = 1 + horizontal_grow = "true" + + border = "all" + border_size = 5 + + [label] + id = "defender_name" + definition = "alignment" + + text_alignment = "right" + [/label] + + [/column] + + [column] + border = "all" + border_size = 5 + + [image] + id = "defender_portrait" + [/image] + + [/column] + + [/row] + +[/grid] +#enddef + +#define _GUI_BIG_UNIT_PANEL +[grid] + + [row] + + [column] + {_GUI_BIG_ATTACKER_PANEL} + [/column] + + [column] + {_GUI_BIG_DEFENDER_PANEL} + [/column] + + [/row] + +[/grid] +#enddef + +#define _GUI_BIG_WEAPON_PANEL +[grid] + + [row] + + [column] + horizontal_grow = "true" + + [listbox] + id = "weapon_list" + definition = "default" + + [list_definition] + + [row] + + [column] + vertical_grow = "true" + horizontal_grow = "true" + + [toggle_panel] + definition = "default" + + return_value_id = "ok" + [grid] + + [row] + + [column] + grow_factor = 1 + horizontal_grow = "true" + + border = "all" + border_size = 5 + + [label] + id = "attacker_weapon" + definition = "alignment" + linked_group = "weapon" + [/label] + + [/column] + + [column] + grow_factor = 1 + horizontal_grow = "true" + + border = "all" + border_size = 5 + + [label] + id = "defender_weapon" + definition = "alignment" + linked_group = "weapon" + + text_alignment = "right" + [/label] + + [/column] + + [/row] + + [/grid] + + [/toggle_panel] + + [/column] + + [/row] + + [/list_definition] + + [/listbox] + + [/column] + + [/row] + +[/grid] +#enddef + +#define _GUI_BUTTON_ROW +[grid] + + [row] + + [column] + grow_factor = 1 + + border = "all" + border_size = 5 + horizontal_alignment = "right" + + [button] + id = "ok" + definition = "default" + + label = _ "Attack" + [/button] + + [/column] + + [column] + border = "all" + border_size = 5 + + [button] + id = "cancel" + definition = "default" + + label = _ "Cancel" + [/button] + + [/column] + + [/row] + +[/grid] +#enddef + +[window] /*@ \label{unit_attack.cfg:window} @*/ + id = "unit_attack" + description = "Unit attack dialog." + + [resolution] + definition = "default" + + automatic_placement = "true" + vertical_placement = "center" + horizontal_placement = "center" + + # Both unit panels are the same width. + [linked_group] + id = "unit" + fixed_width = "true" + [/linked_group] + + # All weapons share the same size, regardless whether attacker or + # defender. + [linked_group] + id = "weapon" + fixed_width = "true" + [/linked_group] + + [grid] + + [row] + + [column] + grow_factor = 1 + + border = "all" + border_size = 5 + horizontal_alignment = "left" + [label] + definition = "title" + + label = _ "Attack enemy" + [/label] + + [/column] + + [/row] + + [row] + + [column] + horizontal_grow = "true" + {_GUI_BIG_UNIT_PANEL} + [/column] + + [/row] + + [row] + + [column] + horizontal_grow = "true" + {_GUI_BIG_WEAPON_PANEL} + [/column] + + [/row] + + [row] + + [column] + horizontal_grow = "true" + {_GUI_BUTTON_ROW} + [/column] + + [/row] + + [/grid] + + [/resolution] + +[/window] + +#undef _GUI_BUTTON_ROW /*@ \label{unit_attack.cfg:undefines} @*/ +#undef _GUI_BIG_WEAPON_PANEL +#undef _GUI_BIG_UNIT_PANEL +#undef _GUI_BIG_DEFENDER_PANEL +#undef _GUI_BIG_ATTACKER_PANEL diff --git a/doc/design/gui2/unit_attack.cpp b/doc/design/gui2/unit_attack.cpp new file mode 100644 index 00000000000..829aab94c3e --- /dev/null +++ b/doc/design/gui2/unit_attack.cpp @@ -0,0 +1,138 @@ +#define GETTEXT_DOMAIN "wesnoth-lib" /*@ \label{unit_attack.cpp:textdomain} @*/ + +#include "gui/dialogs/unit_attack.hpp" + +#include "gui/widgets/image.hpp" +#include "gui/widgets/listbox.hpp" +#include "gui/widgets/settings.hpp" +#include "gui/widgets/window.hpp" +#include "unit.hpp" + +namespace gui2 { + +/*@ \label{unit_attack.cpp:wiki} @*/ /*WIKI + * @page = GUIWindowDefinitionWML + * @order = 2_unit_attack + * + * == Unit attack == + * + * This shows the dialog for attacking units. + * + * @start_table = grid + * + * attacker_portrait (image) Shows the portrait of the attacking unit. + * attacker_icon (image) Shows the icon of the attacking unit. + * attacker_name (control) Shows the name of the attacking unit. + * + * + * defender_portrait (image) Shows the portrait of the defending unit. + * defender_icon (image) Shows the icon of the defending unit. + * defender_name (control) Shows the name of the defending unit. + * + * + * (weapon_list) (listbox) The list with weapons to choos from. + * -[attacker_weapon] (control) The weapon for the attacker to use. + * -[defender_weapon] (control) The weapon for the defender to use. + * + * @end_table + */ + +REGISTER_WINDOW(unit_attack) /*@ \label{unit_attack.cpp:register} @*/ + +tunit_attack::tunit_attack( + const unit_map::iterator& attacker_itor + , const unit_map::iterator& defender_itor + , const std::vector& weapons + , const int best_weapon) + : selected_weapon_(-1) + , attacker_itor_(attacker_itor) + , defender_itor_(defender_itor) + , weapons_(weapons) + , best_weapon_(best_weapon) +{ +} + +template +static void set_label( + twindow& window + , const std::string& id + , const std::string& label) +{ + T* widget = find_widget(&window, id, false, false); + if(widget) { + widget->set_label(label); + } +} + +static void set_attacker_info(twindow& w, unit& u) +{ + set_label(w, "attacker_portrait", u.absolute_image()); + set_label(w, "attacker_icon", u.absolute_image()); + set_label(w, "attacker_name", u.name()); +} + +static void set_defender_info(twindow& w, unit& u) +{ + set_label(w, "defender_portrait", u.absolute_image()); + set_label(w, "defender_icon", u.absolute_image()); + set_label(w, "defender_name", u.name()); +} + +static void set_weapon_info(twindow& window + , const std::vector& weapons + , const int best_weapon) +{ + tlistbox& weapon_list = + find_widget(&window, "weapon_list", false); + window.keyboard_capture(&weapon_list); + + const config empty; + attack_type no_weapon(empty); + + for (size_t i = 0; i < weapons.size(); ++i) { + const battle_context::unit_stats& attacker = + weapons[i].get_attacker_stats(); + + const battle_context::unit_stats& defender = + weapons[i].get_defender_stats(); + + const attack_type& attacker_weapon = attack_type(*attacker.weapon); + const attack_type& defender_weapon = attack_type( + defender.weapon ? *defender.weapon : no_weapon); + + std::map data; + string_map item; + + item["label"] = attacker_weapon.name(); + data.insert(std::make_pair("attacker_weapon", item)); + + item["label"] = defender_weapon.name(); + data.insert(std::make_pair("defender_weapon", item)); + + weapon_list.add_row(data); + + } + + assert(best_weapon < static_cast(weapon_list.get_item_count())); + weapon_list.select_row(best_weapon); +} + +void tunit_attack::pre_show(CVideo& /*video*/, twindow& window) /*@ \label{unit_attack.cpp:pre_show} @*/ +{ + set_attacker_info(window, *attacker_itor_); + set_defender_info(window, *defender_itor_); + + selected_weapon_ = -1; + set_weapon_info(window, weapons_, best_weapon_); +} + +void tunit_attack::post_show(twindow& window) +{ + if(get_retval() == twindow::OK) { + selected_weapon_ = find_widget(&window, "weapon_list", false) + .get_selected_row(); + } +} + +} // namespace gui2 + diff --git a/doc/design/gui2/unit_attack.hpp b/doc/design/gui2/unit_attack.hpp new file mode 100644 index 00000000000..129197b017d --- /dev/null +++ b/doc/design/gui2/unit_attack.hpp @@ -0,0 +1,54 @@ +#ifndef GUI_DIALOGS_UNIT_ATTACK_HPP_INCLUDED +#define GUI_DIALOGS_UNIT_ATTACK_HPP_INCLUDED + +#include "actions.hpp" +#include "gui/dialogs/dialog.hpp" +#include "unit_map.hpp" + +namespace gui2 { + +class tunit_attack /*@ \label{unit_attack.hpp:class} @*/ + : public tdialog +{ +public: + tunit_attack( /*@ \label{unit_attack.hpp:constructor} @*/ + const unit_map::iterator& attacker_itor + , const unit_map::iterator& defender_itor + , const std::vector& weapons + , const int best_weapon); + + /***** ***** ***** setters / getters for members ***** ****** *****/ /*@ \label{unit_attack.hpp:settersgetters} @*/ + + int get_selected_weapon() const { return selected_weapon_; } + +private: + + /** Inherited from tdialog, implemented by REGISTER_WINDOW. */ /*@ \label{unit_attack.hpp:member} @*/ + virtual const std::string& window_id() const; + + /** Inherited from tdialog. */ /*@ \label{unit_attack.hpp:pre_show} @*/ + void pre_show(CVideo& video, twindow& window); + + /** Inherited from tdialog. */ /*@ \label{unit_attack.hpp:post_show} @*/ + void post_show(twindow& window); +/*@ \label{unit_attack.hpp:members} @*/ + /** The index of the selected weapon. */ + int selected_weapon_; + + /** Iterator pointing to the attacker. */ + unit_map::iterator attacker_itor_; + + /** Iterator pointing to the defender. */ + unit_map::iterator defender_itor_; + + /** List of all battle contexts used for getting the weapons. */ + std::vector weapons_; + + /** The best weapon, aka the one high-lighted. */ + int best_weapon_; +}; + +} // namespace gui2 + +#endif +