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
18#include "object/textscroller.hpp"
19
20#include <boost/optional.hpp>
21#include <sexp/value.hpp>
22
23#include "control/input_manager.hpp"
24#include "supertux/globals.hpp"
25#include "supertux/fadetoblack.hpp"
26#include "supertux/info_box_line.hpp"
27#include "supertux/screen_manager.hpp"
28#include "util/log.hpp"
29#include "util/reader.hpp"
30#include "util/reader_collection.hpp"
31#include "util/reader_document.hpp"
32#include "util/reader_mapping.hpp"
33#include "video/drawing_context.hpp"
34#include "video/video_system.hpp"
35#include "video/viewport.hpp"
36
37namespace {
38
39const float LEFT_BORDER = 0;
40const float DEFAULT_SPEED = 60;
41
42} // namespace
43
44TextScroller::TextScroller(const ReaderMapping& mapping) :
45 controller(),
46 m_filename(),
47 m_lines(),
48 m_scroll(),
49 m_speed(DEFAULT_SPEED),
50 m_finished(false),
51 m_fading(false)
52{
53 if (!mapping.get("file", m_filename))
54 {
55 log_warning << mapping.get_doc().get_filename() << "'file' tag missing" << std::endl;
56 }
57 else
58 {
59 parse_file(m_filename);
60 }
61
62 mapping.get("speed", m_speed);
63}
64
65TextScroller::TextScroller(const ReaderObject& root) :
66 controller(),
67 m_filename(),
68 m_lines(),
69 m_scroll(),
70 m_speed(DEFAULT_SPEED),
71 m_finished(false),
72 m_fading(false)
73{
74 parse_root(root);
75}
76
77void
78TextScroller::parse_file(const std::string& filename)
79{
80 register_translation_directory(filename);
81 auto doc = ReaderDocument::from_file(filename);
82 auto root = doc.get_root();
83 parse_root(root);
84}
85
86void
87TextScroller::parse_root(const ReaderObject& root)
88{
89 if (root.get_name() != "supertux-text")
90 {
91 throw std::runtime_error("File isn't a supertux-text file");
92 }
93 else
94 {
95 auto mapping = root.get_mapping();
96
97 int version = 1;
98 mapping.get("version", version);
99 if (version == 1)
100 {
101 log_info << "[" << mapping.get_doc().get_filename() << "] Text uses old format: version 1" << std::endl;
102
103 std::string text;
104 if (!mapping.get("text", text)) {
105 throw std::runtime_error("File doesn't contain a text field");
106 }
107
108 // Split text string lines into a vector
109 m_lines = InfoBoxLine::split(text, static_cast<float>(SCREEN_WIDTH) - 2.0f * LEFT_BORDER);
110 }
111 else if (version == 2)
112 {
113 boost::optional<ReaderCollection> content_collection;
114 if (!mapping.get("content", content_collection)) {
115 throw std::runtime_error("File doesn't contain content");
116 } else {
117 parse_content(*content_collection);
118 }
119 }
120 }
121}
122
123void
124TextScroller::parse_content(const ReaderCollection& collection)
125{
126 for (const auto& item : collection.get_objects())
127 {
128 if (item.get_name() == "image")
129 {
130 std::string image_file = item.get_sexp().as_array()[1].as_string();
131 m_lines.emplace_back(new InfoBoxLine('!', image_file));
132 }
133 else if (item.get_name() == "person")
134 {
135 bool simple;
136 std::string name, info, image_file;
137
138 if (!item.get_mapping().get("simple", simple)) {
139 simple = false;
140 }
141
142 if (simple) {
143 if (!item.get_mapping().get("name", name) || !item.get_mapping().get("info", info)) {
144 throw std::runtime_error("Simple entry requires both name and info specified");
145 }
146
147 if (item.get_mapping().get("image", image_file)) {
148 log_warning << "[" << collection.get_doc().get_filename() << "] Simple person entry shouldn't specify images" << std::endl;
149 }
150
151 m_lines.emplace_back(new InfoBoxLine(' ', name + " (" + info + ")")); // NOLINT
152 } else {
153 if (item.get_mapping().get("name", name)) {
154 m_lines.emplace_back(new InfoBoxLine('\t', name));
155 }
156
157 if (item.get_mapping().get("image", image_file) && !simple) {
158 m_lines.emplace_back(new InfoBoxLine('!', image_file));
159 }
160
161 if (item.get_mapping().get("info", info)) {
162 m_lines.emplace_back(new InfoBoxLine(' ', info));
163 }
164 }
165 }
166 else if (item.get_name() == "blank")
167 {
168 // Empty line
169 m_lines.emplace_back(new InfoBoxLine('\t', ""));
170 }
171 else if (item.get_name() == "text")
172 {
173 std::string type, string;
174
175 if (!item.get_mapping().get("type", type)) {
176 type = "normal";
177 }
178
179 if (!item.get_mapping().get("string", string)) {
180 throw std::runtime_error("Text entry requires a string");
181 }
182
183 if (type == "normal")
184 m_lines.emplace_back(new InfoBoxLine('\t', string));
185 else if (type == "normal-left")
186 m_lines.emplace_back(new InfoBoxLine('#', string));
187 else if (type == "small")
188 m_lines.emplace_back(new InfoBoxLine(' ', string));
189 else if (type == "heading")
190 m_lines.emplace_back(new InfoBoxLine('-', string));
191 else if (type == "reference")
192 m_lines.emplace_back(new InfoBoxLine('*', string));
193 else {
194 log_warning << "[" << item.get_doc().get_filename() << "] Unknown text type '" << type << "'" << std::endl;
195 m_lines.emplace_back(new InfoBoxLine('\t', string));
196 }
197 }
198 else
199 {
200 log_warning << "[" << item.get_doc().get_filename() << "] Unknown token '" << item.get_name() << "'" << std::endl;
201 }
202 }
203}
204
205void
206TextScroller::draw(DrawingContext& context)
207{
208 context.push_transform();
209 context.set_translation(Vector(0, 0));
210
211 const float ctx_w = static_cast<float>(context.get_width());
212 const float ctx_h = static_cast<float>(context.get_height());
213
214 float y = ctx_h - m_scroll;
215
216 { // draw text
217 for (const auto& line : m_lines)
218 {
219 if (y + line->get_height() >= 0 && ctx_h - y >= 0) {
220 line->draw(context, Rectf(LEFT_BORDER, y, ctx_w - 2*LEFT_BORDER, y), LAYER_GUI);
221 }
222
223 y += line->get_height();
224 }
225 }
226
227 context.pop_transform();
228
229 // close when done
230 if (y < 0)
231 {
232 m_finished = true;
233 }
234}
235
236void
237TextScroller::update(float dt_sec)
238{
239 m_scroll += m_speed * dt_sec;
240
241 if (m_scroll < 0)
242 m_scroll = 0;
243
244 if (controller.pressed(Control::START) ||
245 controller.pressed(Control::ESCAPE)) {
246 ScreenManager::current()->pop_screen(std::unique_ptr<ScreenFade>(new FadeToBlack(FadeToBlack::FADEOUT, 0.25)));
247 }
248
249 { // close when done
250 if (m_finished && !m_fading)
251 {
252 m_fading = true;
253 ScreenManager::current()->pop_screen(std::unique_ptr<ScreenFade>(new FadeToBlack(FadeToBlack::FADEOUT, 0.25)));
254 }
255 }
256}
257
258void
259TextScroller::set_speed(float speed)
260{
261 m_speed = speed;
262}
263
264void
265TextScroller::scroll(float offset)
266{
267 m_scroll += offset;
268 if (m_scroll < 0.0f)
269 {
270 m_scroll = 0.0f;
271 }
272}
273
274ObjectSettings
275TextScroller::get_settings()
276{
277 ObjectSettings result = GameObject::get_settings();
278
279 result.add_float(_("Speed"), &m_speed, "speed", DEFAULT_SPEED);
280 result.add_file(_("File"), &m_filename, "file");
281
282 return result;
283}
284
285/* EOF */
286