textscroller.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. // SuperTux
  2. // Copyright (C) 2006 Matthias Braun <matze@braunis.de>
  3. // 2018 Ingo Ruhnke <grumbel@gmail.com>
  4. //
  5. // This program is free software: you can redistribute it and/or modify
  6. // it under the terms of the GNU General Public License as published by
  7. // the Free Software Foundation, either version 3 of the License, or
  8. // (at your option) any later version.
  9. //
  10. // This program is distributed in the hope that it will be useful,
  11. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. // GNU General Public License for more details.
  14. //
  15. // You should have received a copy of the GNU General Public License
  16. // along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. #include "object/textscroller.hpp"
  18. #include <optional>
  19. #include <sexp/value.hpp>
  20. #include "control/input_manager.hpp"
  21. #include "editor/editor.hpp"
  22. #include "supertux/globals.hpp"
  23. #include "supertux/fadetoblack.hpp"
  24. #include "supertux/info_box_line.hpp"
  25. #include "supertux/screen_manager.hpp"
  26. #include "supertux/sector.hpp"
  27. #include "util/log.hpp"
  28. #include "util/reader.hpp"
  29. #include "util/reader_collection.hpp"
  30. #include "util/reader_document.hpp"
  31. #include "util/reader_mapping.hpp"
  32. #include "video/drawing_context.hpp"
  33. #include "video/video_system.hpp"
  34. #include "video/viewport.hpp"
  35. namespace {
  36. const float LEFT_BORDER = 0;
  37. const float DEFAULT_SPEED = 60;
  38. const float SCROLL_JUMP = 60;
  39. } // namespace
  40. TextScroller::TextScroller(const ReaderMapping& mapping) :
  41. controller(&InputManager::current()->get_controller()),
  42. m_filename(),
  43. m_finish_script(),
  44. m_lines(),
  45. m_scroll(),
  46. m_default_speed(DEFAULT_SPEED),
  47. m_x_offset(),
  48. m_controllable(true),
  49. m_finished(),
  50. m_fading(),
  51. m_x_anchor(XAnchor::SCROLLER_ANCHOR_CENTER),
  52. m_text_align(TextAlign::SCROLLER_ALIGN_CENTER)
  53. {
  54. mapping.get("file", m_filename);
  55. if (!Editor::is_active())
  56. {
  57. if (m_filename.empty())
  58. throw std::runtime_error("Cannot load text scroller: No file specified!");
  59. parse_file(m_filename);
  60. }
  61. mapping.get("finish-script", m_finish_script, "");
  62. mapping.get("speed", m_default_speed);
  63. mapping.get("x-offset", m_x_offset);
  64. mapping.get("controllable", m_controllable, true);
  65. std::string x_anchor_str;
  66. if (mapping.get("x-anchor", x_anchor_str))
  67. {
  68. if (x_anchor_str == "left")
  69. m_x_anchor = XAnchor::SCROLLER_ANCHOR_LEFT;
  70. else if (x_anchor_str == "right")
  71. m_x_anchor = XAnchor::SCROLLER_ANCHOR_RIGHT;
  72. else
  73. m_x_anchor = XAnchor::SCROLLER_ANCHOR_CENTER;
  74. }
  75. std::string text_align_str;
  76. if (mapping.get("text-align", text_align_str))
  77. {
  78. if (text_align_str == "left")
  79. m_text_align = TextAlign::SCROLLER_ALIGN_LEFT;
  80. else if (text_align_str == "right")
  81. m_text_align = TextAlign::SCROLLER_ALIGN_RIGHT;
  82. else
  83. m_text_align = TextAlign::SCROLLER_ALIGN_CENTER;
  84. }
  85. }
  86. TextScroller::TextScroller(const ReaderObject& root) :
  87. controller(&InputManager::current()->get_controller()),
  88. m_filename(),
  89. m_finish_script(),
  90. m_lines(),
  91. m_scroll(),
  92. m_default_speed(DEFAULT_SPEED),
  93. m_x_offset(),
  94. m_controllable(true),
  95. m_finished(),
  96. m_fading(),
  97. m_x_anchor(XAnchor::SCROLLER_ANCHOR_CENTER),
  98. m_text_align(TextAlign::SCROLLER_ALIGN_CENTER)
  99. {
  100. parse_root(root);
  101. }
  102. void
  103. TextScroller::parse_file(const std::string& filename)
  104. {
  105. register_translation_directory(filename);
  106. auto doc = ReaderDocument::from_file(filename);
  107. auto root = doc.get_root();
  108. parse_root(root);
  109. }
  110. void
  111. TextScroller::parse_root(const ReaderObject& root)
  112. {
  113. if (root.get_name() != "supertux-text")
  114. {
  115. throw std::runtime_error("File isn't a supertux-text file");
  116. }
  117. else
  118. {
  119. auto mapping = root.get_mapping();
  120. int version = 1;
  121. mapping.get("version", version);
  122. if (version == 1)
  123. {
  124. log_info << "[" << mapping.get_doc().get_filename() << "] Text uses old format: version 1" << std::endl;
  125. std::string text;
  126. if (!mapping.get("text", text)) {
  127. throw std::runtime_error("File doesn't contain a text field");
  128. }
  129. // Split text string lines into a vector
  130. m_lines = InfoBoxLine::split(text, static_cast<float>(SCREEN_WIDTH) - 2.0f * LEFT_BORDER);
  131. }
  132. else if (version == 2)
  133. {
  134. std::optional<ReaderCollection> content_collection;
  135. if (!mapping.get("content", content_collection)) {
  136. throw std::runtime_error("File doesn't contain content");
  137. } else {
  138. parse_content(*content_collection);
  139. }
  140. }
  141. }
  142. }
  143. void
  144. TextScroller::parse_content(const ReaderCollection& collection)
  145. {
  146. for (const auto& item : collection.get_objects())
  147. {
  148. if (item.get_name() == "image")
  149. {
  150. std::string image_file = item.get_sexp().as_array()[1].as_string();
  151. m_lines.emplace_back(new InfoBoxLine('!', image_file));
  152. }
  153. else if (item.get_name() == "person")
  154. {
  155. bool simple;
  156. std::string name, info, image_file;
  157. item.get_mapping().get("simple", simple, false);
  158. if (simple) {
  159. if (!item.get_mapping().get("name", name) || !item.get_mapping().get("info", info)) {
  160. throw std::runtime_error("Simple entry requires both name and info specified");
  161. }
  162. if (item.get_mapping().get("image", image_file)) {
  163. log_warning << "[" << collection.get_doc().get_filename() << "] Simple person entry shouldn't specify images" << std::endl;
  164. }
  165. m_lines.emplace_back(new InfoBoxLine(' ', name + " (" + info + ")")); // NOLINT
  166. } else {
  167. if (item.get_mapping().get("name", name)) {
  168. m_lines.emplace_back(new InfoBoxLine('\t', name));
  169. }
  170. if (item.get_mapping().get("image", image_file)) {
  171. m_lines.emplace_back(new InfoBoxLine('!', image_file));
  172. }
  173. if (item.get_mapping().get("info", info)) {
  174. m_lines.emplace_back(new InfoBoxLine(' ', info));
  175. }
  176. }
  177. }
  178. else if (item.get_name() == "blank")
  179. {
  180. // Empty line
  181. m_lines.emplace_back(new InfoBoxLine('\t', ""));
  182. }
  183. else if (item.get_name() == "text")
  184. {
  185. std::string type, string;
  186. if (!item.get_mapping().get("type", type)) {
  187. type = "normal";
  188. }
  189. if (!item.get_mapping().get("string", string)) {
  190. throw std::runtime_error("Text entry requires a string");
  191. }
  192. if (type == "normal")
  193. m_lines.emplace_back(new InfoBoxLine('\t', string));
  194. else if (type == "normal-left")
  195. m_lines.emplace_back(new InfoBoxLine('#', string));
  196. else if (type == "small")
  197. m_lines.emplace_back(new InfoBoxLine(' ', string));
  198. else if (type == "heading")
  199. m_lines.emplace_back(new InfoBoxLine('-', string));
  200. else if (type == "reference")
  201. m_lines.emplace_back(new InfoBoxLine('*', string));
  202. else {
  203. log_warning << "[" << item.get_doc().get_filename() << "] Unknown text type '" << type << "'" << std::endl;
  204. m_lines.emplace_back(new InfoBoxLine('\t', string));
  205. }
  206. }
  207. else
  208. {
  209. log_warning << "[" << item.get_doc().get_filename() << "] Unknown token '" << item.get_name() << "'" << std::endl;
  210. }
  211. }
  212. }
  213. void
  214. TextScroller::draw(DrawingContext& context)
  215. {
  216. context.push_transform();
  217. context.set_translation(Vector(0, 0));
  218. context.transform().scale = 1.f;
  219. const float ctx_w = context.get_width();
  220. const float ctx_h = context.get_height();
  221. float y = floorf(ctx_h - m_scroll);
  222. { // draw text
  223. for (const auto& line : m_lines)
  224. {
  225. if (y + line->get_height() >= 0 && ctx_h - y >= 0) {
  226. line->draw(context, Rectf(LEFT_BORDER, y, ctx_w * (m_x_anchor == XAnchor::SCROLLER_ANCHOR_LEFT ? 0.f :
  227. m_x_anchor == XAnchor::SCROLLER_ANCHOR_RIGHT ? 2.f : 1.f) + m_x_offset, y), LAYER_GUI,
  228. (m_text_align == TextAlign::SCROLLER_ALIGN_LEFT ? line->LineAlignment::LEFT :
  229. m_text_align == TextAlign::SCROLLER_ALIGN_RIGHT ? line->LineAlignment::RIGHT :
  230. line->LineAlignment::CENTER));
  231. }
  232. y += floorf(line->get_height());
  233. }
  234. }
  235. context.pop_transform();
  236. // close when done
  237. if (y < 0)
  238. {
  239. m_finished = true;
  240. set_default_speed(0.f);
  241. }
  242. }
  243. void
  244. TextScroller::update(float dt_sec)
  245. {
  246. float speed = m_default_speed;
  247. if (controller && m_controllable) {
  248. // allow changing speed with up and down keys
  249. if (controller->hold(Control::UP)) {
  250. speed = -m_default_speed * 5;
  251. } else if (controller->hold(Control::DOWN)) {
  252. speed = m_default_speed * 5;
  253. }
  254. // allow jumping ahead with certain keys
  255. if (controller->pressed_any(Control::JUMP, Control::ACTION, Control::MENU_SELECT) &&
  256. !(controller->pressed(Control::UP))) { // prevent skipping if jump with up is enabled
  257. scroll(SCROLL_JUMP);
  258. }
  259. // use start or escape keys to exit
  260. if (controller->pressed_any(Control::START, Control::ESCAPE) &&
  261. !m_fading && m_finish_script.empty()) {
  262. m_fading = true;
  263. ScreenManager::current()->pop_screen(std::make_unique<FadeToBlack>(FadeToBlack::FADEOUT, 0.5f));
  264. return;
  265. }
  266. }
  267. m_scroll += speed * dt_sec;
  268. if (m_scroll < 0)
  269. m_scroll = 0;
  270. if (!m_finish_script.empty())
  271. {
  272. Sector::get().run_script(m_finish_script, "finishscript");
  273. }
  274. else
  275. {
  276. // close when done
  277. if (m_finished && !m_fading)
  278. {
  279. m_fading = true;
  280. ScreenManager::current()->pop_screen(std::unique_ptr<ScreenFade>(new FadeToBlack(FadeToBlack::FADEOUT, 0.25f)));
  281. }
  282. }
  283. }
  284. void
  285. TextScroller::set_default_speed(float default_speed)
  286. {
  287. m_default_speed = default_speed;
  288. }
  289. void
  290. TextScroller::scroll(float offset)
  291. {
  292. m_scroll += offset;
  293. if (m_scroll < 0.0f)
  294. {
  295. m_scroll = 0.0f;
  296. }
  297. }
  298. ObjectSettings
  299. TextScroller::get_settings()
  300. {
  301. ObjectSettings result = GameObject::get_settings();
  302. result.add_file(_("File"), &m_filename, "file");
  303. result.add_script(_("Finish Script"), &m_finish_script, "finish-script");
  304. result.add_float(_("Speed"), &m_default_speed, "speed", DEFAULT_SPEED);
  305. result.add_float(_("X-offset"), &m_x_offset, "x-offset");
  306. result.add_bool(_("Controllable"), &m_controllable, "controllable", true);
  307. result.add_enum(_("Anchor"), reinterpret_cast<int*>(&m_x_anchor),
  308. { _("Left"), _("Center"), _("Right") },
  309. { "left", "center", "right" },
  310. static_cast<int>(XAnchor::SCROLLER_ANCHOR_CENTER), "x-anchor");
  311. result.add_enum(_("Text Alignment"), reinterpret_cast<int*>(&m_text_align),
  312. { _("Left"), _("Center"), _("Right") },
  313. { "left", "center", "right" },
  314. static_cast<int>(TextAlign::SCROLLER_ALIGN_CENTER), "text-align");
  315. result.add_remove();
  316. return result;
  317. }
  318. /* EOF */