| 1 | |
|---|
| 2 | // HighConsole.cpp |
|---|
| 3 | // On-screen debug console. |
|---|
| 4 | // |
|---|
| 5 | // Copyright (c) 2009 Michael Imamura. |
|---|
| 6 | // |
|---|
| 7 | // Licensed under GrokkSoft HoverRace SourceCode License v1.0(the "License"); |
|---|
| 8 | // you may not use this file except in compliance with the License. |
|---|
| 9 | // |
|---|
| 10 | // A copy of the license should have been attached to the package from which |
|---|
| 11 | // you have taken this file. If you can not find the license you can not use |
|---|
| 12 | // this file. |
|---|
| 13 | // |
|---|
| 14 | // |
|---|
| 15 | // The author makes no representations about the suitability of |
|---|
| 16 | // this software for any purpose. It is provided "as is" "AS IS", |
|---|
| 17 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
|---|
| 18 | // implied. |
|---|
| 19 | // |
|---|
| 20 | // See the License for the specific language governing permissions |
|---|
| 21 | // and limitations under the License. |
|---|
| 22 | |
|---|
| 23 | #include "StdAfx.h" |
|---|
| 24 | |
|---|
| 25 | #include <deque> |
|---|
| 26 | |
|---|
| 27 | #include <boost/thread/locks.hpp> |
|---|
| 28 | |
|---|
| 29 | #include "../../engine/Script/Core.h" |
|---|
| 30 | #include "../../engine/Util/Config.h" |
|---|
| 31 | #include "../../engine/VideoServices/2DViewPort.h" |
|---|
| 32 | #include "../../engine/VideoServices/StaticText.h" |
|---|
| 33 | #include "../../engine/VideoServices/VideoBuffer.h" |
|---|
| 34 | |
|---|
| 35 | #include "HighConsole.h" |
|---|
| 36 | |
|---|
| 37 | using namespace HoverRace; |
|---|
| 38 | using HoverRace::Util::Config; |
|---|
| 39 | using HoverRace::Util::OS; |
|---|
| 40 | using HoverRace::VideoServices::Font; |
|---|
| 41 | using HoverRace::VideoServices::StaticText; |
|---|
| 42 | |
|---|
| 43 | #ifdef max |
|---|
| 44 | # undef max |
|---|
| 45 | #endif |
|---|
| 46 | |
|---|
| 47 | namespace { |
|---|
| 48 | static const std::string COMMAND_PROMPT(">> "); |
|---|
| 49 | static const std::string CONTINUE_PROMPT(":> "); |
|---|
| 50 | } |
|---|
| 51 | |
|---|
| 52 | namespace HoverRace { |
|---|
| 53 | namespace Client { |
|---|
| 54 | |
|---|
| 55 | class HighConsole::LogLines |
|---|
| 56 | { |
|---|
| 57 | public: |
|---|
| 58 | LogLines(); |
|---|
| 59 | ~LogLines(); |
|---|
| 60 | |
|---|
| 61 | public: |
|---|
| 62 | int GetHeight() const { return height; } |
|---|
| 63 | |
|---|
| 64 | public: |
|---|
| 65 | void Add(const std::string &s, const Font &font, MR_UInt8 color); |
|---|
| 66 | void Clear(); |
|---|
| 67 | |
|---|
| 68 | void Render(MR_2DViewPort *vp, int x, int y); |
|---|
| 69 | |
|---|
| 70 | private: |
|---|
| 71 | typedef std::deque<StaticText*> lines_t; |
|---|
| 72 | lines_t lines; |
|---|
| 73 | int height; |
|---|
| 74 | |
|---|
| 75 | static const int MAX_LINES = 10; |
|---|
| 76 | }; |
|---|
| 77 | |
|---|
| 78 | HighConsole::HighConsole(Script::Core *scripting) : |
|---|
| 79 | SUPER(scripting), visible(false), cursorOn(true), cursorTick(0) |
|---|
| 80 | { |
|---|
| 81 | vp = new MR_2DViewPort(); |
|---|
| 82 | |
|---|
| 83 | logFont = new Font("Courier New", 16); |
|---|
| 84 | |
|---|
| 85 | commandPrompt = new StaticText(COMMAND_PROMPT, *logFont, 0x0a, StaticText::EFFECT_SHADOW); |
|---|
| 86 | continuePrompt = new StaticText(CONTINUE_PROMPT, *logFont, 0x0a, StaticText::EFFECT_SHADOW); |
|---|
| 87 | cursor = new StaticText("_", *logFont, 0x0e, StaticText::EFFECT_SHADOW); |
|---|
| 88 | |
|---|
| 89 | charWidth = commandPrompt->GetWidth() / COMMAND_PROMPT.length(); |
|---|
| 90 | consoleWidth = 800; // Will be corrected on first rendered frame. |
|---|
| 91 | |
|---|
| 92 | submitBuffer.reserve(1024); |
|---|
| 93 | historyBuffer.reserve(1024); |
|---|
| 94 | commandLine.reserve(1024); |
|---|
| 95 | commandLineDisplay = new StaticText("", *logFont, 0x0a, StaticText::EFFECT_SHADOW); |
|---|
| 96 | |
|---|
| 97 | logLines = new LogLines(); |
|---|
| 98 | |
|---|
| 99 | // Introductory text for the console log. |
|---|
| 100 | Config *cfg = cfg->GetInstance(); |
|---|
| 101 | logLines->Add(PACKAGE_NAME " version " + cfg->GetVersion(), |
|---|
| 102 | Font("Arial", 20, true), 0x0a); |
|---|
| 103 | |
|---|
| 104 | Script::Core *env = GetScripting(); |
|---|
| 105 | std::string intro = env->GetVersionString(); |
|---|
| 106 | intro += " :: Console active."; |
|---|
| 107 | logLines->Add(intro, *logFont, 0x0e); |
|---|
| 108 | } |
|---|
| 109 | |
|---|
| 110 | HighConsole::~HighConsole() |
|---|
| 111 | { |
|---|
| 112 | delete logLines; |
|---|
| 113 | |
|---|
| 114 | delete cursor; |
|---|
| 115 | delete continuePrompt; |
|---|
| 116 | delete commandPrompt; |
|---|
| 117 | delete commandLineDisplay; |
|---|
| 118 | |
|---|
| 119 | delete logFont; |
|---|
| 120 | |
|---|
| 121 | delete vp; |
|---|
| 122 | } |
|---|
| 123 | |
|---|
| 124 | void HighConsole::Advance(Util::OS::timestamp_t tick) |
|---|
| 125 | { |
|---|
| 126 | // Cursor visibility is based on the last character typed |
|---|
| 127 | // (so that the cursor stays visible while typing). |
|---|
| 128 | cursorOn = (OS::TimeDiff(tick, cursorTick) % 1000) < 500; |
|---|
| 129 | |
|---|
| 130 | if (submitBuffer.empty()) return; |
|---|
| 131 | |
|---|
| 132 | std::string history; |
|---|
| 133 | std::string chunk; |
|---|
| 134 | { |
|---|
| 135 | // See OnChar() for why we're careful about this. |
|---|
| 136 | boost::lock_guard<boost::mutex> lock(submitBufferMutex); |
|---|
| 137 | history.swap(historyBuffer); |
|---|
| 138 | chunk.swap(submitBuffer); |
|---|
| 139 | } |
|---|
| 140 | |
|---|
| 141 | // Add the history lines to the log. |
|---|
| 142 | // We assume that each line in the log terminates with "\n". |
|---|
| 143 | std::string line; |
|---|
| 144 | line.reserve(1024); |
|---|
| 145 | for (std::string::iterator iter = history.begin(); |
|---|
| 146 | iter != history.end(); ++iter) |
|---|
| 147 | { |
|---|
| 148 | if (*iter == '\n') { |
|---|
| 149 | logLines->Add(line, *logFont, 0x0a); |
|---|
| 150 | line.clear(); |
|---|
| 151 | } |
|---|
| 152 | else { |
|---|
| 153 | line += *iter; |
|---|
| 154 | } |
|---|
| 155 | } |
|---|
| 156 | |
|---|
| 157 | SubmitChunk(chunk); |
|---|
| 158 | } |
|---|
| 159 | |
|---|
| 160 | void HighConsole::Clear() |
|---|
| 161 | { |
|---|
| 162 | logLines->Clear(); |
|---|
| 163 | } |
|---|
| 164 | |
|---|
| 165 | /** |
|---|
| 166 | * Add a log entry, pre-processing the string for display purposes. |
|---|
| 167 | * @param s The log string. We assume that the trailing "\n" has been stripped. |
|---|
| 168 | * @param color The color. |
|---|
| 169 | */ |
|---|
| 170 | void HighConsole::AddLogEntry(const std::string &s, MR_UInt8 color) |
|---|
| 171 | { |
|---|
| 172 | std::string buf; |
|---|
| 173 | buf.reserve(1024); |
|---|
| 174 | int i = 0; |
|---|
| 175 | for (std::string::const_iterator iter = s.begin(); iter != s.end(); |
|---|
| 176 | ++iter, ++i) |
|---|
| 177 | { |
|---|
| 178 | char c = *iter; |
|---|
| 179 | switch (c) { |
|---|
| 180 | case '\t': |
|---|
| 181 | buf.resize(buf.length() + (8 - (buf.length() % 8)), ' '); |
|---|
| 182 | break; |
|---|
| 183 | |
|---|
| 184 | case '\n': |
|---|
| 185 | logLines->Add(buf, *logFont, color); |
|---|
| 186 | buf.clear(); |
|---|
| 187 | break; |
|---|
| 188 | |
|---|
| 189 | default: |
|---|
| 190 | if (c >= 32 && c < 127) { |
|---|
| 191 | // Line wrap if necessary. |
|---|
| 192 | if ((buf.length() + 1) * charWidth > consoleWidth) { |
|---|
| 193 | logLines->Add(buf, *logFont, color); |
|---|
| 194 | buf.clear(); |
|---|
| 195 | } |
|---|
| 196 | buf += c; |
|---|
| 197 | } |
|---|
| 198 | } |
|---|
| 199 | } |
|---|
| 200 | logLines->Add(buf, *logFont, color); |
|---|
| 201 | } |
|---|
| 202 | |
|---|
| 203 | void HighConsole::LogInfo(const std::string &s) |
|---|
| 204 | { |
|---|
| 205 | AddLogEntry(s, 0x0a); |
|---|
| 206 | } |
|---|
| 207 | |
|---|
| 208 | void HighConsole::LogError(const std::string &s) |
|---|
| 209 | { |
|---|
| 210 | AddLogEntry(s, 0x23); |
|---|
| 211 | } |
|---|
| 212 | |
|---|
| 213 | /** |
|---|
| 214 | * Handle a character keypress. |
|---|
| 215 | * Assume that the keypress is always consumed. |
|---|
| 216 | * @param c The character. |
|---|
| 217 | */ |
|---|
| 218 | void HighConsole::OnChar(char c) |
|---|
| 219 | { |
|---|
| 220 | cursorTick = OS::Time(); |
|---|
| 221 | |
|---|
| 222 | switch (c) { |
|---|
| 223 | case 8: // Backspace. |
|---|
| 224 | case 127: // DEL. |
|---|
| 225 | if (commandLine.length() > 0) { |
|---|
| 226 | commandLine.resize(commandLine.length() - 1); |
|---|
| 227 | //TODO: Update wrapped version of line. |
|---|
| 228 | } |
|---|
| 229 | break; |
|---|
| 230 | case 13: // CR. |
|---|
| 231 | commandLine += '\n'; |
|---|
| 232 | { |
|---|
| 233 | // We assume that OnChar() will probably be called on one |
|---|
| 234 | // thread while Advance() is called on another. |
|---|
| 235 | boost::lock_guard<boost::mutex> lock(submitBufferMutex); |
|---|
| 236 | historyBuffer += |
|---|
| 237 | (GetInputState() == ISTATE_COMMAND) ? |
|---|
| 238 | COMMAND_PROMPT : |
|---|
| 239 | CONTINUE_PROMPT; |
|---|
| 240 | historyBuffer += commandLine; |
|---|
| 241 | submitBuffer += commandLine; |
|---|
| 242 | } |
|---|
| 243 | commandLine.clear(); |
|---|
| 244 | break; |
|---|
| 245 | default: |
|---|
| 246 | if (c >= 32 && c <= 126) { |
|---|
| 247 | commandLine += c; |
|---|
| 248 | //TODO: Update wrapped version of line. |
|---|
| 249 | } |
|---|
| 250 | } |
|---|
| 251 | } |
|---|
| 252 | |
|---|
| 253 | /** |
|---|
| 254 | * Renders the console. |
|---|
| 255 | * @param dest The destination viewport (may not be @c NULL). |
|---|
| 256 | */ |
|---|
| 257 | void HighConsole::Render(MR_VideoBuffer *dest) |
|---|
| 258 | { |
|---|
| 259 | if (!visible) return; |
|---|
| 260 | |
|---|
| 261 | vp->Setup(dest, 0, 0, dest->GetXRes(), dest->GetYRes()); |
|---|
| 262 | |
|---|
| 263 | const int viewHeight = vp->GetYRes(); |
|---|
| 264 | const int viewWidth = vp->GetXRes(); |
|---|
| 265 | |
|---|
| 266 | consoleWidth = viewWidth - (PADDING_LEFT * 2); |
|---|
| 267 | |
|---|
| 268 | // Prepare the command-line. |
|---|
| 269 | // Select prompt based on state. |
|---|
| 270 | const StaticText *prompt = |
|---|
| 271 | (GetInputState() == ISTATE_COMMAND) ? |
|---|
| 272 | commandPrompt : |
|---|
| 273 | continuePrompt; |
|---|
| 274 | commandLineDisplay->SetText(commandLine); |
|---|
| 275 | |
|---|
| 276 | // Calculate the total height of the console (for background). |
|---|
| 277 | int commandLineHeight = |
|---|
| 278 | std::max(prompt->GetHeight(), |
|---|
| 279 | std::max(commandLineDisplay->GetHeight(), cursor->GetHeight())); |
|---|
| 280 | int totalHeight = |
|---|
| 281 | PADDING_TOP + |
|---|
| 282 | logLines->GetHeight() + |
|---|
| 283 | commandLineHeight + |
|---|
| 284 | PADDING_BOTTOM; |
|---|
| 285 | |
|---|
| 286 | // Draw the background. |
|---|
| 287 | for (int ly = viewHeight - totalHeight - 1; |
|---|
| 288 | ly < viewHeight - commandLineHeight - PADDING_BOTTOM; ++ly) |
|---|
| 289 | { |
|---|
| 290 | vp->DrawHorizontalLine(ly, 0, viewWidth - 1, 0x18); |
|---|
| 291 | } |
|---|
| 292 | for (int i = 0, ly = viewHeight - commandLineHeight - PADDING_BOTTOM; |
|---|
| 293 | ly < viewHeight; ++i, ++ly) |
|---|
| 294 | { |
|---|
| 295 | vp->DrawHorizontalLine(ly, 0, viewWidth - 1, 0x1a - ((i >= 5) ? 5 : i)); |
|---|
| 296 | } |
|---|
| 297 | |
|---|
| 298 | int x = 0; |
|---|
| 299 | int y = viewHeight - totalHeight + PADDING_TOP; |
|---|
| 300 | |
|---|
| 301 | // Render the log lines. |
|---|
| 302 | logLines->Render(vp, PADDING_LEFT, y); |
|---|
| 303 | y += logLines->GetHeight() + (PADDING_BOTTOM / 2); |
|---|
| 304 | |
|---|
| 305 | // Render the command line. |
|---|
| 306 | x = PADDING_LEFT; |
|---|
| 307 | prompt->Blt(x, y, vp); |
|---|
| 308 | x += prompt->GetWidth(); |
|---|
| 309 | commandLineDisplay->Blt(x, y, vp); |
|---|
| 310 | if (cursorOn) { |
|---|
| 311 | x += commandLineDisplay->GetWidth(); |
|---|
| 312 | cursor->Blt(x, y, vp); |
|---|
| 313 | } |
|---|
| 314 | } |
|---|
| 315 | |
|---|
| 316 | HighConsole::LogLines::LogLines() : |
|---|
| 317 | lines(), height(0) |
|---|
| 318 | { |
|---|
| 319 | } |
|---|
| 320 | |
|---|
| 321 | HighConsole::LogLines::~LogLines() |
|---|
| 322 | { |
|---|
| 323 | Clear(); |
|---|
| 324 | } |
|---|
| 325 | |
|---|
| 326 | void HighConsole::LogLines::Add(const std::string &s, const Font &font, MR_UInt8 color) |
|---|
| 327 | { |
|---|
| 328 | // Remove the top line if we're full. |
|---|
| 329 | if (lines.size() == MAX_LINES) { |
|---|
| 330 | StaticText *line = lines.front(); |
|---|
| 331 | height -= line->GetHeight(); |
|---|
| 332 | delete line; |
|---|
| 333 | lines.pop_front(); |
|---|
| 334 | } |
|---|
| 335 | |
|---|
| 336 | // Add the new line. |
|---|
| 337 | StaticText *line = new StaticText(s, font, color); |
|---|
| 338 | height += line->GetHeight(); |
|---|
| 339 | lines.push_back(line); |
|---|
| 340 | } |
|---|
| 341 | |
|---|
| 342 | void HighConsole::LogLines::Clear() |
|---|
| 343 | { |
|---|
| 344 | for (lines_t::iterator iter = lines.begin(); iter != lines.end(); ++iter) |
|---|
| 345 | { |
|---|
| 346 | delete *iter; |
|---|
| 347 | } |
|---|
| 348 | lines.clear(); |
|---|
| 349 | height = 0; |
|---|
| 350 | } |
|---|
| 351 | |
|---|
| 352 | void HighConsole::LogLines::Render(MR_2DViewPort *vp, int x, int y) |
|---|
| 353 | { |
|---|
| 354 | for (lines_t::iterator iter = lines.begin(); iter != lines.end(); ++iter) { |
|---|
| 355 | const StaticText *line = *iter; |
|---|
| 356 | line->Blt(x, y, vp); |
|---|
| 357 | y += line->GetHeight(); |
|---|
| 358 | } |
|---|
| 359 | } |
|---|
| 360 | |
|---|
| 361 | } // namespace Client |
|---|
| 362 | } // namespace HoverRace |
|---|