Removed GUI1 Help interface code
Not needed anymore since we're moving to GUI2 for this dialog.
This commit is contained in:
parent
1e96d9c87b
commit
5022a38608
8 changed files with 0 additions and 1391 deletions
|
@ -268,10 +268,7 @@ gui/widgets/widget.cpp
|
||||||
gui/widgets/widget_helpers.cpp
|
gui/widgets/widget_helpers.cpp
|
||||||
halo.cpp
|
halo.cpp
|
||||||
help/help.cpp
|
help/help.cpp
|
||||||
help/help_browser.cpp
|
|
||||||
help/help_impl.cpp
|
help/help_impl.cpp
|
||||||
help/help_menu.cpp
|
|
||||||
help/help_text_area.cpp
|
|
||||||
help/help_topic_generators.cpp
|
help/help_topic_generators.cpp
|
||||||
hotkey/hotkey_handler.cpp
|
hotkey/hotkey_handler.cpp
|
||||||
hotkey/hotkey_handler_mp.cpp
|
hotkey/hotkey_handler_mp.cpp
|
||||||
|
|
|
@ -29,7 +29,6 @@
|
||||||
#include "gui/dialogs/help_browser.hpp"
|
#include "gui/dialogs/help_browser.hpp"
|
||||||
#include "gui/dialogs/transient_message.hpp"
|
#include "gui/dialogs/transient_message.hpp"
|
||||||
#include "gui/widgets/settings.hpp"
|
#include "gui/widgets/settings.hpp"
|
||||||
#include "help/help_browser.hpp" // for help_browser
|
|
||||||
#include "help/help_impl.hpp" // for hidden_symbol, toplevel, etc
|
#include "help/help_impl.hpp" // for hidden_symbol, toplevel, etc
|
||||||
#include "key.hpp" // for CKey
|
#include "key.hpp" // for CKey
|
||||||
#include "log.hpp" // for LOG_STREAM, log_domain
|
#include "log.hpp" // for LOG_STREAM, log_domain
|
||||||
|
|
|
@ -1,242 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright (C) 2003 - 2018 by David White <dave@whitevine.net>
|
|
||||||
Part of 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "help/help_browser.hpp"
|
|
||||||
#include <iostream> // for operator<<, basic_ostream, etc
|
|
||||||
#include <SDL_mouse.h> // for SDL_GetMouseState, etc
|
|
||||||
#include "cursor.hpp" // for set, CURSOR_TYPE::HYPERLINK, etc
|
|
||||||
#include "font/constants.hpp" // for relative_size
|
|
||||||
#include "gettext.hpp" // for _
|
|
||||||
#include "gui/dialogs/transient_message.hpp"
|
|
||||||
#include "help/help_text_area.hpp" // for help_text_area
|
|
||||||
#include "help/help_impl.hpp" // for find_topic, hidden_symbol, etc
|
|
||||||
#include "key.hpp" // for CKey
|
|
||||||
#include "log.hpp" // for log_scope
|
|
||||||
#include "sdl/rect.hpp"
|
|
||||||
|
|
||||||
class CVideo;
|
|
||||||
struct SDL_Rect;
|
|
||||||
|
|
||||||
namespace help {
|
|
||||||
|
|
||||||
help_browser::help_browser(CVideo& video, const section &toplevel) :
|
|
||||||
gui::widget(video),
|
|
||||||
menu_(video,
|
|
||||||
toplevel),
|
|
||||||
text_area_(video, toplevel), toplevel_(toplevel),
|
|
||||||
ref_cursor_(false),
|
|
||||||
back_topics_(),
|
|
||||||
forward_topics_(),
|
|
||||||
back_button_(video, "", gui::button::TYPE_PRESS, "button_normal/button_small_H22", gui::button::DEFAULT_SPACE, true, "icons/arrows/long_arrow_ornate_left"),
|
|
||||||
forward_button_(video, "", gui::button::TYPE_PRESS, "button_normal/button_small_H22", gui::button::DEFAULT_SPACE, true, "icons/arrows/long_arrow_ornate_right"),
|
|
||||||
shown_topic_(nullptr)
|
|
||||||
{
|
|
||||||
// Hide the buttons at first since we do not have any forward or
|
|
||||||
// back topics at this point. They will be unhidden when history
|
|
||||||
// appears.
|
|
||||||
back_button_.hide(true);
|
|
||||||
forward_button_.hide(true);
|
|
||||||
// Set sizes to some default values.
|
|
||||||
set_measurements(font::relative_size(400), font::relative_size(500));
|
|
||||||
}
|
|
||||||
|
|
||||||
void help_browser::adjust_layout()
|
|
||||||
{
|
|
||||||
const int menu_buttons_padding = font::relative_size(10);
|
|
||||||
const int menu_y = location().y;
|
|
||||||
const int menu_x = location().x;
|
|
||||||
const int menu_w = 250;
|
|
||||||
const int menu_h = height();
|
|
||||||
|
|
||||||
const int menu_text_area_padding = font::relative_size(10);
|
|
||||||
const int text_area_y = location().y;
|
|
||||||
const int text_area_x = menu_x + menu_w + menu_text_area_padding;
|
|
||||||
const int text_area_w = width() - menu_w - menu_text_area_padding;
|
|
||||||
const int text_area_h = height();
|
|
||||||
|
|
||||||
const int button_border_padding = 0;
|
|
||||||
const int button_button_padding = font::relative_size(10);
|
|
||||||
const int back_button_x = location().x + button_border_padding;
|
|
||||||
const int back_button_y = menu_y + menu_h + menu_buttons_padding;
|
|
||||||
const int forward_button_x = back_button_x + back_button_.width() + button_button_padding;
|
|
||||||
const int forward_button_y = back_button_y;
|
|
||||||
|
|
||||||
menu_.set_width(menu_w);
|
|
||||||
menu_.set_location(menu_x, menu_y);
|
|
||||||
menu_.set_max_height(menu_h);
|
|
||||||
menu_.set_max_width(menu_w);
|
|
||||||
|
|
||||||
text_area_.set_location(text_area_x, text_area_y);
|
|
||||||
text_area_.set_width(text_area_w);
|
|
||||||
text_area_.set_height(text_area_h);
|
|
||||||
|
|
||||||
back_button_.set_location(back_button_x, back_button_y);
|
|
||||||
forward_button_.set_location(forward_button_x, forward_button_y);
|
|
||||||
|
|
||||||
set_dirty(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void help_browser::update_location(const SDL_Rect&)
|
|
||||||
{
|
|
||||||
adjust_layout();
|
|
||||||
}
|
|
||||||
|
|
||||||
void help_browser::process_event()
|
|
||||||
{
|
|
||||||
CKey key;
|
|
||||||
int mousex, mousey;
|
|
||||||
SDL_GetMouseState(&mousex,&mousey);
|
|
||||||
|
|
||||||
/// Fake focus functionality for the menu, only process it if it has focus.
|
|
||||||
if (sdl::point_in_rect(mousex, mousey, menu_.location())) {
|
|
||||||
menu_.process();
|
|
||||||
const topic *chosen_topic = menu_.chosen_topic();
|
|
||||||
if (chosen_topic != nullptr && chosen_topic != shown_topic_) {
|
|
||||||
/// A new topic has been chosen in the menu, display it.
|
|
||||||
show_topic(*chosen_topic);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (back_button_.pressed()) {
|
|
||||||
move_in_history(back_topics_, forward_topics_);
|
|
||||||
}
|
|
||||||
if (forward_button_.pressed()) {
|
|
||||||
move_in_history(forward_topics_, back_topics_);
|
|
||||||
}
|
|
||||||
back_button_.hide(back_topics_.empty());
|
|
||||||
forward_button_.hide(forward_topics_.empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
void help_browser::move_in_history(std::deque<const topic *> &from,
|
|
||||||
std::deque<const topic *> &to)
|
|
||||||
{
|
|
||||||
if (!from.empty()) {
|
|
||||||
const topic *to_show = from.back();
|
|
||||||
from.pop_back();
|
|
||||||
if (shown_topic_ != nullptr) {
|
|
||||||
if (to.size() > max_history) {
|
|
||||||
to.pop_front();
|
|
||||||
}
|
|
||||||
to.push_back(shown_topic_);
|
|
||||||
}
|
|
||||||
show_topic(*to_show, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void help_browser::handle_event(const SDL_Event &event)
|
|
||||||
{
|
|
||||||
gui::widget::handle_event(event);
|
|
||||||
|
|
||||||
SDL_MouseButtonEvent mouse_event = event.button;
|
|
||||||
if (event.type == SDL_MOUSEBUTTONDOWN) {
|
|
||||||
if (mouse_event.button == SDL_BUTTON_LEFT) {
|
|
||||||
// Did the user click a cross-reference?
|
|
||||||
const int mousex = mouse_event.x;
|
|
||||||
const int mousey = mouse_event.y;
|
|
||||||
const std::string ref = text_area_.ref_at(mousex, mousey);
|
|
||||||
if (!ref.empty()) {
|
|
||||||
const topic *t = find_topic(toplevel_, ref);
|
|
||||||
if (t == nullptr) {
|
|
||||||
//
|
|
||||||
// HACK: there are difficult-to-solve issues with a GUI2 popup over the
|
|
||||||
// GUI1 help browser (see issue #2587). Simply disabling it for now.
|
|
||||||
// Should be reenabled once the help browser switches to GUI2.
|
|
||||||
//
|
|
||||||
// -- vultraz, 2018-03-05
|
|
||||||
//
|
|
||||||
#if 0
|
|
||||||
std::stringstream msg;
|
|
||||||
msg << _("Reference to unknown topic: ") << "'" << ref << "'.";
|
|
||||||
gui2::show_transient_message("", msg.str());
|
|
||||||
#endif
|
|
||||||
update_cursor();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
show_topic(*t);
|
|
||||||
update_cursor();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
const bool mouse_back = !back_button_.hidden() && mouse_event.button == SDL_BUTTON_X1;
|
|
||||||
const bool mouse_forward = !forward_button_.hidden() && mouse_event.button == SDL_BUTTON_X2;
|
|
||||||
|
|
||||||
if (mouse_back) {
|
|
||||||
move_in_history(back_topics_, forward_topics_);
|
|
||||||
}
|
|
||||||
if (mouse_forward) {
|
|
||||||
move_in_history(forward_topics_, back_topics_);
|
|
||||||
}
|
|
||||||
if (mouse_back || mouse_forward) {
|
|
||||||
back_button_.hide(back_topics_.empty());
|
|
||||||
forward_button_.hide(forward_topics_.empty());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (event.type == SDL_MOUSEMOTION) {
|
|
||||||
update_cursor();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void help_browser::update_cursor()
|
|
||||||
{
|
|
||||||
int mousex, mousey;
|
|
||||||
SDL_GetMouseState(&mousex,&mousey);
|
|
||||||
const std::string ref = text_area_.ref_at(mousex, mousey);
|
|
||||||
if (!ref.empty() && !ref_cursor_) {
|
|
||||||
cursor::set(cursor::HYPERLINK);
|
|
||||||
ref_cursor_ = true;
|
|
||||||
}
|
|
||||||
else if (ref.empty() && ref_cursor_) {
|
|
||||||
cursor::set(cursor::NORMAL);
|
|
||||||
ref_cursor_ = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void help_browser::show_topic(const std::string &topic_id)
|
|
||||||
{
|
|
||||||
const topic *t = find_topic(toplevel_, topic_id);
|
|
||||||
|
|
||||||
if (t != nullptr) {
|
|
||||||
show_topic(*t);
|
|
||||||
} else if (topic_id.find(unit_prefix)==0 || topic_id.find(hidden_symbol() + unit_prefix)==0) {
|
|
||||||
show_topic(unknown_unit_topic);
|
|
||||||
} else {
|
|
||||||
std::cerr << "Help browser tried to show topic with id '" << topic_id
|
|
||||||
<< "' but that topic could not be found." << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void help_browser::show_topic(const topic &t, bool save_in_history)
|
|
||||||
{
|
|
||||||
log_scope("show_topic");
|
|
||||||
|
|
||||||
if (save_in_history) {
|
|
||||||
forward_topics_.clear();
|
|
||||||
if (shown_topic_ != nullptr) {
|
|
||||||
if (back_topics_.size() > max_history) {
|
|
||||||
back_topics_.pop_front();
|
|
||||||
}
|
|
||||||
back_topics_.push_back(shown_topic_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
shown_topic_ = &t;
|
|
||||||
text_area_.show_topic(t);
|
|
||||||
menu_.select_topic(t);
|
|
||||||
update_cursor();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
} // end namespace help
|
|
|
@ -1,65 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright (C) 2003 - 2018 by David White <dave@whitevine.net>
|
|
||||||
Part of 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <deque> // for deque
|
|
||||||
#include <string> // for string
|
|
||||||
#include <SDL_events.h> // for SDL_Event
|
|
||||||
#include "help_menu.hpp" // for help_menu
|
|
||||||
#include "help_text_area.hpp" // for help_text_area
|
|
||||||
#include "widgets/button.hpp" // for button
|
|
||||||
#include "widgets/widget.hpp" // for widget
|
|
||||||
class CVideo; // lines 18-18
|
|
||||||
struct SDL_Rect;
|
|
||||||
|
|
||||||
namespace help {
|
|
||||||
|
|
||||||
/// A help browser widget.
|
|
||||||
class help_browser : public gui::widget
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
help_browser(CVideo& video, const section &toplevel);
|
|
||||||
|
|
||||||
void adjust_layout();
|
|
||||||
|
|
||||||
/// Display the topic with the specified identifier. Open the menu
|
|
||||||
/// on the right location and display the topic in the text area.
|
|
||||||
void show_topic(const std::string &topic_id);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual void update_location(const SDL_Rect& rect);
|
|
||||||
virtual void process_event();
|
|
||||||
virtual void handle_event(const SDL_Event &event);
|
|
||||||
|
|
||||||
private:
|
|
||||||
/// Update the current cursor, set it to the reference cursor if
|
|
||||||
/// mousex, mousey is over a cross-reference, otherwise, set it to
|
|
||||||
/// the normal cursor.
|
|
||||||
void update_cursor();
|
|
||||||
void show_topic(const topic &t, bool save_in_history=true);
|
|
||||||
/// Move in the topic history. Pop an element from from and insert
|
|
||||||
/// it in to. Pop at the fronts if the maximum number of elements is
|
|
||||||
/// exceeded.
|
|
||||||
void move_in_history(std::deque<const topic *> &from, std::deque<const topic *> &to);
|
|
||||||
help_menu menu_;
|
|
||||||
help_text_area text_area_;
|
|
||||||
const section &toplevel_;
|
|
||||||
bool ref_cursor_; // If the cursor currently is the hyperlink cursor.
|
|
||||||
std::deque<const topic *> back_topics_, forward_topics_;
|
|
||||||
gui::button back_button_, forward_button_;
|
|
||||||
topic const *shown_topic_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // end namespace help
|
|
|
@ -1,238 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright (C) 2003 - 2018 by David White <dave@whitevine.net>
|
|
||||||
Part of 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "help/help_menu.hpp"
|
|
||||||
|
|
||||||
#include "game_config.hpp" // for menu_contract, menu_expand
|
|
||||||
#include "help/help_impl.hpp" // for section, topic, topic_list, etc
|
|
||||||
#include "sound.hpp" // for play_UI_sound
|
|
||||||
#include "wml_separators.hpp" // for IMG_TEXT_SEPARATOR, etc
|
|
||||||
|
|
||||||
#include <algorithm> // for find
|
|
||||||
#include <iostream> // for basic_ostream, operator<<, etc
|
|
||||||
#include <list> // for _List_const_iterator, etc
|
|
||||||
#include <utility> // for pair
|
|
||||||
#include <SDL.h>
|
|
||||||
|
|
||||||
class CVideo; // lines 56-56
|
|
||||||
|
|
||||||
namespace help {
|
|
||||||
|
|
||||||
help_menu::help_menu(CVideo &video, const section& toplevel, int max_height) :
|
|
||||||
gui::menu(video, empty_string_vector, true, max_height, -1, nullptr, &gui::menu::bluebg_style),
|
|
||||||
visible_items_(),
|
|
||||||
toplevel_(toplevel),
|
|
||||||
expanded_(),
|
|
||||||
restorer_(),
|
|
||||||
chosen_topic_(nullptr),
|
|
||||||
selected_item_(&toplevel, "")
|
|
||||||
{
|
|
||||||
silent_ = true; //silence the default menu sounds
|
|
||||||
update_visible_items(toplevel_);
|
|
||||||
display_visible_items();
|
|
||||||
if (!visible_items_.empty())
|
|
||||||
selected_item_ = visible_items_.front();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool help_menu::expanded(const section &sec)
|
|
||||||
{
|
|
||||||
return expanded_.find(&sec) != expanded_.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
void help_menu::expand(const section &sec)
|
|
||||||
{
|
|
||||||
if (sec.id != "toplevel" && expanded_.insert(&sec).second) {
|
|
||||||
sound::play_UI_sound(game_config::sounds::menu_expand);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void help_menu::contract(const section &sec)
|
|
||||||
{
|
|
||||||
if (expanded_.erase(&sec)) {
|
|
||||||
sound::play_UI_sound(game_config::sounds::menu_contract);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void help_menu::update_visible_items(const section &sec, unsigned level)
|
|
||||||
{
|
|
||||||
if (level == 0) {
|
|
||||||
// Clear if this is the top level, otherwise append items.
|
|
||||||
visible_items_.clear();
|
|
||||||
}
|
|
||||||
section_list::const_iterator sec_it;
|
|
||||||
for (sec_it = sec.sections.begin(); sec_it != sec.sections.end(); ++sec_it) {
|
|
||||||
if (is_visible_id((*sec_it)->id)) {
|
|
||||||
const std::string vis_string = get_string_to_show(*(*sec_it), level + 1);
|
|
||||||
visible_items_.emplace_back(*sec_it, vis_string);
|
|
||||||
if (expanded(*(*sec_it))) {
|
|
||||||
update_visible_items(*(*sec_it), level + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
topic_list::const_iterator topic_it;
|
|
||||||
for (topic_it = sec.topics.begin(); topic_it != sec.topics.end(); ++topic_it) {
|
|
||||||
if (is_visible_id(topic_it->id)) {
|
|
||||||
const std::string vis_string = get_string_to_show(*topic_it, level + 1);
|
|
||||||
visible_items_.emplace_back(&(*topic_it), vis_string);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string help_menu::indent_list(const std::string& icon, const unsigned level) {
|
|
||||||
std::stringstream to_show;
|
|
||||||
for (unsigned i = 1; i < level; ++i) {
|
|
||||||
to_show << " "; // Indent 4 spaces
|
|
||||||
}
|
|
||||||
|
|
||||||
to_show << IMG_TEXT_SEPARATOR << IMAGE_PREFIX << icon;
|
|
||||||
return to_show.str();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string help_menu::get_string_to_show(const section &sec, const unsigned level)
|
|
||||||
{
|
|
||||||
std::stringstream to_show;
|
|
||||||
to_show << indent_list(expanded(sec) ? open_section_img : closed_section_img, level)
|
|
||||||
<< IMG_TEXT_SEPARATOR << sec.title;
|
|
||||||
return to_show.str();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string help_menu::get_string_to_show(const topic &topic, const unsigned level)
|
|
||||||
{
|
|
||||||
std::stringstream to_show;
|
|
||||||
to_show << indent_list(topic_img, level)
|
|
||||||
<< IMG_TEXT_SEPARATOR << topic.title;
|
|
||||||
return to_show.str();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool help_menu::select_topic_internal(const topic &t, const section &sec)
|
|
||||||
{
|
|
||||||
topic_list::const_iterator tit =
|
|
||||||
std::find(sec.topics.begin(), sec.topics.end(), t);
|
|
||||||
if (tit != sec.topics.end()) {
|
|
||||||
// topic starting with ".." are assumed as rooted in the parent section
|
|
||||||
// and so only expand the parent when selected
|
|
||||||
if (t.id.size()<2 || t.id[0] != '.' || t.id[1] != '.')
|
|
||||||
expand(sec);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
section_list::const_iterator sit;
|
|
||||||
for (sit = sec.sections.begin(); sit != sec.sections.end(); ++sit) {
|
|
||||||
if (select_topic_internal(t, *(*sit))) {
|
|
||||||
expand(sec);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void help_menu::select_topic(const topic &t)
|
|
||||||
{
|
|
||||||
if (selected_item_ == t) {
|
|
||||||
// The requested topic is already selected.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (select_topic_internal(t, toplevel_)) {
|
|
||||||
update_visible_items(toplevel_);
|
|
||||||
for (std::vector<visible_item>::const_iterator it = visible_items_.begin();
|
|
||||||
it != visible_items_.end(); ++it) {
|
|
||||||
if (*it == t) {
|
|
||||||
selected_item_ = *it;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
display_visible_items();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int help_menu::process()
|
|
||||||
{
|
|
||||||
int res = menu::process();
|
|
||||||
int mousex, mousey;
|
|
||||||
SDL_GetMouseState(&mousex,&mousey);
|
|
||||||
|
|
||||||
if (!visible_items_.empty() &&
|
|
||||||
static_cast<std::size_t>(res) < visible_items_.size()) {
|
|
||||||
|
|
||||||
selected_item_ = visible_items_[res];
|
|
||||||
const section* sec = selected_item_.sec;
|
|
||||||
if (sec != nullptr) {
|
|
||||||
// Check how we click on the section
|
|
||||||
int x = mousex - menu::location().x;
|
|
||||||
|
|
||||||
const std::string icon_img = expanded(*sec) ? open_section_img : closed_section_img;
|
|
||||||
// we remove the right thickness (ne present between icon and text)
|
|
||||||
int text_start = style_->item_size(indent_list(icon_img, sec->level)).w - style_->get_thickness();
|
|
||||||
|
|
||||||
// NOTE: if you want to forbid click to the left of the icon
|
|
||||||
// also check x >= text_start-image_width(icon_img)
|
|
||||||
if (menu::double_clicked() || x < text_start) {
|
|
||||||
// Open or close a section if we double-click on it
|
|
||||||
// or do simple click on the icon.
|
|
||||||
expanded(*sec) ? contract(*sec) : expand(*sec);
|
|
||||||
update_visible_items(toplevel_);
|
|
||||||
display_visible_items();
|
|
||||||
} else if (x >= text_start){
|
|
||||||
// click on title open the topic associated to this section
|
|
||||||
chosen_topic_ = find_topic(default_toplevel, ".."+sec->id );
|
|
||||||
}
|
|
||||||
} else if (selected_item_.t != nullptr) {
|
|
||||||
/// Choose a topic if it is clicked.
|
|
||||||
chosen_topic_ = selected_item_.t;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
const topic *help_menu::chosen_topic()
|
|
||||||
{
|
|
||||||
const topic *ret = chosen_topic_;
|
|
||||||
chosen_topic_ = nullptr;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
void help_menu::display_visible_items()
|
|
||||||
{
|
|
||||||
std::vector<std::string> menu_items;
|
|
||||||
for(std::vector<visible_item>::const_iterator items_it = visible_items_.begin(),
|
|
||||||
end = visible_items_.end(); items_it != end; ++items_it) {
|
|
||||||
std::string to_show = items_it->visible_string;
|
|
||||||
if (selected_item_ == *items_it)
|
|
||||||
to_show = std::string("*") + to_show;
|
|
||||||
menu_items.push_back(to_show);
|
|
||||||
}
|
|
||||||
set_items(menu_items, false, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
help_menu::visible_item::visible_item(const section *_sec, const std::string &vis_string) :
|
|
||||||
t(nullptr), sec(_sec), visible_string(vis_string) {}
|
|
||||||
|
|
||||||
help_menu::visible_item::visible_item(const topic *_t, const std::string &vis_string) :
|
|
||||||
t(_t), sec(nullptr), visible_string(vis_string) {}
|
|
||||||
|
|
||||||
bool help_menu::visible_item::operator==(const section &_sec) const
|
|
||||||
{
|
|
||||||
return sec != nullptr && *sec == _sec;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool help_menu::visible_item::operator==(const topic &_t) const
|
|
||||||
{
|
|
||||||
return t != nullptr && *t == _t;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool help_menu::visible_item::operator==(const visible_item &vis_item) const
|
|
||||||
{
|
|
||||||
return t == vis_item.t && sec == vis_item.sec;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // end namespace help
|
|
|
@ -1,102 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright (C) 2003 - 2018 by David White <dave@whitevine.net>
|
|
||||||
Part of 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <set> // for set
|
|
||||||
#include <string> // for string, basic_string
|
|
||||||
#include <vector> // for vector
|
|
||||||
#include "widgets/menu.hpp" // for menu
|
|
||||||
|
|
||||||
class CVideo;
|
|
||||||
struct surface_restorer;
|
|
||||||
|
|
||||||
namespace help { struct section; }
|
|
||||||
namespace help { struct topic; }
|
|
||||||
|
|
||||||
namespace help {
|
|
||||||
|
|
||||||
/// The menu to the left in the help browser, where topics can be
|
|
||||||
/// navigated through and chosen.
|
|
||||||
class help_menu : public gui::menu
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
help_menu(CVideo &video, const section &toplevel, int max_height=-1);
|
|
||||||
int process();
|
|
||||||
|
|
||||||
/// Make the topic the currently selected one, and expand all
|
|
||||||
/// sections that need to be expanded to show it.
|
|
||||||
void select_topic(const topic &t);
|
|
||||||
|
|
||||||
/// If a topic has been chosen, return that topic, otherwise
|
|
||||||
/// nullptr. If one topic is returned, it will not be returned again,
|
|
||||||
/// if it is not re-chosen.
|
|
||||||
const topic *chosen_topic();
|
|
||||||
|
|
||||||
private:
|
|
||||||
/// Information about an item that is visible in the menu.
|
|
||||||
struct visible_item {
|
|
||||||
visible_item(const section *_sec, const std::string &visible_string);
|
|
||||||
visible_item(const topic *_t, const std::string &visible_string);
|
|
||||||
// Invariant, one if these should be nullptr. The constructors
|
|
||||||
// enforce it.
|
|
||||||
const topic *t;
|
|
||||||
const section *sec;
|
|
||||||
std::string visible_string;
|
|
||||||
bool operator==(const visible_item &vis_item) const;
|
|
||||||
bool operator==(const section &sec) const;
|
|
||||||
bool operator==(const topic &t) const;
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Regenerate what items are visible by checking what sections are
|
|
||||||
/// expanded.
|
|
||||||
void update_visible_items(const section &top_level, unsigned starting_level=0);
|
|
||||||
|
|
||||||
/// Return true if the section is expanded.
|
|
||||||
bool expanded(const section &sec);
|
|
||||||
|
|
||||||
/// Mark a section as expanded. Do not update the visible items or
|
|
||||||
/// anything.
|
|
||||||
void expand(const section &sec);
|
|
||||||
|
|
||||||
/// Contract (close) a section. That is, mark it as not expanded,
|
|
||||||
/// visible items are not updated.
|
|
||||||
void contract(const section &sec);
|
|
||||||
|
|
||||||
/// Return the string to use as the prefix for the icon part of the
|
|
||||||
/// menu-string at the specified level.
|
|
||||||
std::string indent_list(const std::string &icon, const unsigned level);
|
|
||||||
/// Return the string to use as the menu-string for sections at the
|
|
||||||
/// specified level.
|
|
||||||
std::string get_string_to_show(const section &sec, const unsigned level);
|
|
||||||
/// Return the string to use as the menu-string for topics at the
|
|
||||||
/// specified level.
|
|
||||||
std::string get_string_to_show(const topic &topic, const unsigned level);
|
|
||||||
|
|
||||||
/// Draw the currently visible items.
|
|
||||||
void display_visible_items();
|
|
||||||
|
|
||||||
/// Internal recursive thingie. did_expand will be true if any
|
|
||||||
/// section was expanded, otherwise untouched.
|
|
||||||
bool select_topic_internal(const topic &t, const section &sec);
|
|
||||||
|
|
||||||
std::vector<visible_item> visible_items_;
|
|
||||||
const section &toplevel_;
|
|
||||||
std::set<const section*> expanded_;
|
|
||||||
surface_restorer restorer_;
|
|
||||||
topic const *chosen_topic_;
|
|
||||||
visible_item selected_item_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // end namespace help
|
|
|
@ -1,573 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright (C) 2003 - 2018 by David White <dave@whitevine.net>
|
|
||||||
Part of 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "help/help_text_area.hpp"
|
|
||||||
|
|
||||||
#include "config.hpp" // for config, etc
|
|
||||||
#include "game_config.hpp" // for debug
|
|
||||||
#include "help/help_impl.hpp" // for parse_error, box_width, etc
|
|
||||||
#include "image.hpp" // for get_image
|
|
||||||
#include "log.hpp" // for LOG_STREAM, log_domain, etc
|
|
||||||
#include "preferences/general.hpp" // for font_scaled
|
|
||||||
#include "sdl/rect.hpp" // for draw_rectangle, etc
|
|
||||||
#include "serialization/parser.hpp" // for read, write
|
|
||||||
#include "video.hpp" // for CVideo
|
|
||||||
|
|
||||||
#include <algorithm> // for max, min, find_if
|
|
||||||
#include <ostream> // for operator<<, stringstream, etc
|
|
||||||
#include <vector> // for vector, etc
|
|
||||||
|
|
||||||
static lg::log_domain log_display("display");
|
|
||||||
#define WRN_DP LOG_STREAM(warn, log_display)
|
|
||||||
|
|
||||||
namespace help {
|
|
||||||
|
|
||||||
help_text_area::help_text_area(CVideo &video, const section &toplevel) :
|
|
||||||
gui::scrollarea(video),
|
|
||||||
items_(),
|
|
||||||
last_row_(),
|
|
||||||
toplevel_(toplevel),
|
|
||||||
shown_topic_(nullptr),
|
|
||||||
title_spacing_(16),
|
|
||||||
curr_loc_(0, 0),
|
|
||||||
min_row_height_(font::get_max_height(normal_font_size)),
|
|
||||||
curr_row_height_(min_row_height_),
|
|
||||||
contents_height_(0)
|
|
||||||
{
|
|
||||||
set_scroll_rate(40);
|
|
||||||
}
|
|
||||||
|
|
||||||
void help_text_area::set_inner_location(const SDL_Rect& rect)
|
|
||||||
{
|
|
||||||
bg_register(rect);
|
|
||||||
if (shown_topic_)
|
|
||||||
set_items();
|
|
||||||
}
|
|
||||||
|
|
||||||
void help_text_area::show_topic(const topic &t)
|
|
||||||
{
|
|
||||||
shown_topic_ = &t;
|
|
||||||
set_items();
|
|
||||||
set_dirty(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
help_text_area::item::item(surface surface, int x, int y, const std::string& _text,
|
|
||||||
const std::string& reference_to, bool _floating,
|
|
||||||
bool _box, ALIGNMENT alignment) :
|
|
||||||
rect(),
|
|
||||||
surf(surface),
|
|
||||||
text(_text),
|
|
||||||
ref_to(reference_to),
|
|
||||||
floating(_floating), box(_box),
|
|
||||||
align(alignment)
|
|
||||||
{
|
|
||||||
rect.x = x;
|
|
||||||
rect.y = y;
|
|
||||||
rect.w = box ? surface->w + box_width * 2 : surface->w;
|
|
||||||
rect.h = box ? surface->h + box_width * 2 : surface->h;
|
|
||||||
}
|
|
||||||
|
|
||||||
help_text_area::item::item(surface surface, int x, int y, bool _floating,
|
|
||||||
bool _box, ALIGNMENT alignment) :
|
|
||||||
rect(),
|
|
||||||
surf(surface),
|
|
||||||
text(""),
|
|
||||||
ref_to(""),
|
|
||||||
floating(_floating),
|
|
||||||
box(_box), align(alignment)
|
|
||||||
{
|
|
||||||
rect.x = x;
|
|
||||||
rect.y = y;
|
|
||||||
rect.w = box ? surface->w + box_width * 2 : surface->w;
|
|
||||||
rect.h = box ? surface->h + box_width * 2 : surface->h;
|
|
||||||
}
|
|
||||||
|
|
||||||
void help_text_area::set_items()
|
|
||||||
{
|
|
||||||
last_row_.clear();
|
|
||||||
items_.clear();
|
|
||||||
curr_loc_.first = 0;
|
|
||||||
curr_loc_.second = 0;
|
|
||||||
curr_row_height_ = min_row_height_;
|
|
||||||
// Add the title item.
|
|
||||||
const std::string show_title =
|
|
||||||
font::make_text_ellipsis(shown_topic_->title, title_size, inner_location().w);
|
|
||||||
surface surf(font::get_rendered_text(show_title, title_size,
|
|
||||||
font::NORMAL_COLOR, TTF_STYLE_BOLD));
|
|
||||||
if (surf != nullptr) {
|
|
||||||
add_item(item(surf, 0, 0, show_title));
|
|
||||||
curr_loc_.second = title_spacing_;
|
|
||||||
contents_height_ = title_spacing_;
|
|
||||||
down_one_line();
|
|
||||||
}
|
|
||||||
// Parse and add the text.
|
|
||||||
const config& parsed_items = shown_topic_->text.parsed_text();
|
|
||||||
for(auto& item : parsed_items.all_children_range()) {
|
|
||||||
#define TRY(name) do { \
|
|
||||||
if (item.key == #name) \
|
|
||||||
handle_##name##_cfg(item.cfg); \
|
|
||||||
} while (0)
|
|
||||||
|
|
||||||
TRY(text);
|
|
||||||
TRY(ref);
|
|
||||||
TRY(img);
|
|
||||||
TRY(bold);
|
|
||||||
TRY(italic);
|
|
||||||
TRY(header);
|
|
||||||
TRY(jump);
|
|
||||||
TRY(format);
|
|
||||||
#undef TRY
|
|
||||||
}
|
|
||||||
down_one_line(); // End the last line.
|
|
||||||
int h = height();
|
|
||||||
set_position(0);
|
|
||||||
set_full_size(contents_height_);
|
|
||||||
set_shown_size(h);
|
|
||||||
}
|
|
||||||
|
|
||||||
void help_text_area::handle_ref_cfg(const config &cfg)
|
|
||||||
{
|
|
||||||
const std::string dst = cfg["dst"];
|
|
||||||
const std::string text = cfg["text"];
|
|
||||||
bool force = cfg["force"].to_bool();
|
|
||||||
|
|
||||||
if (dst.empty()) {
|
|
||||||
std::stringstream msg;
|
|
||||||
msg << "Ref markup must have dst attribute. Please submit a bug"
|
|
||||||
" report if you have not modified the game files yourself. Erroneous config: ";
|
|
||||||
write(msg, cfg);
|
|
||||||
throw parse_error(msg.str());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (find_topic(toplevel_, dst) == nullptr && !force) {
|
|
||||||
// detect the broken link but quietly silence the hyperlink for normal user
|
|
||||||
add_text_item(text, game_config::debug ? dst : "", true);
|
|
||||||
|
|
||||||
// FIXME: workaround: if different campaigns define different
|
|
||||||
// terrains, some terrains available in one campaign will
|
|
||||||
// appear in the list of seen terrains, and be displayed in the
|
|
||||||
// help, even if the current campaign does not handle such
|
|
||||||
// terrains. This will lead to the unit page generator creating
|
|
||||||
// invalid references.
|
|
||||||
//
|
|
||||||
// Disabling this is a kludgey workaround until the
|
|
||||||
// encountered_terrains system is fixed
|
|
||||||
//
|
|
||||||
// -- Ayin apr 8 2005
|
|
||||||
#if 0
|
|
||||||
if (game_config::debug) {
|
|
||||||
std::stringstream msg;
|
|
||||||
msg << "Reference to non-existent topic '" << dst
|
|
||||||
<< "'. Please submit a bug report if you have not"
|
|
||||||
"modified the game files yourself. Erroneous config: ";
|
|
||||||
write(msg, cfg);
|
|
||||||
throw parse_error(msg.str());
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
} else {
|
|
||||||
add_text_item(text, dst);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void help_text_area::handle_img_cfg(const config &cfg)
|
|
||||||
{
|
|
||||||
const std::string src = cfg["src"];
|
|
||||||
const std::string align = cfg["align"];
|
|
||||||
bool floating = cfg["float"].to_bool();
|
|
||||||
bool box = cfg["box"].to_bool(true);
|
|
||||||
if (src.empty()) {
|
|
||||||
throw parse_error("Img markup must have src attribute.");
|
|
||||||
}
|
|
||||||
add_img_item(src, align, floating, box);
|
|
||||||
}
|
|
||||||
|
|
||||||
void help_text_area::handle_bold_cfg(const config &cfg)
|
|
||||||
{
|
|
||||||
const std::string text = cfg["text"];
|
|
||||||
if (text.empty()) {
|
|
||||||
throw parse_error("Bold markup must have text attribute.");
|
|
||||||
}
|
|
||||||
add_text_item(text, "", false, -1, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void help_text_area::handle_italic_cfg(const config &cfg)
|
|
||||||
{
|
|
||||||
const std::string text = cfg["text"];
|
|
||||||
if (text.empty()) {
|
|
||||||
throw parse_error("Italic markup must have text attribute.");
|
|
||||||
}
|
|
||||||
add_text_item(text, "", false, -1, false, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void help_text_area::handle_header_cfg(const config &cfg)
|
|
||||||
{
|
|
||||||
const std::string text = cfg["text"];
|
|
||||||
if (text.empty()) {
|
|
||||||
throw parse_error("Header markup must have text attribute.");
|
|
||||||
}
|
|
||||||
add_text_item(text, "", false, title2_size, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void help_text_area::handle_jump_cfg(const config &cfg)
|
|
||||||
{
|
|
||||||
const std::string amount_str = cfg["amount"];
|
|
||||||
const std::string to_str = cfg["to"];
|
|
||||||
if (amount_str.empty() && to_str.empty()) {
|
|
||||||
throw parse_error("Jump markup must have either a to or an amount attribute.");
|
|
||||||
}
|
|
||||||
unsigned jump_to = curr_loc_.first;
|
|
||||||
if (!amount_str.empty()) {
|
|
||||||
unsigned amount;
|
|
||||||
try {
|
|
||||||
amount = lexical_cast<unsigned, std::string>(amount_str);
|
|
||||||
}
|
|
||||||
catch (bad_lexical_cast&) {
|
|
||||||
throw parse_error("Invalid amount the amount attribute in jump markup.");
|
|
||||||
}
|
|
||||||
jump_to += amount;
|
|
||||||
}
|
|
||||||
if (!to_str.empty()) {
|
|
||||||
unsigned to;
|
|
||||||
try {
|
|
||||||
to = lexical_cast<unsigned, std::string>(to_str);
|
|
||||||
}
|
|
||||||
catch (bad_lexical_cast&) {
|
|
||||||
throw parse_error("Invalid amount in the to attribute in jump markup.");
|
|
||||||
}
|
|
||||||
if (to < jump_to) {
|
|
||||||
down_one_line();
|
|
||||||
}
|
|
||||||
jump_to = to;
|
|
||||||
}
|
|
||||||
if (jump_to != 0 && static_cast<int>(jump_to) <
|
|
||||||
get_max_x(curr_loc_.first, curr_row_height_)) {
|
|
||||||
|
|
||||||
curr_loc_.first = jump_to;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void help_text_area::handle_format_cfg(const config &cfg)
|
|
||||||
{
|
|
||||||
const std::string text = cfg["text"];
|
|
||||||
if (text.empty()) {
|
|
||||||
throw parse_error("Format markup must have text attribute.");
|
|
||||||
}
|
|
||||||
bool bold = cfg["bold"].to_bool();
|
|
||||||
bool italic = cfg["italic"].to_bool();
|
|
||||||
int font_size = cfg["font_size"].to_int(normal_font_size);
|
|
||||||
color_t color = help::string_to_color(cfg["color"]);
|
|
||||||
add_text_item(text, "", false, font_size, bold, italic, color);
|
|
||||||
}
|
|
||||||
|
|
||||||
void help_text_area::handle_text_cfg(const config& cfg) {
|
|
||||||
add_text_item(cfg["text"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
void help_text_area::add_text_item(const std::string& text, const std::string& ref_dst,
|
|
||||||
bool broken_link, int _font_size, bool bold, bool italic,
|
|
||||||
color_t text_color
|
|
||||||
)
|
|
||||||
{
|
|
||||||
const int font_size = _font_size < 0 ? normal_font_size : _font_size;
|
|
||||||
// font::line_width(), font::get_rendered_text() are not use scaled font inside
|
|
||||||
const int scaled_font_size = preferences::font_scaled(font_size);
|
|
||||||
if (text.empty())
|
|
||||||
return;
|
|
||||||
const int remaining_width = get_remaining_width();
|
|
||||||
std::size_t first_word_start = text.find_first_not_of(" ");
|
|
||||||
if (first_word_start == std::string::npos) {
|
|
||||||
first_word_start = 0;
|
|
||||||
}
|
|
||||||
if (text[first_word_start] == '\n') {
|
|
||||||
down_one_line();
|
|
||||||
std::string rest_text = text;
|
|
||||||
rest_text.erase(0, first_word_start + 1);
|
|
||||||
add_text_item(rest_text, ref_dst, broken_link, _font_size, bold, italic, text_color);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const std::string first_word = get_first_word(text);
|
|
||||||
int state = 0;
|
|
||||||
state |= bold ? TTF_STYLE_BOLD : 0;
|
|
||||||
state |= italic ? TTF_STYLE_ITALIC : 0;
|
|
||||||
if (curr_loc_.first != get_min_x(curr_loc_.second, curr_row_height_)
|
|
||||||
&& remaining_width < font::line_width(first_word, scaled_font_size, state)) {
|
|
||||||
// The first word does not fit, and we are not at the start of
|
|
||||||
// the line. Move down.
|
|
||||||
down_one_line();
|
|
||||||
std::string s = remove_first_space(text);
|
|
||||||
add_text_item(s, ref_dst, broken_link, _font_size, bold, italic, text_color);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
std::vector<std::string> parts = split_in_width(text, font_size, remaining_width);
|
|
||||||
std::string first_part = parts.front();
|
|
||||||
// Always override the color if we have a cross reference.
|
|
||||||
color_t color;
|
|
||||||
if(ref_dst.empty())
|
|
||||||
color = text_color;
|
|
||||||
else if(broken_link)
|
|
||||||
color = font::BAD_COLOR;
|
|
||||||
else
|
|
||||||
color = font::YELLOW_COLOR;
|
|
||||||
|
|
||||||
// In split_in_width(), no_break_after() and no_break_before() are used(see marked-up_text.cpp).
|
|
||||||
// Thus, even if there is enough remaining_width for the next word,
|
|
||||||
// sometimes empty string is returned from split_in_width().
|
|
||||||
if (first_part.empty()) {
|
|
||||||
down_one_line();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
surface surf(font::get_rendered_text(first_part, scaled_font_size, color, state));
|
|
||||||
if (!surf.null())
|
|
||||||
add_item(item(surf, curr_loc_.first, curr_loc_.second, first_part, ref_dst));
|
|
||||||
}
|
|
||||||
if (parts.size() > 1) {
|
|
||||||
|
|
||||||
std::string& s = parts.back();
|
|
||||||
|
|
||||||
const std::string first_word_before = get_first_word(s);
|
|
||||||
const std::string first_word_after = get_first_word(remove_first_space(s));
|
|
||||||
if (get_remaining_width() >= font::line_width(first_word_after, scaled_font_size, state)
|
|
||||||
&& get_remaining_width()
|
|
||||||
< font::line_width(first_word_before, scaled_font_size, state)) {
|
|
||||||
// If the removal of the space made this word fit, we
|
|
||||||
// must move down a line, otherwise it will be drawn
|
|
||||||
// without a space at the end of the line.
|
|
||||||
s = remove_first_space(s);
|
|
||||||
down_one_line();
|
|
||||||
}
|
|
||||||
else if (!(font::line_width(first_word_before, scaled_font_size, state)
|
|
||||||
< get_remaining_width())) {
|
|
||||||
s = remove_first_space(s);
|
|
||||||
}
|
|
||||||
add_text_item(s, ref_dst, broken_link, _font_size, bold, italic, text_color);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void help_text_area::add_img_item(const std::string& path, const std::string& alignment,
|
|
||||||
const bool floating, const bool box)
|
|
||||||
{
|
|
||||||
surface surf(image::get_image(path));
|
|
||||||
if (surf.null())
|
|
||||||
return;
|
|
||||||
ALIGNMENT align = str_to_align(alignment);
|
|
||||||
if (align == HERE && floating) {
|
|
||||||
WRN_DP << "Floating image with align HERE, aligning left." << std::endl;
|
|
||||||
align = LEFT;
|
|
||||||
}
|
|
||||||
const int width = surf->w + (box ? box_width * 2 : 0);
|
|
||||||
int xpos;
|
|
||||||
int ypos = curr_loc_.second;
|
|
||||||
int text_width = inner_location().w;
|
|
||||||
switch (align) {
|
|
||||||
case HERE:
|
|
||||||
xpos = curr_loc_.first;
|
|
||||||
break;
|
|
||||||
case LEFT:
|
|
||||||
default:
|
|
||||||
xpos = 0;
|
|
||||||
break;
|
|
||||||
case MIDDLE:
|
|
||||||
xpos = text_width / 2 - width / 2 - (box ? box_width : 0);
|
|
||||||
break;
|
|
||||||
case RIGHT:
|
|
||||||
xpos = text_width - width - (box ? box_width * 2 : 0);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (curr_loc_.first != get_min_x(curr_loc_.second, curr_row_height_)
|
|
||||||
&& (xpos < curr_loc_.first || xpos + width > text_width)) {
|
|
||||||
down_one_line();
|
|
||||||
add_img_item(path, alignment, floating, box);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (!floating) {
|
|
||||||
curr_loc_.first = xpos;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
ypos = get_y_for_floating_img(width, xpos, ypos);
|
|
||||||
}
|
|
||||||
add_item(item(surf, xpos, ypos, floating, box, align));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int help_text_area::get_y_for_floating_img(const int width, const int x, const int desired_y)
|
|
||||||
{
|
|
||||||
int min_y = desired_y;
|
|
||||||
for (std::list<item>::const_iterator it = items_.begin(); it != items_.end(); ++it) {
|
|
||||||
const item& itm = *it;
|
|
||||||
if (itm.floating) {
|
|
||||||
if ((itm.rect.x + itm.rect.w > x && itm.rect.x < x + width)
|
|
||||||
|| (itm.rect.x > x && itm.rect.x < x + width)) {
|
|
||||||
min_y = std::max<int>(min_y, itm.rect.y + itm.rect.h);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return min_y;
|
|
||||||
}
|
|
||||||
|
|
||||||
int help_text_area::get_min_x(const int y, const int height)
|
|
||||||
{
|
|
||||||
int min_x = 0;
|
|
||||||
for (std::list<item>::const_iterator it = items_.begin(); it != items_.end(); ++it) {
|
|
||||||
const item& itm = *it;
|
|
||||||
if (itm.floating) {
|
|
||||||
if (itm.rect.y < y + height && itm.rect.y + itm.rect.h > y && itm.align == LEFT) {
|
|
||||||
min_x = std::max<int>(min_x, itm.rect.w + 5);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return min_x;
|
|
||||||
}
|
|
||||||
|
|
||||||
int help_text_area::get_max_x(const int y, const int height)
|
|
||||||
{
|
|
||||||
int text_width = inner_location().w;
|
|
||||||
int max_x = text_width;
|
|
||||||
for (std::list<item>::const_iterator it = items_.begin(); it != items_.end(); ++it) {
|
|
||||||
const item& itm = *it;
|
|
||||||
if (itm.floating) {
|
|
||||||
if (itm.rect.y < y + height && itm.rect.y + itm.rect.h > y) {
|
|
||||||
if (itm.align == RIGHT) {
|
|
||||||
max_x = std::min<int>(max_x, text_width - itm.rect.w - 5);
|
|
||||||
} else if (itm.align == MIDDLE) {
|
|
||||||
max_x = std::min<int>(max_x, text_width / 2 - itm.rect.w / 2 - 5);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return max_x;
|
|
||||||
}
|
|
||||||
|
|
||||||
void help_text_area::add_item(const item &itm)
|
|
||||||
{
|
|
||||||
items_.push_back(itm);
|
|
||||||
if (!itm.floating) {
|
|
||||||
curr_loc_.first += itm.rect.w;
|
|
||||||
curr_row_height_ = std::max<int>(itm.rect.h, curr_row_height_);
|
|
||||||
contents_height_ = std::max<int>(contents_height_, curr_loc_.second + curr_row_height_);
|
|
||||||
last_row_.push_back(&items_.back());
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (itm.align == LEFT) {
|
|
||||||
curr_loc_.first = itm.rect.w + 5;
|
|
||||||
}
|
|
||||||
contents_height_ = std::max<int>(contents_height_, itm.rect.y + itm.rect.h);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
help_text_area::ALIGNMENT help_text_area::str_to_align(const std::string &cmp_str)
|
|
||||||
{
|
|
||||||
if (cmp_str == "left") {
|
|
||||||
return LEFT;
|
|
||||||
} else if (cmp_str == "middle") {
|
|
||||||
return MIDDLE;
|
|
||||||
} else if (cmp_str == "right") {
|
|
||||||
return RIGHT;
|
|
||||||
} else if (cmp_str == "here" || cmp_str.empty()) { // Make the empty string be "here" alignment.
|
|
||||||
return HERE;
|
|
||||||
}
|
|
||||||
std::stringstream msg;
|
|
||||||
msg << "Invalid alignment string: '" << cmp_str << "'";
|
|
||||||
throw parse_error(msg.str());
|
|
||||||
}
|
|
||||||
|
|
||||||
void help_text_area::down_one_line()
|
|
||||||
{
|
|
||||||
adjust_last_row();
|
|
||||||
last_row_.clear();
|
|
||||||
curr_loc_.second += curr_row_height_ + (curr_row_height_ == min_row_height_ ? 0 : 2);
|
|
||||||
curr_row_height_ = min_row_height_;
|
|
||||||
contents_height_ = std::max<int>(curr_loc_.second + curr_row_height_, contents_height_);
|
|
||||||
curr_loc_.first = get_min_x(curr_loc_.second, curr_row_height_);
|
|
||||||
}
|
|
||||||
|
|
||||||
void help_text_area::adjust_last_row()
|
|
||||||
{
|
|
||||||
for (std::list<item *>::iterator it = last_row_.begin(); it != last_row_.end(); ++it) {
|
|
||||||
item &itm = *(*it);
|
|
||||||
const int gap = curr_row_height_ - itm.rect.h;
|
|
||||||
itm.rect.y += gap / 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int help_text_area::get_remaining_width()
|
|
||||||
{
|
|
||||||
const int total_w = get_max_x(curr_loc_.second, curr_row_height_);
|
|
||||||
return total_w - curr_loc_.first;
|
|
||||||
}
|
|
||||||
|
|
||||||
void help_text_area::draw_contents()
|
|
||||||
{
|
|
||||||
const SDL_Rect& loc = inner_location();
|
|
||||||
bg_restore();
|
|
||||||
surface& screen = video().getSurface();
|
|
||||||
clip_rect_setter clip_rect_set(screen, &loc);
|
|
||||||
for(std::list<item>::const_iterator it = items_.begin(), end = items_.end(); it != end; ++it) {
|
|
||||||
SDL_Rect dst = it->rect;
|
|
||||||
dst.y -= get_position();
|
|
||||||
if (dst.y < static_cast<int>(loc.h) && dst.y + it->rect.h > 0) {
|
|
||||||
dst.x += loc.x;
|
|
||||||
dst.y += loc.y;
|
|
||||||
if (it->box) {
|
|
||||||
for (int i = 0; i < box_width; ++i) {
|
|
||||||
SDL_Rect draw_rect {
|
|
||||||
dst.x,
|
|
||||||
dst.y,
|
|
||||||
it->rect.w - i * 2,
|
|
||||||
it->rect.h - i * 2
|
|
||||||
};
|
|
||||||
|
|
||||||
sdl::draw_rectangle(draw_rect, {0, 0, 0, 0});
|
|
||||||
++dst.x;
|
|
||||||
++dst.y;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sdl_blit(it->surf, nullptr, screen, &dst);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void help_text_area::scroll(unsigned int)
|
|
||||||
{
|
|
||||||
// Nothing will be done on the actual scroll event. The scroll
|
|
||||||
// position is checked when drawing instead and things drawn
|
|
||||||
// accordingly.
|
|
||||||
set_dirty(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool help_text_area::item_at::operator()(const item& item) const {
|
|
||||||
return sdl::point_in_rect(x_, y_, item.rect);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string help_text_area::ref_at(const int x, const int y)
|
|
||||||
{
|
|
||||||
const int local_x = x - location().x;
|
|
||||||
const int local_y = y - location().y;
|
|
||||||
if (local_y < height() && local_y > 0) {
|
|
||||||
const int cmp_y = local_y + get_position();
|
|
||||||
const std::list<item>::const_iterator it =
|
|
||||||
std::find_if(items_.begin(), items_.end(), item_at(local_x, cmp_y));
|
|
||||||
if (it != items_.end()) {
|
|
||||||
if (!(*it).ref_to.empty()) {
|
|
||||||
return ((*it).ref_to);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
} // end namespace help
|
|
|
@ -1,167 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright (C) 2003 - 2018 by David White <dave@whitevine.net>
|
|
||||||
Part of 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <list> // for list
|
|
||||||
#include <string> // for string
|
|
||||||
#include <utility> // for pair
|
|
||||||
#include "font/standard_colors.hpp" // for NORMAL_COLOR
|
|
||||||
#include "sdl/surface.hpp" // for surface
|
|
||||||
#include "widgets/scrollarea.hpp" // for scrollarea
|
|
||||||
class CVideo;
|
|
||||||
class config;
|
|
||||||
namespace help { struct section; }
|
|
||||||
namespace help { struct topic; }
|
|
||||||
|
|
||||||
namespace help {
|
|
||||||
|
|
||||||
|
|
||||||
/// The area where the content is shown in the help browser.
|
|
||||||
class help_text_area : public gui::scrollarea
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
help_text_area(CVideo &video, const section &toplevel);
|
|
||||||
/// Display the topic.
|
|
||||||
void show_topic(const topic &t);
|
|
||||||
|
|
||||||
/// Return the ID that is cross-referenced at the (screen)
|
|
||||||
/// coordinates x, y. If no cross-reference is there, return the
|
|
||||||
/// empty string.
|
|
||||||
std::string ref_at(const int x, const int y);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual void scroll(unsigned int pos);
|
|
||||||
virtual void set_inner_location(const SDL_Rect& rect);
|
|
||||||
|
|
||||||
private:
|
|
||||||
enum ALIGNMENT {LEFT, MIDDLE, RIGHT, HERE};
|
|
||||||
/// Convert a string to an alignment. Throw parse_error if
|
|
||||||
/// unsuccessful.
|
|
||||||
ALIGNMENT str_to_align(const std::string &s);
|
|
||||||
|
|
||||||
/// An item that is displayed in the text area. Contains the surface
|
|
||||||
/// that should be blitted along with some other information.
|
|
||||||
struct item {
|
|
||||||
|
|
||||||
item(surface surface, int x, int y, const std::string& text="",
|
|
||||||
const std::string& reference_to="", bool floating=false,
|
|
||||||
bool box=false, ALIGNMENT alignment=HERE);
|
|
||||||
|
|
||||||
item(surface surface, int x, int y,
|
|
||||||
bool floating, bool box=false, ALIGNMENT=HERE);
|
|
||||||
|
|
||||||
/// Relative coordinates of this item.
|
|
||||||
SDL_Rect rect;
|
|
||||||
|
|
||||||
surface surf;
|
|
||||||
|
|
||||||
// If this item contains text, this will contain that text.
|
|
||||||
std::string text;
|
|
||||||
|
|
||||||
// If this item contains a cross-reference, this is the id
|
|
||||||
// of the referenced topic.
|
|
||||||
std::string ref_to;
|
|
||||||
|
|
||||||
// If this item is floating, that is, if things should be filled
|
|
||||||
// around it.
|
|
||||||
bool floating;
|
|
||||||
bool box;
|
|
||||||
ALIGNMENT align;
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Function object to find an item at the specified coordinates.
|
|
||||||
class item_at {
|
|
||||||
public:
|
|
||||||
item_at(const int x, const int y) : x_(x), y_(y) {}
|
|
||||||
bool operator()(const item&) const;
|
|
||||||
private:
|
|
||||||
const int x_, y_;
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Update the vector with the items of the shown topic, creating
|
|
||||||
/// surfaces for everything and putting things where they belong.
|
|
||||||
void set_items();
|
|
||||||
|
|
||||||
// Create appropriate items from configs. Items will be added to the
|
|
||||||
// internal vector. These methods check that the necessary
|
|
||||||
// attributes are specified.
|
|
||||||
void handle_ref_cfg(const config &cfg);
|
|
||||||
void handle_img_cfg(const config &cfg);
|
|
||||||
void handle_bold_cfg(const config &cfg);
|
|
||||||
void handle_italic_cfg(const config &cfg);
|
|
||||||
void handle_header_cfg(const config &cfg);
|
|
||||||
void handle_jump_cfg(const config &cfg);
|
|
||||||
void handle_format_cfg(const config &cfg);
|
|
||||||
void handle_text_cfg(const config &cfg);
|
|
||||||
|
|
||||||
void draw_contents();
|
|
||||||
|
|
||||||
/// Add an item with text. If ref_dst is something else than the
|
|
||||||
/// empty string, the text item will be underlined to show that it
|
|
||||||
/// is a cross-reference. The item will also remember what the
|
|
||||||
/// reference points to. If font_size is below zero, the default
|
|
||||||
/// will be used.
|
|
||||||
void add_text_item(const std::string& text, const std::string& ref_dst="",
|
|
||||||
bool broken_link = false,
|
|
||||||
int font_size=-1, bool bold=false, bool italic=false,
|
|
||||||
color_t color=font::NORMAL_COLOR);
|
|
||||||
|
|
||||||
/// Add an image item with the specified attributes.
|
|
||||||
void add_img_item(const std::string& path, const std::string& alignment, const bool floating,
|
|
||||||
const bool box);
|
|
||||||
|
|
||||||
/// Move the current input point to the next line.
|
|
||||||
void down_one_line();
|
|
||||||
|
|
||||||
/// Adjust the heights of the items in the last row to make it look
|
|
||||||
/// good .
|
|
||||||
void adjust_last_row();
|
|
||||||
|
|
||||||
/// Return the width that remain on the line the current input point is at.
|
|
||||||
int get_remaining_width();
|
|
||||||
|
|
||||||
/// Return the least x coordinate at which something of the
|
|
||||||
/// specified height can be drawn at the specified y coordinate
|
|
||||||
/// without interfering with floating images.
|
|
||||||
int get_min_x(const int y, const int height=0);
|
|
||||||
|
|
||||||
/// Analogous with get_min_x but return the maximum X.
|
|
||||||
int get_max_x(const int y, const int height=0);
|
|
||||||
|
|
||||||
/// Find the lowest y coordinate where a floating img of the
|
|
||||||
/// specified width and at the specified x coordinate can be
|
|
||||||
/// placed. Start looking at desired_y and continue downwards. Only
|
|
||||||
/// check against other floating things, since text and inline
|
|
||||||
/// images only can be above this place if called correctly.
|
|
||||||
int get_y_for_floating_img(const int width, const int x, const int desired_y);
|
|
||||||
|
|
||||||
/// Add an item to the internal list, update the locations and row
|
|
||||||
/// height.
|
|
||||||
void add_item(const item& itm);
|
|
||||||
|
|
||||||
std::list<item> items_;
|
|
||||||
std::list<item *> last_row_;
|
|
||||||
const section &toplevel_;
|
|
||||||
topic const *shown_topic_;
|
|
||||||
const int title_spacing_;
|
|
||||||
// The current input location when creating items.
|
|
||||||
std::pair<int, int> curr_loc_;
|
|
||||||
const unsigned min_row_height_;
|
|
||||||
unsigned curr_row_height_;
|
|
||||||
/// The height of all items in total.
|
|
||||||
int contents_height_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // end namespace help
|
|
Loading…
Add table
Reference in a new issue