mirror of
https://github.com/scummvm/scummvm.git
synced 2026-05-21 05:40:43 +00:00
407 lines
12 KiB
C++
407 lines
12 KiB
C++
/* ScummVM - Graphic Adventure Engine
|
|
*
|
|
* ScummVM is the legal property of its developers, whose names
|
|
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
* file distributed with this source distribution.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#include "common/system.h"
|
|
#include "common/unicode-bidi.h"
|
|
#include "common/translation.h"
|
|
|
|
#include "graphics/macgui/mactext.h"
|
|
#include "graphics/macgui/macfontmanager.h"
|
|
|
|
#include "gui/gui-manager.h"
|
|
|
|
#include "gui/ThemeEngine.h"
|
|
#include "gui/ThemeEval.h"
|
|
|
|
#include "gui/widgets/richtext.h"
|
|
#include "gui/widgets/scrollbar.h"
|
|
#include "gui/animation/FluidScroll.h"
|
|
|
|
namespace GUI {
|
|
|
|
const Graphics::TTFMap ttfFamily[] = {
|
|
{"NotoSans-Regular.ttf", Graphics::kMacFontRegular},
|
|
{"NotoSans-Bold.ttf", Graphics::kMacFontBold},
|
|
{"NotoSerif-Italic.ttf", Graphics::kMacFontItalic},
|
|
{"NotoSerif-Bold-Italic.ttf", Graphics::kMacFontBold | Graphics::kMacFontItalic},
|
|
{nullptr, 0}
|
|
};
|
|
|
|
RichTextWidget::RichTextWidget(GuiObject *boss, int x, int y, int w, int h, bool scale, const Common::U32String &text, const Common::U32String &tooltip)
|
|
: Widget(boss, x, y, w, h, scale, tooltip), CommandSender(nullptr) {
|
|
|
|
_text = text;
|
|
|
|
init();
|
|
}
|
|
|
|
RichTextWidget::RichTextWidget(GuiObject *boss, int x, int y, int w, int h, const Common::U32String &text, const Common::U32String &tooltip)
|
|
: RichTextWidget(boss, x, y, w, h, false, text, tooltip) {
|
|
}
|
|
|
|
RichTextWidget::RichTextWidget(GuiObject *boss, const Common::String &name, const Common::U32String &text, const Common::U32String &tooltip)
|
|
: Widget(boss, name, tooltip), CommandSender(nullptr) {
|
|
|
|
_text = text;
|
|
|
|
init();
|
|
}
|
|
|
|
void RichTextWidget::init() {
|
|
setFlags(WIDGET_ENABLED | WIDGET_CLEARBG | WIDGET_TRACK_MOUSE | WIDGET_DYN_TOOLTIP | WIDGET_WANT_TICKLE | WIDGET_RETAIN_FOCUS);
|
|
|
|
_type = kRichTextWidget;
|
|
|
|
_verticalScroll = new ScrollBarWidget(this, _w - 16, 0, 16, _h);
|
|
_verticalScroll->setTarget(this);
|
|
_scrolledX = 0;
|
|
_scrolledY = 0;
|
|
|
|
_innerMargin = g_gui.xmlEval()->getVar("Globals.RichTextWidget.InnerMargin", 0);
|
|
_scrollbarWidth = g_gui.xmlEval()->getVar("Globals.Scrollbar.Width", 0);
|
|
|
|
_textWidth = MAX(1, _w - _scrollbarWidth - 2 * _innerMargin);
|
|
_textHeight = MAX(1, _h - 2 * _innerMargin);
|
|
|
|
_limitH = 140;
|
|
|
|
_scrollPos = 0.0f;
|
|
_fluidScroller = new FluidScroller();
|
|
_isDragging = false;
|
|
}
|
|
|
|
|
|
RichTextWidget::~RichTextWidget() {
|
|
delete _txtWnd;
|
|
|
|
if (_surface)
|
|
_surface->free();
|
|
delete _surface;
|
|
|
|
if (_cachedTextSurface)
|
|
_cachedTextSurface->free();
|
|
delete _cachedTextSurface;
|
|
delete _fluidScroller;
|
|
}
|
|
|
|
void RichTextWidget::handleMouseWheel(int x, int y, int direction) {
|
|
_fluidScroller->handleMouseWheel(direction);
|
|
applyScrollPos();
|
|
}
|
|
|
|
void RichTextWidget::handleMouseDown(int x, int y, int button, int clickCount) {
|
|
_mouseDownY = _mouseDownStartY = y;
|
|
_fluidScroller->stopAnimation();
|
|
}
|
|
|
|
void RichTextWidget::handleMouseUp(int x, int y, int button, int clickCount) {
|
|
if (_isDragging)
|
|
_fluidScroller->startFling();
|
|
|
|
// Allow some tiny finger slipping
|
|
if (ABS(_mouseDownY - _mouseDownStartY) > 5 || _isDragging) {
|
|
_mouseDownY = _mouseDownStartY = 0;
|
|
_isDragging = false;
|
|
|
|
return;
|
|
}
|
|
|
|
_mouseDownY = _mouseDownStartY = 0;
|
|
_isDragging = false;
|
|
|
|
if (!_txtWnd)
|
|
return;
|
|
|
|
Common::String link = _txtWnd->getMouseLink(x - _innerMargin + _scrolledX, y - _innerMargin + _scrolledY).encode();
|
|
|
|
if (link.hasPrefixIgnoreCase("http"))
|
|
g_system->openUrl(link);
|
|
}
|
|
|
|
void RichTextWidget::handleMouseMoved(int x, int y, int button) {
|
|
if (_txtWnd) {
|
|
Common::String link = _txtWnd->getMouseLink(x - _innerMargin + _scrolledX, y - _innerMargin + _scrolledY).encode();
|
|
|
|
if (!link.empty() && link.hasPrefixIgnoreCase("http"))
|
|
g_gui.theme()->setActiveCursor(GUI::ThemeEngine::kCursorIndex);
|
|
else
|
|
g_gui.theme()->setActiveCursor(GUI::ThemeEngine::kCursorNormal);
|
|
}
|
|
|
|
if (_mouseDownStartY == 0 || _mouseDownY == y || !_txtWnd)
|
|
return;
|
|
|
|
int deltaY = _mouseDownY - y;
|
|
|
|
if (!_isDragging && ABS(deltaY) > 5)
|
|
_isDragging = true;
|
|
|
|
if (_isDragging) {
|
|
_mouseDownY = y;
|
|
if (deltaY != 0) {
|
|
_fluidScroller->feedDrag(g_system->getMillis(), deltaY);
|
|
applyScrollPos();
|
|
}
|
|
}
|
|
}
|
|
|
|
void RichTextWidget::handleTickle() {
|
|
if (_fluidScroller->update(g_system->getMillis(), _scrollPos))
|
|
applyScrollPos();
|
|
}
|
|
|
|
void RichTextWidget::applyScrollPos() {
|
|
_scrollPos = _fluidScroller->getVisualPosition();
|
|
_scrolledY = (int)_scrollPos;
|
|
int maxScroll = MAX(0, _txtWnd->getTextHeight() - _limitH);
|
|
_verticalScroll->_currentPos = CLIP((int)_scrolledY, 0, (int)maxScroll);
|
|
_verticalScroll->recalc();
|
|
markAsDirty();
|
|
}
|
|
|
|
void RichTextWidget::handleTooltipUpdate(int x, int y) {
|
|
if (!_txtWnd)
|
|
return;
|
|
|
|
_tooltip = _txtWnd->getMouseLink(x - _innerMargin + _scrolledX, y - _innerMargin + _scrolledY);
|
|
}
|
|
|
|
void RichTextWidget::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
|
|
Widget::handleCommand(sender, cmd, data);
|
|
switch (cmd) {
|
|
case kSetPositionCmd:
|
|
_scrolledY = _verticalScroll->_currentPos;
|
|
_scrollPos = _scrolledY;
|
|
_fluidScroller->stopAnimation();
|
|
_scrollPos = _fluidScroller->setPosition(_scrollPos, false);
|
|
reflowLayout();
|
|
g_gui.scheduleTopDialogRedraw();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void RichTextWidget::recalc() {
|
|
_innerMargin = g_gui.xmlEval()->getVar("Globals.RichTextWidget.InnerMargin", 0);
|
|
_scrollbarWidth = g_gui.xmlEval()->getVar("Globals.Scrollbar.Width", 0);
|
|
_textWidth = MAX(1, _w - _scrollbarWidth - 2 * _innerMargin);
|
|
_textHeight = MAX(1, _h - 2 * _innerMargin);
|
|
_limitH = _textHeight;
|
|
|
|
// Workaround: Currently Graphics::MacText::setMaxWidth does not work well.
|
|
// There is a known limitation that the size is skipped when the text contains table,
|
|
// and there is also an issue with the font.
|
|
// So for now we recreate the widget.
|
|
// if (_txtWnd) {
|
|
// _txtWnd->setMaxWidth(_textWidth);
|
|
// if (_surface->w != _textWidth || _surface->h != _textHeight)
|
|
// _surface->create(_textWidth, _textHeight, g_gui.getWM()->_pixelformat);
|
|
// } else {
|
|
// createWidget();
|
|
// }
|
|
|
|
if (!_surface || _surface->w != _textWidth) {
|
|
if (_surface) {
|
|
_surface->free();
|
|
delete _surface;
|
|
_surface = nullptr;
|
|
}
|
|
delete _txtWnd;
|
|
_txtWnd = nullptr;
|
|
if (_cachedTextSurface) {
|
|
_cachedTextSurface->free();
|
|
delete _cachedTextSurface;
|
|
_cachedTextSurface = nullptr;
|
|
}
|
|
} else if (_surface->h != _textHeight) {
|
|
_surface->create(_textWidth, _textHeight, g_gui.getWM()->_pixelformat);
|
|
|
|
int h = _txtWnd->getTextHeight();
|
|
int maxScroll = MAX(0, h - _limitH);
|
|
_verticalScroll->_numEntries = h;
|
|
_verticalScroll->_currentPos = CLIP((int)_scrolledY, 0, (int)maxScroll);
|
|
_verticalScroll->_entriesPerPage = _limitH;
|
|
_verticalScroll->_singleStep = _h / 4;
|
|
_verticalScroll->setPos(_w - _scrollbarWidth, 0);
|
|
_verticalScroll->setSize(_scrollbarWidth, _h - 1);
|
|
_verticalScroll->setVisible(_verticalScroll->_numEntries > _limitH); //show when there is something to scroll
|
|
_verticalScroll->recalc();
|
|
_fluidScroller->setBounds((float)maxScroll, (float)_limitH, (float)_verticalScroll->_singleStep);
|
|
}
|
|
}
|
|
|
|
void RichTextWidget::createWidget() {
|
|
Graphics::MacWindowManager *wm = g_gui.getWM();
|
|
|
|
uint8 bgR, bgG, bgB;
|
|
uint32 bg;
|
|
if (g_gui.theme()->getDrawDataColor(kDDWidgetBackgroundDefault, bgR, bgG, bgB))
|
|
bg = wm->_pixelformat.ARGBToColor(255, bgR, bgG, bgB);
|
|
else
|
|
bg = wm->_pixelformat.ARGBToColor(0xFF, 0xFF, 0xFF, 0xFF);
|
|
|
|
TextColorData *normal = g_gui.theme()->getTextColorData(kTextColorNormal);
|
|
uint32 fg = wm->_pixelformat.RGBToColor(normal->r, normal->g, normal->b);
|
|
|
|
const int fontHeight = g_gui.xmlEval()->getVar("Globals.Font.Height", 25);
|
|
|
|
int newId;
|
|
bool useTTF = false; // For English we use MacFONTs
|
|
|
|
#ifdef USE_TRANSLATION
|
|
// MacFONTs do not contain diacritic marks or non-English characters, so we have to use TTF instead
|
|
if (TransMan.getCurrentLanguage() != "en")
|
|
useTTF = true;
|
|
#endif
|
|
|
|
if (useTTF)
|
|
newId = wm->_fontMan->registerTTFFont(ttfFamily);
|
|
else
|
|
newId = Graphics::kMacFontNewYork;
|
|
|
|
#ifdef USE_FREETYPE2
|
|
// Use anti-aliased (light) rendering for the GUI help text.
|
|
Graphics::TTFRenderMode prevRenderMode = wm->_fontMan->getTTFRenderMode();
|
|
wm->_fontMan->setTTFRenderMode(Graphics::kTTFRenderModeLight);
|
|
#endif
|
|
|
|
Graphics::MacFont macFont(newId, fontHeight, Graphics::kMacFontRegular);
|
|
_txtWnd = new Graphics::MacText(Common::U32String(), wm, &macFont, fg, bg, _textWidth, Graphics::kTextAlignLeft);
|
|
|
|
if (!_imageArchive.empty())
|
|
_txtWnd->setImageArchive(_imageArchive);
|
|
|
|
_txtWnd->setMarkdownText(_text);
|
|
|
|
#ifdef USE_FREETYPE2
|
|
// Restore previous render mode now that all fonts have been loaded
|
|
wm->_fontMan->setTTFRenderMode(prevRenderMode);
|
|
#endif
|
|
|
|
int textHeight = _txtWnd->getTextHeight();
|
|
|
|
if (textHeight > 0) {
|
|
if (!_cachedTextSurface || _cachedTextSurface->w != _textWidth || _cachedTextSurface->h != textHeight) {
|
|
if (_cachedTextSurface) {
|
|
_cachedTextSurface->free();
|
|
delete _cachedTextSurface;
|
|
}
|
|
_cachedTextSurface = new Graphics::ManagedSurface(_textWidth, textHeight, wm->_pixelformat);
|
|
}
|
|
_cachedTextSurface->clear(bg);
|
|
_txtWnd->draw(_cachedTextSurface, 0, 0, _textWidth, textHeight, 0, 0);
|
|
}
|
|
|
|
if (!_surface || _surface->w != _textWidth || _surface->h != _textHeight) {
|
|
if (_surface)
|
|
_surface->create(_textWidth, _textHeight, wm->_pixelformat);
|
|
else
|
|
_surface = new Graphics::ManagedSurface(_textWidth, _textHeight, wm->_pixelformat);
|
|
}
|
|
|
|
int h = _txtWnd->getTextHeight();
|
|
int maxScroll = MAX(0, h - _limitH);
|
|
_verticalScroll->_numEntries = h;
|
|
_verticalScroll->_currentPos = CLIP((int)_scrolledY, 0, (int)maxScroll);
|
|
_verticalScroll->_entriesPerPage = _limitH;
|
|
_verticalScroll->_singleStep = _h / 4;
|
|
_verticalScroll->setPos(_w - _scrollbarWidth, 0);
|
|
_verticalScroll->setSize(_scrollbarWidth, _h - 1);
|
|
_verticalScroll->setVisible(_verticalScroll->_numEntries > _limitH); //show when there is something to scroll
|
|
_verticalScroll->recalc();
|
|
_fluidScroller->setBounds((float)maxScroll, (float)_limitH, (float)_verticalScroll->_singleStep);
|
|
}
|
|
|
|
void RichTextWidget::reflowLayout() {
|
|
Widget::reflowLayout();
|
|
|
|
recalc();
|
|
}
|
|
|
|
void RichTextWidget::ensureWidget() {
|
|
if (_txtWnd)
|
|
return;
|
|
createWidget();
|
|
}
|
|
|
|
void RichTextWidget::drawWidget() {
|
|
ensureWidget();
|
|
|
|
if (!_txtWnd)
|
|
recalc();
|
|
|
|
g_gui.theme()->drawWidgetBackground(Common::Rect(_x, _y, _x + _w, _y + _h), ThemeEngine::kWidgetBackgroundPlain);
|
|
|
|
uint8 bgR, bgG, bgB;
|
|
uint32 bg;
|
|
if (g_gui.theme()->getDrawDataColor(kDDWidgetBackgroundDefault, bgR, bgG, bgB))
|
|
bg = g_gui.getWM()->_pixelformat.ARGBToColor(255, bgR, bgG, bgB);
|
|
else
|
|
bg = g_gui.getWM()->_pixelformat.ARGBToColor(0xFF, 0xFF, 0xFF, 0xFF);
|
|
|
|
_surface->clear(bg);
|
|
|
|
if (_cachedTextSurface) {
|
|
int cachedHeight = _cachedTextSurface->h;
|
|
int srcY = _scrolledY < 0 ? 0 : (int)_scrolledY;
|
|
int destY = _scrolledY < 0 ? -(int)_scrolledY : 0;
|
|
|
|
if (srcY < cachedHeight)
|
|
_surface->simpleBlitFrom(*_cachedTextSurface, Common::Rect(0, srcY, _textWidth,
|
|
MIN(srcY + _textHeight - destY, cachedHeight)), Common::Point(0, destY));
|
|
} else
|
|
_txtWnd->draw(_surface, 0, _scrolledY, _textWidth, _textHeight, 0, 0);
|
|
|
|
g_gui.theme()->drawManagedSurface(Common::Point(_x + _innerMargin, _y + _innerMargin), *_surface, Graphics::ALPHA_OPAQUE);
|
|
}
|
|
|
|
void RichTextWidget::draw() {
|
|
Widget::draw();
|
|
|
|
if (_verticalScroll->isVisible()) {
|
|
_verticalScroll->draw();
|
|
}
|
|
}
|
|
|
|
void RichTextWidget::markAsDirty() {
|
|
Widget::markAsDirty();
|
|
|
|
if (_verticalScroll->isVisible()) {
|
|
_verticalScroll->markAsDirty();
|
|
}
|
|
}
|
|
|
|
bool RichTextWidget::containsWidget(Widget *w) const {
|
|
if (w == _verticalScroll || _verticalScroll->containsWidget(w))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
Widget *RichTextWidget::findWidget(int x, int y) {
|
|
if (_verticalScroll->isVisible() && x >= _w - _scrollbarWidth)
|
|
return _verticalScroll;
|
|
|
|
return this;
|
|
}
|
|
|
|
} // End of namespace GUI
|