34static constexpr int CONSOLE_ALPHA = 180;
35static constexpr uint64_t BLINK_RATE = 500000;
36static constexpr int CHAR_BORDER = 4;
41static constexpr std::string_view defaultFont =
"skins/VeraMono.ttf.gz";
45 int screenW_,
int screenH_,
bool openGL_)
46 :
Layer(COVER_NONE, Z_CONSOLE)
48 , display(reactor.getDisplay())
50 , consoleSetting(console.getConsoleSetting())
54 , consolePlacementSetting(
55 reactor.getCommandController(),
"consoleplacement",
56 "position of the console within the emulator",
62 {
"topleft", CP_TOP_LEFT},
64 {
"topright", CP_TOP_RIGHT},
66 {
"center", CP_CENTER},
68 {
"bottomleft", CP_BOTTOM_LEFT},
69 {
"bottom", CP_BOTTOM},
70 {
"bottomright", CP_BOTTOM_RIGHT}})
71 , fontSizeSetting(reactor.getCommandController(),
72 "consolefontsize",
"Size of the console font", 12, 8, 32)
73 , fontSetting(reactor.getCommandController(),
74 "consolefont",
"console font file", defaultFont)
75 , consoleColumnsSetting(reactor.getCommandController(),
76 "consolecolumns",
"number of columns in the console",
77 initFontAndGetColumns(), 32, 999)
78 , consoleRowsSetting(reactor.getCommandController(),
79 "consolerows",
"number of rows in the console",
81 , backgroundSetting(reactor.getCommandController(),
82 "consolebackground",
"console background file",
83 "skins/ConsoleBackgroundGrey.png")
84 , lastBlinkTime(Timer::getTime())
89 setCoverage(COVER_PARTIAL);
94 backgroundSetting.setChecker([
this](TclObject& value) {
95 loadBackground(value.getString());
99 consoleSetting.attach(*
this);
100 fontSetting.attach(*
this);
101 fontSizeSetting.attach(*
this);
102 setActive(consoleSetting.getBoolean());
105int OSDConsoleRenderer::initFontAndGetColumns()
108 fontSetting.setChecker([
this](TclObject& value) {
109 loadFont(value.getString());
112 loadFont(fontSetting.getString());
113 }
catch (MSXException&) {
116 reactor.getCliComm().printWarning(
117 "Loading selected font (", fontSetting.getString(),
118 ") failed. Reverting to default font (", defaultFont,
").");
119 fontSetting.setString(defaultFont);
122 throw FatalError(
"Couldn't load default console font.\n"
123 "Please check your installation.");
127 return (((screenW - CHAR_BORDER) / font.getWidth()) * 30) / 32;
129int OSDConsoleRenderer::getRows()
132 return ((screenH / font.getHeight()) * 6) / 15;
134OSDConsoleRenderer::~OSDConsoleRenderer()
136 fontSizeSetting.detach(*
this);
137 fontSetting.detach(*
this);
138 consoleSetting.detach(*
this);
142void OSDConsoleRenderer::adjustColRow()
144 unsigned consoleColumns = std::min<unsigned>(
145 consoleColumnsSetting.getInt(),
146 (screenW - CHAR_BORDER) / font.getWidth());
147 unsigned consoleRows = std::min<unsigned>(
148 consoleRowsSetting.getInt(),
149 screenH / font.getHeight());
150 console.setColumns(consoleColumns);
151 console.setRows(consoleRows);
154void OSDConsoleRenderer::update(
const Setting&
setting)
noexcept
156 if (&
setting == &consoleSetting) {
157 setActive(consoleSetting.getBoolean());
158 }
else if (&
setting ==
one_of(&fontSetting, &fontSizeSetting)) {
159 loadFont(fontSetting.getString());
165void OSDConsoleRenderer::setActive(
bool active_)
167 if (active == active_)
return;
170 display.repaintDelayed(40000);
172 activeTime = Timer::getTime();
175byte OSDConsoleRenderer::getVisibility()
const
177 const uint64_t FADE_IN_DURATION = 100000;
178 const uint64_t FADE_OUT_DURATION = 150000;
180 auto now = Timer::getTime();
181 auto dur = now - activeTime;
183 if (dur > FADE_IN_DURATION) {
186 display.repaintDelayed(40000);
187 return byte((dur * 255) / FADE_IN_DURATION);
190 if (dur > FADE_OUT_DURATION) {
193 display.repaintDelayed(40000);
194 return byte(255 - ((dur * 255) / FADE_OUT_DURATION));
199bool OSDConsoleRenderer::updateConsoleRect()
203 ivec2 size((font.getWidth() * narrow<int>(console.getColumns())) + CHAR_BORDER,
204 font.getHeight() * narrow<int>(console.getRows()));
208 switch (consolePlacementSetting.getEnum()) {
216 case CP_BOTTOM_RIGHT:
217 pos[0] = (screenW -
size[0]);
223 pos[0] = (screenW -
size[0]) / 2;
226 switch (consolePlacementSetting.getEnum()) {
235 pos[1] = (screenH -
size[1]) / 2;
239 case CP_BOTTOM_RIGHT:
241 pos[1] = (screenH -
size[1]);
245 bool result = (pos != bgPos) || (
size != bgSize);
251void OSDConsoleRenderer::loadFont(std::string_view value)
254 auto newFont = TTFFont(filename, fontSizeSetting.getInt());
255 if (!newFont.isFixedWidth()) {
256 throw MSXException(value,
" is not a monospaced font");
258 font = std::move(newFont);
262void OSDConsoleRenderer::loadBackground(std::string_view value)
265 backgroundImage.reset();
268 auto* output = display.getOutputSurface();
270 backgroundImage.reset();
275 backgroundImage = std::make_unique<SDLImage>(*output, filename, bgSize);
279 backgroundImage = std::make_unique<GLImage>(*output, filename, bgSize);
284void OSDConsoleRenderer::drawText(OutputSurface& output, std::string_view text,
285 int cx,
int cy,
byte alpha, uint32_t rgb)
287 auto xy = getTextPos(cx, cy);
288 auto [inCache,
image, width] = getFromCache(text, rgb);
290 std::string textStr(text);
292 uint32_t rgb2 = openGL ? 0xffffff : rgb;
294 width = font.getSize(textStr)[0];
295 surf = font.render(textStr,
299 }
catch (MSXException&
e) {
300 static bool alreadyPrinted =
false;
301 if (!alreadyPrinted) {
302 alreadyPrinted =
true;
303 reactor.getCliComm().printWarning(
304 "Invalid console text (invalid UTF-8): ",
309 std::unique_ptr<BaseImage> image2;
312 }
else if (!openGL) {
313 image2 = std::make_unique<SDLImage>(output, std::move(surf));
317 image2 = std::make_unique<GLImage>(output, std::move(surf));
320 image = image2.get();
321 insertInCache(std::move(textStr), rgb, std::move(image2), width);
325 byte r = (rgb >> 16) & 0xff;
326 byte g = (rgb >> 8) & 0xff;
327 byte b = (rgb >> 0) & 0xff;
328 image->draw(output, xy, r,
g, b, alpha);
330 image->draw(output, xy, alpha);
335std::tuple<bool, BaseImage*, unsigned> OSDConsoleRenderer::getFromCache(
336 std::string_view text, uint32_t rgb)
344 if ((it->text == text) && (openGL || (it->rgb == rgb))) {
351 for (it =
begin(textCache); it !=
end(textCache); ++it) {
352 if (it->text != text)
continue;
353 if (!openGL && (it->rgb != rgb))
continue;
354found: BaseImage*
image = it->image.get();
355 unsigned width = it->width;
357 if (it !=
begin(textCache)) {
360 textCache.splice(
begin(textCache), textCache, it);
362 return {
true,
image, width};
364 return {
false,
nullptr, 0};
367void OSDConsoleRenderer::insertInCache(
368 std::string text, uint32_t rgb, std::unique_ptr<BaseImage>
image,
371 constexpr unsigned MAX_TEXT_CACHE_SIZE = 250;
372 if (textCache.size() == MAX_TEXT_CACHE_SIZE) {
374 if (
auto it = std::prev(
std::end(textCache)); it == cacheHint) {
375 cacheHint =
begin(textCache);
377 textCache.pop_back();
379 textCache.emplace_front(std::move(text), rgb, std::move(
image), width);
382void OSDConsoleRenderer::clearCache()
386 textCache.emplace_back(std::string{}, 0,
nullptr, 0);
387 cacheHint =
begin(textCache);
390gl::ivec2 OSDConsoleRenderer::getTextPos(
int cursorX,
int cursorY)
const
392 return bgPos +
ivec2(CHAR_BORDER + cursorX * font.getWidth(),
393 bgSize[1] - (font.getHeight() * (cursorY + 1)) - 1);
396void OSDConsoleRenderer::paint(OutputSurface& output)
398 byte visibility = getVisibility();
399 if (!visibility)
return;
401 if (updateConsoleRect()) {
403 loadBackground(backgroundSetting.getString());
404 }
catch (MSXException&
e) {
405 reactor.getCliComm().printWarning(
e.getMessage());
410 if (!backgroundImage) {
414 backgroundImage = std::make_unique<SDLImage>(
415 output, bgSize, CONSOLE_ALPHA);
419 backgroundImage = std::make_unique<GLImage>(
420 output, bgSize, CONSOLE_ALPHA);
423 }
catch (MSXException&) {
427 if (backgroundImage) {
428 backgroundImage->draw(output, bgPos, visibility);
431 drawConsoleText(output, visibility);
434 auto now = Timer::getTime();
435 if (lastBlinkTime < now) {
436 lastBlinkTime = now + BLINK_RATE;
440 auto [cursorX, cursorY] = console.getCursorPosition();
441 if ((
unsigned(cursorX) != lastCursorX) || (
unsigned(cursorY) != lastCursorY)) {
443 lastBlinkTime = now + BLINK_RATE;
444 lastCursorX = cursorX;
445 lastCursorY = cursorY;
447 if (blink && (console.getScrollBack() == 0)) {
448 drawText(output,
"_", cursorX, cursorY, visibility, 0xffffff);
452void OSDConsoleRenderer::drawConsoleText(OutputSurface& output,
byte visibility)
454 const auto rows = console.getRows();
455 const auto columns = console.getColumns();
456 const auto scrollBack = console.getScrollBack();
457 const auto& lines = console.getLines();
460 auto [cursorY_, subLine, lineIdx] = [&] {
462 size_t target = rows + scrollBack;
463 for (
auto idx :
xrange(lines.size())) {
464 count +=
std::max(
size_t(1), (lines[idx].numChars() + columns - 1) / columns);
465 if (
count >= target) {
466 return std::tuple(
int(rows - 1),
int(
count - target), idx);
469 int y = int(
count - 1 - scrollBack);
470 return std::tuple(y, 0, lines.size() - 1);
472 int cursorY = cursorY_;
475 std::string_view text = lines[lineIdx].str();
476 auto it =
begin(text);
477 auto endIt =
end(text);
479 std::string_view::size_type idx = it -
begin(text);
480 unsigned chunkIdx = 1;
481 const auto& chunks0 = lines[lineIdx].getChunks();
482 while ((chunkIdx < chunks0.size()) && (chunks0[chunkIdx].pos <= idx)) {
488 auto remainingColumns = columns;
489 const auto& chunks = lines[lineIdx].getChunks();
490 if (!chunks.empty()) {
492 while (remainingColumns && (it < endIt)) {
493 auto startColumn = remainingColumns;
495 auto nextColorIt = (chunkIdx == chunks.size())
497 :
begin(text) + chunks[chunkIdx].pos;
498 auto maxIt =
std::min(endIt, nextColorIt);
499 while (remainingColumns && (
e < maxIt)) {
504 std::string_view subText(&*it,
e - it);
505 auto rgb = chunks[chunkIdx - 1].rgb;
506 auto cursorX = narrow<int>(columns - startColumn);
507 drawText(output, subText, cursorX, cursorY, visibility, rgb);
512 if (
e == nextColorIt) ++chunkIdx;
516 if (--cursorY < 0)
break;
520 text = lines[lineIdx].str();
Wrapper around a SDL_Surface.
std::string resolve(std::string_view filename) const
Interface for display layers.
OSDConsoleRenderer(Reactor &reactor, CommandConsole &console, int screenW, int screenH, bool openGL)
Contains the main loop of openMSX.
ALWAYS_INLINE unsigned count(const uint8_t *pIn, const uint8_t *pMatch, const uint8_t *pInLimit)
constexpr vecN< N, T > min(const vecN< N, T > &x, const vecN< N, T > &y)
constexpr vecN< N, T > max(const vecN< N, T > &x, const vecN< N, T > &y)
This file implemented 3 utility functions:
const FileContext & systemFileContext()
uint8_t byte
8 bit unsigned integer
void advance(octet_iterator &it, distance_type n)
size_t size(std::string_view utf8)
uint32_t next(octet_iterator &it)
constexpr auto xrange(T e)
constexpr auto begin(const zstring_view &x)
constexpr auto end(const zstring_view &x)