1 /**
2  * @file
3  * @brief Formatted scroller
4 **/
5 
6 #include "AppHdr.h"
7 
8 #include "scroller.h"
9 #include "stringutil.h"
10 
11 using namespace ui;
12 
13 static vector<formatted_scroller*> open_scrollers;
14 static bool from_webtiles;
15 
_line_height()16 static int _line_height()
17 {
18 #ifdef USE_TILE_LOCAL
19     return tiles.get_crt_font()->char_height();
20 #else
21     return 1;
22 #endif
23 }
24 
add_formatted_string(const formatted_string & fs,bool new_line)25 void formatted_scroller::add_formatted_string(const formatted_string& fs, bool new_line)
26 {
27     contents += fs;
28     if (new_line)
29         contents.cprintf("\n");
30     m_contents_dirty = true;
31 }
32 
add_text(const string & s,bool new_line)33 void formatted_scroller::add_text(const string& s, bool new_line)
34 {
35     add_formatted_string(formatted_string::parse_string(s), new_line);
36 }
37 
add_raw_text(const string & s,bool new_line)38 void formatted_scroller::add_raw_text(const string& s, bool new_line)
39 {
40     contents.cprintf("%s%s", s.c_str(), new_line ? "\n" : "");
41     m_contents_dirty = true;
42 }
43 
44 class UIHookedScroller : public Scroller
45 {
46 public:
UIHookedScroller(formatted_scroller & _fs)47     UIHookedScroller(formatted_scroller &_fs) : Scroller(), fs(_fs) {};
set_scroll(int y)48     virtual void set_scroll(int y) override {
49         if (y == m_scroll)
50             return;
51 #ifdef USE_TILE_WEB
52         tiles.json_open_object();
53         tiles.json_write_bool("from_webtiles", from_webtiles);
54         tiles.json_write_int("scroll", y / _line_height());
55         tiles.ui_state_change("formatted-scroller", 1);
56 #endif
57         Scroller::set_scroll(y);
58     };
59 protected:
60     formatted_scroller &fs;
61 };
62 
scroll_to_end()63 void formatted_scroller::scroll_to_end()
64 {
65     // this needs to match the value in ui-layout.js scroller_scroll_to_line
66     // (TODO: why?)
67     m_scroll = numeric_limits<int32_t>::max();
68     m_scroll_dirty = true;
69 }
70 
show()71 int formatted_scroller::show()
72 {
73     auto vbox = make_shared<Box>(Widget::VERT);
74     vbox->set_cross_alignment(Widget::Align::STRETCH);
75 
76     if (!m_title.empty())
77     {
78         shared_ptr<Text> title = make_shared<Text>();
79         title->set_text(m_title);
80         title->set_margin_for_crt(0, 0, 1, 0);
81         title->set_margin_for_sdl(0, 0, 20, 0);
82         auto title_hbox = make_shared<Box>(Widget::HORZ);
83 #ifdef USE_TILE_LOCAL
84         title_hbox->set_main_alignment(Widget::Align::CENTER);
85 #endif
86         title_hbox->add_child(move(title));
87         vbox->add_child(move(title_hbox));
88     }
89 
90 #ifdef USE_TILE_LOCAL
91     if (!(m_flags & FS_PREWRAPPED_TEXT))
92         vbox->max_size().width = tiles.get_crt_font()->char_width()*80;
93 #endif
94 
95     m_scroller = make_shared<UIHookedScroller>(*this);
96 #ifndef USE_TILE_LOCAL // ensure CRT scroller uses full height
97     m_scroller->expand_v = true;
98 #endif
99     auto text = make_shared<Text>();
100     formatted_string c = formatted_string::parse_string(contents.to_colour_string());
101     text->set_text(c);
102     text->set_highlight_pattern(highlight, true);
103     text->set_wrap_text(!(m_flags & FS_PREWRAPPED_TEXT));
104     m_scroller->set_child(text);
105     vbox->add_child(m_scroller);
106 
107     if (!m_more.empty())
108     {
109         shared_ptr<Text> more = make_shared<Text>();
110         more = make_shared<Text>();
111         more->set_text(m_more);
112         more->set_margin_for_crt(1, 0, 0, 0);
113         more->set_margin_for_sdl(20, 0, 0, 0);
114         vbox->add_child(move(more));
115     }
116 
117     auto popup = make_shared<ui::Popup>(vbox);
118 
119     m_contents_dirty = false;
120     bool done = false;
121     popup->on_keydown_event([&done, &text, this](const KeyEvent& ev) {
122         m_lastch = ev.key();
123         done = !process_key(m_lastch);
124         if (m_contents_dirty)
125         {
126             m_contents_dirty = false;
127             text->set_text(contents);
128 #ifdef USE_TILE_WEB
129             tiles.json_open_object();
130             tiles.json_write_string("text", contents.to_colour_string());
131             tiles.json_write_string("highlight", highlight);
132             tiles.ui_state_change("formatted-scroller", 0);
133 #endif
134         }
135         if (m_scroll_dirty)
136         {
137             m_scroll_dirty = false;
138             m_scroller->set_scroll(m_scroll);
139         }
140         if (done)
141             return true;
142         if (m_scroller->on_event(ev))
143             return true;
144         if (m_flags & FS_EASY_EXIT)
145             return done = true;
146         return true;
147     });
148 
149 #ifdef USE_TILE_WEB
150     tiles.json_open_object();
151     tiles.json_write_string("tag", m_tag);
152     tiles.json_write_string("text", contents.to_colour_string());
153     tiles.json_write_string("highlight", highlight);
154     tiles.json_write_string("more", m_more.to_colour_string());
155     tiles.json_write_bool("start_at_end", m_flags & FS_START_AT_END);
156     tiles.push_ui_layout("formatted-scroller", 2);
157     popup->on_layout_pop([](){ tiles.pop_ui_layout(); });
158 #endif
159 
160     if (m_flags & FS_START_AT_END)
161         scroll_to_end();
162 
163     open_scrollers.push_back(this);
164     if (m_scroll_dirty)
165     {
166         m_scroll_dirty = false;
167         m_scroller->set_scroll(m_scroll);
168     }
169 
170     ui::run_layout(move(popup), done);
171     open_scrollers.pop_back();
172 
173     return m_lastch;
174 }
175 
process_key(int ch)176 bool formatted_scroller::process_key(int ch)
177 {
178     switch (ch)
179     {
180         case CK_MOUSE_CMD:
181         CASE_ESCAPE
182             return false;
183         default:
184             return true;
185     }
186 }
187 
set_scroll(int y)188 void formatted_scroller::set_scroll(int y)
189 {
190     if (from_webtiles)
191         m_scroller->set_scroll(y);
192     else
193     {
194         m_scroll = y;
195         m_scroll_dirty = true;
196     }
197 }
198 
recv_formatted_scroller_scroll(int line)199 void recv_formatted_scroller_scroll(int line)
200 {
201     if (open_scrollers.size() == 0)
202         return;
203     formatted_scroller *scroller = open_scrollers.back();
204     from_webtiles = true;
205     scroller->set_scroll(line*_line_height());
206     from_webtiles = false;
207     // XXX: since the scroll event from webtiles is not delivered by the event
208     // pumping loop in ui::pump_events, the UI system won't automatically draw
209     // any changes for console spectators, so we need to force a redraw here.
210     ui::force_render();
211 }
212