RigsofRods
Soft-body Physics Simulation
GUI_ConsoleView.cpp
Go to the documentation of this file.
1 /*
2  This source file is part of Rigs of Rods
3  Copyright 2005-2012 Pierre-Michel Ricordel
4  Copyright 2007-2012 Thomas Fischer
5  Copyright 2013-2020 Petr Ohlidal
6 
7  For more information, see http://www.rigsofrods.org/
8 
9  Rigs of Rods is free software: you can redistribute it and/or modify
10  it under the terms of the GNU General Public License version 3, as
11  published by the Free Software Foundation.
12 
13  Rigs of Rods is distributed in the hope that it will be useful,
14  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  GNU General Public License for more details.
17 
18  You should have received a copy of the GNU General Public License
19  along with Rigs of Rods. If not, see <http://www.gnu.org/licenses/>.
20 */
21 
22 
23 #include "GUI_ConsoleView.h"
24 
25 #include "Actor.h"
26 #include "Application.h"
27 #include "Console.h"
28 #include "GUIManager.h"
29 #include "GUIUtils.h"
30 #include "Language.h"
31 #include "Network.h"
32 
33 #include <algorithm> // min
34 #include <fmt/core.h>
35 #include <string>
36 
37 using namespace RoR;
38 using namespace GUI;
39 using namespace Ogre;
40 
41 inline void color2i(ImVec4 v, int&r, int&g, int&b) { r=(int)(v.x*255); g=(int)(v.y*255); b=(int)(v.z*255); }
42 
43 static const int LINE_BUF_MAX = 5000;
44 
46 {
47  // Update pre-filtered message list
48  int num_incoming = this->UpdateMessages();
49 
50  // Gather visible (non-expired) messages
51  const unsigned long curr_timestamp = App::GetConsole()->queryMessageTimer();
52  m_display_messages.clear();
53  std::string last_prefix;
54  for (Console::Message& m: m_filtered_messages)
55  {
56  if (cvw_msg_duration_ms == 0 || curr_timestamp <= m.cm_timestamp + cvw_msg_duration_ms)
57  {
58  if (m.cm_area == Console::CONSOLE_MSGTYPE_INFO && m.cm_type != Console::CONSOLE_SYSTEM_NETCHAT &&
59  m_display_messages.size() != 0 && !cvw_enable_scrolling && last_prefix == std::string(m.cm_text.substr(0, m.cm_text.find(" ")))) // same prefix with previous line
60  {
61  m_display_messages.pop_back(); // keep in same line
62  }
63  m_display_messages.push_back(&m);
64  last_prefix = m.cm_text.substr(0, m.cm_text.find(" "));
65  }
66  }
67 
68  // Prepare drawing
69  ImDrawList* drawlist = ImGui::GetWindowDrawList();
70  drawlist->ChannelsSplit(2); // 2 layers: 0=background, 1=text
71 
72  if (!cvw_enable_scrolling)
73  {
74  // Draw from bottom, messages may be multiline
75  ImVec2 cursor = ImGui::GetWindowPos() + ImVec2(0, ImGui::GetWindowHeight());
76  for (int i = m_display_messages.size() - 1; i >= 0; --i)
77  {
78  Console::Message const& m = *m_display_messages[i];
79  float msg_h = ImGui::CalcTextSize(m.cm_text.c_str()).y + (2 * cvw_background_padding.y) + cvw_line_spacing;
80  cursor -= ImVec2(0, msg_h);
81  if (cursor.y < ImGui::GetWindowPos().y)
82  {
83  break; // Window is filled up
84  }
85  this->DrawMessage(cursor, m);
86  }
87  }
88  else
89  {
90  // Add a dummy sized as messages to facilitate builtin scrolling
91  float line_h = ImGui::CalcTextSize("").y + (2 * cvw_background_padding.y) + cvw_line_spacing;
92  float dummy_h = line_h * (float)m_display_messages.size();
93  ImGui::SetCursorPosY(dummy_h);
94 
95  // Autoscroll
96  if (num_incoming != 0 && std::abs(ImGui::GetScrollMaxY() - ImGui::GetScrollY()) < line_h)
97  {
98  // it's evaluated on next frame, so we must exxagerate in advance.
99  ImGui::SetScrollY(ImGui::GetScrollMaxY() + (num_incoming * line_h) + 100.f);
100  }
101 
102  // Calculate visible area height
103  float view_height = ImGui::GetWindowHeight();
104  if (ImGui::GetScrollMaxX() > 0) // Account for horizontal scrollbar, if visible.
105  {
106  view_height -= ImGui::GetStyle().ScrollbarSize;
107  }
108 
109  // Calculate message range and cursor pos
110  int msg_start = 0, msg_count = 0;
111  ImVec2 cursor = ImGui::GetWindowPos();
112  if (ImGui::GetScrollMaxY() == 0) // No scrolling
113  {
114  msg_count = (int)m_display_messages.size();
115  cursor += ImVec2(0, view_height - (msg_count * line_h)); // Align to bottom
116  }
117  else // Scrolling
118  {
119  const float scroll_rel = ImGui::GetScrollY()/ImGui::GetScrollMaxY();
120  const float scroll_offset = ((dummy_h - view_height) * scroll_rel);
121  msg_start = std::max(0, (int)(scroll_offset/line_h));
122 
123  msg_count = std::min((int)(view_height / line_h)+2, // Bias (2) for partially visible messages (1 top, 1 bottom)
124  (int)m_display_messages.size() - msg_start);
125 
126  const float line_offset = scroll_offset/line_h;
127  if (cvw_smooth_scrolling)
128  {
129  cursor -= ImVec2(0, (line_offset - (float)(int)line_offset)*line_h);
130  }
131  }
132 
133  // Horizontal scrolling
134  if (ImGui::GetScrollMaxX() > 0)
135  {
136  cursor -= ImVec2(ImGui::GetScrollX(), 0);
137  }
138 
139  // Draw the messages
140  for (int i = msg_start; i < msg_start + msg_count; i++)
141  {
142  const Console::Message& m = *m_display_messages[i];
143 
144  ImVec2 text_size = this->DrawMessage(cursor, m);
145  ImGui::SetCursorPosX(text_size.x); // Enable horizontal scrolling
146 
147  cursor += ImVec2(0.f, line_h);
148  }
149  }
150 
151  // Finalize drawing
152  drawlist->ChannelsMerge();
153 }
154 
155 ImVec2 ConsoleView::DrawMessage(ImVec2 cursor, Console::Message const& m)
156 {
157  Str<LINE_BUF_MAX> line;
159 
160  const unsigned long curr_timestamp = App::GetConsole()->queryMessageTimer();
161  unsigned long overtime = curr_timestamp - (m.cm_timestamp + (cvw_msg_duration_ms - fadeout_interval));
162 
163  if (overtime > fadeout_interval)
164  {
165  alpha = 1.f;
166  }
167  else
168  {
169  alpha -= (float)overtime/(float)fadeout_interval;
170  }
171 
172  // Draw icons based on filters
173  Ogre::TexturePtr icon;
174  if (cvw_enable_icons)
175  {
176  if (m.cm_icon != "")
177  {
178  try
179  {
180  icon = Ogre::TextureManager::getSingleton().load(m.cm_icon, "IconsRG");
181  }
182  catch (...) {}
183  }
184  else if (m.cm_area == Console::MessageArea::CONSOLE_MSGTYPE_SCRIPT)
185  {
186  icon = Ogre::TextureManager::getSingleton().load("script.png", "IconsRG");
187  }
189  {
190  icon = Ogre::TextureManager::getSingleton().load("information.png", "IconsRG");
191  }
193  {
194  icon = Ogre::TextureManager::getSingleton().load("error.png", "IconsRG");
195  }
197  {
198  icon = Ogre::TextureManager::getSingleton().load("cancel.png", "IconsRG");
199  }
201  {
202  icon = Ogre::TextureManager::getSingleton().load("comment.png", "IconsRG");
203  }
204  }
205 
206 #if USE_SOCKETW
207  // Add colored multiplayer username
208  if (m.cm_net_userid)
209  {
210  RoRnet::UserInfo user;
211  if (App::GetNetwork()->GetAnyUserInfo((int)m.cm_net_userid, user) || // Local or remote user
212  App::GetNetwork()->GetDisconnectedUserInfo((int)m.cm_net_userid, user)) // Disconnected remote user
213  {
214  Ogre::ColourValue col = App::GetNetwork()->GetPlayerColor(user.colournum);
215  int r,g,b;
216  color2i(ImVec4(col.r, col.g, col.b, col.a), r,g,b);
217  line << fmt::format("#{:02x}{:02x}{:02x}{}: #000000{}", r, g, b, user.username, m.cm_text);
218  }
219  else // Invalid net userID, display anyway.
220  {
221  line = m.cm_text;
222  }
223  }
224  else // not multiplayer message
225  {
226  line = m.cm_text;
227  }
228 #else // USE_SOCKETW
229  line = m.cm_text;
230 #endif // USE_SOCKETW
231 
232  // Colorize text by type
233  ImVec4 base_color = ImGui::GetStyle().Colors[ImGuiCol_Text];
234  switch (m.cm_type)
235  {
236  case Console::CONSOLE_TITLE: base_color = theme.highlight_text_color; break;
237  case Console::CONSOLE_SYSTEM_ERROR: base_color = theme.error_text_color; break;
238  case Console::CONSOLE_SYSTEM_WARNING: base_color = theme.warning_text_color; break;
239  case Console::CONSOLE_SYSTEM_REPLY: base_color = theme.success_text_color; break;
240  case Console::CONSOLE_HELP: base_color = theme.help_text_color; break;
241  default:;
242  }
243 
244  return this->DrawColoredTextWithIcon(cursor, icon, base_color, line.ToCStr());
245 }
246 
248 {
249  ImGui::TextDisabled("%s", _LC("Console", "By area:"));
250  m_reload_messages |= ImGui::MenuItem(_LC("Console", "Logfile echo"), "", &cvw_filter_area_echo);
251  m_reload_messages |= ImGui::MenuItem(_LC("Console", "Scripting"), "", &cvw_filter_area_script);
252  m_reload_messages |= ImGui::MenuItem(_LC("Console", "Actors"), "", &cvw_filter_area_actor);
253  m_reload_messages |= ImGui::MenuItem(_LC("Console", "Terrain"), "", &cvw_filter_area_terrn);
254 
255  ImGui::Separator();
256  ImGui::TextDisabled("%s",_LC("Console", "By level:"));
257  m_reload_messages |= ImGui::MenuItem(_LC("Console", "Notices"), "", &cvw_filter_type_notice);
258  m_reload_messages |= ImGui::MenuItem(_LC("Console", "Warnings"), "", &cvw_filter_type_warning);
259  m_reload_messages |= ImGui::MenuItem(_LC("Console", "Errors"), "", &cvw_filter_type_error);
260  m_reload_messages |= ImGui::MenuItem(_LC("Console", "Net chat"), "", &cvw_filter_type_chat);
261  m_reload_messages |= ImGui::MenuItem(_LC("Console", "Commands"), "", &cvw_filter_type_cmd);
262 }
263 
265 {
266  const bool area_ok =
267  (m.cm_area == Console::MessageArea::CONSOLE_MSGTYPE_INFO) ||
268  (m.cm_area == Console::MessageArea::CONSOLE_MSGTYPE_LOG && cvw_filter_area_echo) ||
269  (m.cm_area == Console::MessageArea::CONSOLE_MSGTYPE_ACTOR && cvw_filter_area_actor) ||
270  (m.cm_area == Console::MessageArea::CONSOLE_MSGTYPE_TERRN && cvw_filter_area_terrn) ||
271  (m.cm_area == Console::MessageArea::CONSOLE_MSGTYPE_SCRIPT && cvw_filter_area_script);
272 
273  const bool type_ok =
274  (m.cm_type == Console::CONSOLE_HELP && cvw_filter_type_cmd) ||
275  (m.cm_type == Console::CONSOLE_TITLE && cvw_filter_type_cmd) ||
276  (m.cm_type == Console::CONSOLE_SYSTEM_REPLY && cvw_filter_type_cmd) ||
277  (m.cm_type == Console::CONSOLE_SYSTEM_ERROR && cvw_filter_type_error) ||
278  (m.cm_type == Console::CONSOLE_SYSTEM_WARNING && cvw_filter_type_warning) ||
279  (m.cm_type == Console::CONSOLE_SYSTEM_NOTICE && cvw_filter_type_notice) ||
280  (m.cm_type == Console::CONSOLE_SYSTEM_NETCHAT && cvw_filter_type_chat);
281 
282  return type_ok && area_ok;
283 }
284 
285 ImVec2 ConsoleView::DrawColoredTextWithIcon(ImVec2 bg_cursor, Ogre::TexturePtr icon, ImVec4 default_color, std::string const& line)
286 {
287  ImDrawList* drawlist = ImGui::GetWindowDrawList();
288 
289  // Print icon
290  drawlist->ChannelsSetCurrent(1); // Text layer
291  ImVec2 text_cursor = bg_cursor + cvw_background_padding;
292  ImVec2 indent_size(0,0);
293  float text_h = ImGui::CalcTextSize("").y;
294  if (cvw_enable_icons && icon)
295  {
296  ImVec2 icon_size(icon->getWidth(), icon->getHeight());
297  ImVec2 tl = ImVec2(text_cursor.x, text_cursor.y + (text_h / 2) - (icon_size.y / 2));
298  ImVec2 br = tl + icon_size;
299  drawlist->AddImage(reinterpret_cast<ImTextureID>(icon->getHandle()), tl, br, ImVec2(0,0), ImVec2(1,1), ImColor(ImVec4(1,1,1,alpha)));
300  const float ICON_GAP = 8;
301  indent_size = ImVec2(icon_size.x + ICON_GAP, text_h);
302  text_cursor.x += indent_size.x;
303  }
304 
305  // Print colored line segments
306  ImVec2 text_size = DrawColorMarkedText(drawlist, text_cursor, default_color, alpha, /*wrap_width=*/-1.f, line);
307  const ImVec2 total_text_size(indent_size.x + text_size.x, std::max(indent_size.y, text_size.y));
308 
309  // Draw background
310  drawlist->ChannelsSetCurrent(0); // Background layer
311  ImVec2 bg_rect_size = total_text_size + (cvw_background_padding * 2);
312  drawlist->AddRectFilled(bg_cursor, bg_cursor + bg_rect_size,
313  ImColor(ImVec4(0,0,0,(alpha / 2))), ImGui::GetStyle().FrameRounding);
314  return bg_rect_size;
315 }
316 
318 {
319  // Lock the console
321 
322  // Was console cleared?
323  if (lock.messages.size() < m_total_messages)
324  {
325  m_reload_messages = true;
326  }
327 
328  // Handle full reload
329  if (m_reload_messages)
330  {
331  m_filtered_messages.clear();
332  m_total_messages = 0;
333  m_reload_messages = false;
334  }
335 
336  // Apply filtering
337  int orig_size = (int)m_filtered_messages.size();
338  for (size_t i = m_total_messages; i < lock.messages.size() ; ++i)
339  {
340  Console::Message const& m = lock.messages[i];
341  if (this->MessageFilter(m))
342  {
343  if (cvw_enable_scrolling && m.cm_text.find("\n"))
344  {
345  // Keep it simple: only use single-line messages with scrolling view
346  Ogre::StringVector v = Ogre::StringUtil::split(m.cm_text, "\n"); // TODO: optimize
347  for (Ogre::String& s: v)
348  {
349  Ogre::StringUtil::trim(s);
350  if (s != "")
351  {
352  m_filtered_messages.emplace_back(m.cm_area, m.cm_type, s, m.cm_timestamp, m.cm_net_userid, m.cm_icon);
353  }
354  }
355  }
356  else
357  {
358  m_filtered_messages.push_back(m); // Copy
359  }
360  }
361  }
362  m_total_messages = lock.messages.size();
363 
364  return (int)m_filtered_messages.size() - orig_size;
365 }
RoR::GUI::ConsoleView::DrawColoredTextWithIcon
ImVec2 DrawColoredTextWithIcon(ImVec2 text_cursor, Ogre::TexturePtr icon, ImVec4 default_color, std::string const &line)
Returns final text size.
Definition: GUI_ConsoleView.cpp:285
RoR::App::GetNetwork
Network * GetNetwork()
Definition: Application.cpp:284
RoR::GUI::ConsoleView::MessageFilter
bool MessageFilter(Console::Message const &m)
Returns true if message should be displayed.
Definition: GUI_ConsoleView.cpp:264
RoR::Console::Message::cm_net_userid
uint32_t cm_net_userid
Definition: Console.h:77
RoR::GUI::ConsoleView::UpdateMessages
int UpdateMessages()
Ret. num of new message(s)
Definition: GUI_ConsoleView.cpp:317
RoR::GUIManager::GuiTheme
Definition: GUIManager.h:68
RoR::App::GetGuiManager
GUIManager * GetGuiManager()
Definition: Application.cpp:269
RoRnet::UserInfo
Definition: RoRnet.h:170
LINE_BUF_MAX
static const int LINE_BUF_MAX
Definition: GUI_ConsoleView.cpp:43
RoR::Console::Message::cm_timestamp
size_t cm_timestamp
Definition: Console.h:76
RoR::DrawColorMarkedText
ImVec2 DrawColorMarkedText(ImDrawList *drawlist, ImVec2 text_cursor, ImVec4 default_color, float override_alpha, float wrap_width, std::string const &line)
Draw multiline text with '#rrggbb' color markers. Returns total text size.
Definition: GUIUtils.cpp:204
format
Truck file format(technical spec)
GUIUtils.h
RoR::GUI::ConsoleView::DrawConsoleMessages
void DrawConsoleMessages()
Definition: GUI_ConsoleView.cpp:45
GUI_ConsoleView.h
Generic console rendering.
Console.h
RoR::Console::CONSOLE_TITLE
@ CONSOLE_TITLE
Definition: Console.h:49
RoR::GUI::ConsoleView::DrawMessage
ImVec2 DrawMessage(ImVec2 cursor, Console::Message const &m)
Definition: GUI_ConsoleView.cpp:155
RoR::Console::CONSOLE_SYSTEM_NETCHAT
@ CONSOLE_SYSTEM_NETCHAT
Definition: Console.h:55
Language.h
RoR::Console::CONSOLE_SYSTEM_ERROR
@ CONSOLE_SYSTEM_ERROR
Definition: Console.h:52
GUIManager.h
Actor.h
RoR::Console::Message::cm_text
std::string cm_text
Definition: Console.h:78
RoR::Console::MsgLockGuard::messages
std::vector< Message > & messages
Definition: Console.h:91
RoR::Console::CONSOLE_SYSTEM_NOTICE
@ CONSOLE_SYSTEM_NOTICE
Definition: Console.h:51
RoR::Console::Message
Definition: Console.h:67
RoR::Console::MsgLockGuard
Definition: Console.h:82
RoR::GUIManager::GetTheme
GuiTheme & GetTheme()
Definition: GUIManager.h:154
RoR::Str
Wrapper for classic c-string (local buffer) Refresher: strlen() excludes '\0' terminator; strncat() A...
Definition: Str.h:35
RoR::Network::GetDisconnectedUserInfo
bool GetDisconnectedUserInfo(int uid, RoRnet::UserInfo &result)
Definition: Network.cpp:723
RoR::Str::ToCStr
const char * ToCStr() const
Definition: Str.h:46
RoR::Console::queryMessageTimer
unsigned long queryMessageTimer()
Definition: Console.h:98
RoR::GUIManager::GuiTheme::warning_text_color
ImVec4 warning_text_color
Definition: GUIManager.h:78
RoR::GUI::ConsoleView::DrawFilteringOptions
void DrawFilteringOptions()
Definition: GUI_ConsoleView.cpp:247
Application.h
Central state/object manager and communications hub.
RoR::App::GetConsole
Console * GetConsole()
Definition: Application.cpp:270
RoR::GUIManager::GuiTheme::success_text_color
ImVec4 success_text_color
Definition: GUIManager.h:77
RoR::Console::CONSOLE_SYSTEM_REPLY
@ CONSOLE_SYSTEM_REPLY
Success.
Definition: Console.h:54
tl
This is a raw Ogre binding for Imgui No project cmake no just four source because If you re familiar with integration should be pretty straightforward tl
Definition: README-OgreImGui.txt:8
_LC
#define _LC(ctx, str)
Definition: Language.h:42
RoR::Console::Message::cm_area
MessageArea cm_area
Definition: Console.h:74
RoR::Network::GetPlayerColor
Ogre::ColourValue GetPlayerColor(int color_num)
Definition: Network.cpp:94
RoRnet::UserInfo::colournum
int32_t colournum
colour set by server
Definition: RoRnet.h:175
RoRnet::UserInfo::username
char username[RORNET_MAX_USERNAME_LEN]
the nickname of the user (UTF-8)
Definition: RoRnet.h:177
RoR::GUIManager::GuiTheme::help_text_color
ImVec4 help_text_color
Definition: GUIManager.h:79
RoR::GUIManager::GuiTheme::highlight_text_color
ImVec4 highlight_text_color
Definition: GUIManager.h:76
RoR::Console::Message::cm_icon
std::string cm_icon
Definition: Console.h:79
RoR::Console::Message::cm_type
MessageType cm_type
Definition: Console.h:75
color2i
void color2i(ImVec4 v, int &r, int &g, int &b)
Definition: GUI_ConsoleView.cpp:41
Ogre
Definition: ExtinguishableFireAffector.cpp:35
RoR::Console::CONSOLE_SYSTEM_WARNING
@ CONSOLE_SYSTEM_WARNING
Definition: Console.h:53
RoR::Console::CONSOLE_MSGTYPE_INFO
@ CONSOLE_MSGTYPE_INFO
Generic message.
Definition: Console.h:60
RoR::GUIManager::GuiTheme::error_text_color
ImVec4 error_text_color
Definition: GUIManager.h:72
RoR::Console::CONSOLE_HELP
@ CONSOLE_HELP
Definition: Console.h:48
RoR
Definition: AppContext.h:36
Network.h