Rigs of Rods 2023.09
Soft-body Physics Simulation
Loading...
Searching...
No Matches
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
37using namespace RoR;
38using namespace GUI;
39using namespace Ogre;
40
41inline 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
43static 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;
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
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 {
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;
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 {
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
155ImVec2 ConsoleView::DrawMessage(ImVec2 cursor, Console::Message const& m)
156{
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;
175 {
176 if (m.cm_icon != "")
177 {
178 try
179 {
180 icon = Ogre::TextureManager::getSingleton().load(m.cm_icon, "IconsRG");
181 }
182 catch (...) {}
183 }
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
284
285ImVec2 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
330 {
331 m_filtered_messages.clear();
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 {
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}
Central state/object manager and communications hub.
void color2i(ImVec4 v, int &r, int &g, int &b)
static const int LINE_BUF_MAX
Generic console rendering.
#define _LC(ctx, str)
Definition Language.h:38
unsigned long queryMessageTimer()
Definition Console.h:98
@ CONSOLE_MSGTYPE_ACTOR
Parsing/spawn/simulation messages for actors.
Definition Console.h:63
@ CONSOLE_MSGTYPE_SCRIPT
Messages sent from scripts.
Definition Console.h:62
@ CONSOLE_MSGTYPE_INFO
Generic message.
Definition Console.h:60
@ CONSOLE_MSGTYPE_TERRN
Parsing/spawn/simulation messages for terrain.
Definition Console.h:64
@ CONSOLE_MSGTYPE_LOG
Logfile echo.
Definition Console.h:61
@ CONSOLE_SYSTEM_ERROR
Definition Console.h:52
@ CONSOLE_SYSTEM_NOTICE
Definition Console.h:51
@ CONSOLE_SYSTEM_WARNING
Definition Console.h:53
@ CONSOLE_SYSTEM_REPLY
Success.
Definition Console.h:54
@ CONSOLE_SYSTEM_NETCHAT
Definition Console.h:55
@ CONSOLE_TITLE
Definition Console.h:49
GuiTheme & GetTheme()
Definition GUIManager.h:168
Ogre::ColourValue GetPlayerColor(int color_num)
Definition Network.cpp:94
bool GetDisconnectedUserInfo(int uid, RoRnet::UserInfo &result)
Definition Network.cpp:753
Wrapper for classic c-string (local buffer) Refresher: strlen() excludes '\0' terminator; strncat() A...
Definition Str.h:36
const char * ToCStr() const
Definition Str.h:46
GUIManager * GetGuiManager()
Console * GetConsole()
Network * GetNetwork()
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:230
std::string cm_icon
Definition Console.h:79
MessageType cm_type
Definition Console.h:75
uint32_t cm_net_userid
Definition Console.h:77
std::string cm_text
Definition Console.h:78
MessageArea cm_area
Definition Console.h:74
std::vector< Message > & messages
Definition Console.h:91
bool cvw_filter_area_echo
Not the same thing as 'log' command!
ImVec2 DrawMessage(ImVec2 cursor, Console::Message const &m)
size_t cvw_msg_duration_ms
Message expiration; 0 means unlimited.
int UpdateMessages()
Ret. num of new message(s)
std::vector< Console::Message > m_filtered_messages
Updated as needed.
bool MessageFilter(Console::Message const &m)
Returns true if message should be displayed.
std::vector< const Console::Message * > m_display_messages
Rebuilt every frame; kept as member to reuse allocated memory.
ImVec2 DrawColoredTextWithIcon(ImVec2 text_cursor, Ogre::TexturePtr icon, ImVec4 default_color, std::string const &line)
Returns final text size.
char username[RORNET_MAX_USERNAME_LEN]
the nickname of the user (UTF-8)
Definition RoRnet.h:196
int32_t colournum
colour set by server
Definition RoRnet.h:194