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