/**************************************************************************** ** ** Copyright (C) 2018 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the plugins of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:GPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 or (at your option) any later version ** approved by the KDE Free Qt Foundation. The licenses are as published by ** the Free Software Foundation and appearing in the file LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qwasmcompositor.h" #include "qwasmwindow.h" #include "qwasmstylepixmaps_p.h" #include #include #include #include #include #include #include #include #include #include #include #include Q_GUI_EXPORT int qt_defaultDpiX(); QWasmCompositedWindow::QWasmCompositedWindow() : window(nullptr) , parentWindow(nullptr) , flushPending(false) , visible(false) { } QWasmCompositor::QWasmCompositor(QWasmScreen *screen) :QObject(screen) , m_frameBuffer(nullptr) , m_blitter(new QOpenGLTextureBlitter) , m_needComposit(false) , m_inFlush(false) , m_inResize(false) , m_isEnabled(true) , m_targetDevicePixelRatio(1) { } QWasmCompositor::~QWasmCompositor() { delete m_frameBuffer; destroy(); } void QWasmCompositor::destroy() { // Destroy OpenGL resources. This is done here in a separate function // which can be called while screen() still returns a valid screen // (which it might not, during destruction). A valid QScreen is // a requirement for QOffscreenSurface on Wasm since the native // context is tied to a single canvas. if (m_context) { QOffscreenSurface offScreenSurface(screen()->screen()); offScreenSurface.setFormat(m_context->format()); offScreenSurface.create(); m_context->makeCurrent(&offScreenSurface); for (QWasmWindow *window : m_windowStack) window->destroy(); m_blitter.reset(nullptr); m_context.reset(nullptr); } m_isEnabled = false; // prevent frame() from creating a new m_context } void QWasmCompositor::setEnabled(bool enabled) { m_isEnabled = enabled; } void QWasmCompositor::addWindow(QWasmWindow *window, QWasmWindow *parentWindow) { QWasmCompositedWindow compositedWindow; compositedWindow.window = window; compositedWindow.parentWindow = parentWindow; m_compositedWindows.insert(window, compositedWindow); if (parentWindow == 0) m_windowStack.append(window); else m_compositedWindows[parentWindow].childWindows.append(window); notifyTopWindowChanged(window); } void QWasmCompositor::removeWindow(QWasmWindow *window) { QWasmWindow *platformWindow = m_compositedWindows[window].parentWindow; if (platformWindow) { QWasmWindow *parentWindow = window; m_compositedWindows[parentWindow].childWindows.removeAll(window); } m_windowStack.removeAll(window); m_compositedWindows.remove(window); notifyTopWindowChanged(window); } void QWasmCompositor::setVisible(QWasmWindow *window, bool visible) { QWasmCompositedWindow &compositedWindow = m_compositedWindows[window]; if (compositedWindow.visible == visible) return; compositedWindow.visible = visible; compositedWindow.flushPending = true; if (visible) compositedWindow.damage = compositedWindow.window->geometry(); else m_globalDamage = compositedWindow.window->geometry(); // repaint previosly covered area. requestRedraw(); } void QWasmCompositor::raise(QWasmWindow *window) { if (m_compositedWindows.size() <= 1) return; QWasmCompositedWindow &compositedWindow = m_compositedWindows[window]; compositedWindow.damage = compositedWindow.window->geometry(); m_windowStack.removeAll(window); m_windowStack.append(window); notifyTopWindowChanged(window); } void QWasmCompositor::lower(QWasmWindow *window) { if (m_compositedWindows.size() <= 1) return; m_windowStack.removeAll(window); m_windowStack.prepend(window); QWasmCompositedWindow &compositedWindow = m_compositedWindows[window]; m_globalDamage = compositedWindow.window->geometry(); // repaint previosly covered area. notifyTopWindowChanged(window); } void QWasmCompositor::setParent(QWasmWindow *window, QWasmWindow *parent) { m_compositedWindows[window].parentWindow = parent; requestRedraw(); } void QWasmCompositor::flush(QWasmWindow *window, const QRegion ®ion) { QWasmCompositedWindow &compositedWindow = m_compositedWindows[window]; compositedWindow.flushPending = true; compositedWindow.damage = region; requestRedraw(); } int QWasmCompositor::windowCount() const { return m_windowStack.count(); } void QWasmCompositor::redrawWindowContent() { // Redraw window content by sending expose events. This redraw // will cause a backing store flush, which will call requestRedraw() // to composit. for (QWasmWindow *platformWindow : m_windowStack) { QWindow *window = platformWindow->window(); QWindowSystemInterface::handleExposeEvent( window, QRect(QPoint(0, 0), window->geometry().size())); } } void QWasmCompositor::requestRedraw() { if (m_needComposit) return; m_needComposit = true; QCoreApplication::postEvent(this, new QEvent(QEvent::UpdateRequest)); } QWindow *QWasmCompositor::windowAt(QPoint globalPoint, int padding) const { int index = m_windowStack.count() - 1; // qDebug() << "window at" << "point" << p << "window count" << index; while (index >= 0) { const QWasmCompositedWindow &compositedWindow = m_compositedWindows[m_windowStack.at(index)]; //qDebug() << "windwAt testing" << compositedWindow.window << QRect geometry = compositedWindow.window->windowFrameGeometry() .adjusted(-padding, -padding, padding, padding); if (compositedWindow.visible && geometry.contains(globalPoint)) return m_windowStack.at(index)->window(); --index; } return 0; } QWindow *QWasmCompositor::keyWindow() const { return m_windowStack.at(m_windowStack.count() - 1)->window(); } bool QWasmCompositor::event(QEvent *ev) { if (ev->type() == QEvent::UpdateRequest) { if (m_isEnabled) frame(); return true; } return QObject::event(ev); } void QWasmCompositor::blit(QOpenGLTextureBlitter *blitter, QWasmScreen *screen, const QOpenGLTexture *texture, QRect targetGeometry) { QMatrix4x4 m; m.translate(-1.0f, -1.0f); m.scale(2.0f / (float)screen->geometry().width(), 2.0f / (float)screen->geometry().height()); m.translate((float)targetGeometry.width() / 2.0f, (float)-targetGeometry.height() / 2.0f); m.translate(targetGeometry.x(), screen->geometry().height() - targetGeometry.y()); m.scale(0.5f * (float)targetGeometry.width(), 0.5f * (float)targetGeometry.height()); blitter->blit(texture->textureId(), m, QOpenGLTextureBlitter::OriginTopLeft); } void QWasmCompositor::drawWindowContent(QOpenGLTextureBlitter *blitter, QWasmScreen *screen, QWasmWindow *window) { QWasmBackingStore *backingStore = window->backingStore(); if (!backingStore) return; QOpenGLTexture const *texture = backingStore->getUpdatedTexture(); QPoint windowCanvasPosition = window->geometry().topLeft() - screen->geometry().topLeft(); QRect windowCanvasGeometry = QRect(windowCanvasPosition, window->geometry().size()); blit(blitter, screen, texture, windowCanvasGeometry); } QPalette QWasmCompositor::makeWindowPalette() { QPalette palette; palette.setColor(QPalette::Active, QPalette::Highlight, palette.color(QPalette::Active, QPalette::Highlight)); palette.setColor(QPalette::Active, QPalette::Base, palette.color(QPalette::Active, QPalette::Highlight)); palette.setColor(QPalette::Inactive, QPalette::Highlight, palette.color(QPalette::Inactive, QPalette::Dark)); palette.setColor(QPalette::Inactive, QPalette::Base, palette.color(QPalette::Inactive, QPalette::Dark)); palette.setColor(QPalette::Inactive, QPalette::HighlightedText, palette.color(QPalette::Inactive, QPalette::Window)); return palette; } QRect QWasmCompositor::titlebarRect(QWasmTitleBarOptions tb, QWasmCompositor::SubControls subcontrol) { QRect ret; const int controlMargin = 2; const int controlHeight = tb.rect.height() - controlMargin *2; const int delta = controlHeight + controlMargin; int offset = 0; bool isMinimized = tb.state & Qt::WindowMinimized; bool isMaximized = tb.state & Qt::WindowMaximized; ret = tb.rect; switch (subcontrol) { case SC_TitleBarLabel: if (tb.flags & Qt::WindowSystemMenuHint) ret.adjust(delta, 0, -delta, 0); break; case SC_TitleBarCloseButton: if (tb.flags & Qt::WindowSystemMenuHint) { ret.adjust(0, 0, -delta, 0); offset += delta; } break; case SC_TitleBarMaxButton: if (!isMaximized && tb.flags & Qt::WindowMaximizeButtonHint) { ret.adjust(0, 0, -delta*2, 0); offset += (delta +delta); } break; case SC_TitleBarNormalButton: if (isMinimized && (tb.flags & Qt::WindowMinimizeButtonHint)) offset += delta; else if (isMaximized && (tb.flags & Qt::WindowMaximizeButtonHint)) ret.adjust(0, 0, -delta*2, 0); offset += (delta +delta); break; case SC_TitleBarSysMenu: if (tb.flags & Qt::WindowSystemMenuHint) { ret.setRect(tb.rect.left() + controlMargin, tb.rect.top() + controlMargin, controlHeight, controlHeight); } break; default: break; }; if (subcontrol != SC_TitleBarLabel && subcontrol != SC_TitleBarSysMenu) { ret.setRect(tb.rect.right() - offset, tb.rect.top() + controlMargin, controlHeight, controlHeight); } if (qApp->layoutDirection() == Qt::LeftToRight) return ret; QRect rect = ret; rect.translate(2 * (tb.rect.right() - ret.right()) + ret.width() - tb.rect.width(), 0); return rect; } int dpiScaled(qreal value) { return value * (qreal(qt_defaultDpiX()) / 96.0); } QWasmCompositor::QWasmTitleBarOptions QWasmCompositor::makeTitleBarOptions(const QWasmWindow *window) { int width = window->windowFrameGeometry().width(); int border = window->borderWidth(); QWasmTitleBarOptions titleBarOptions; titleBarOptions.rect = QRect(border, border, width - 2 * border, window->titleHeight()); titleBarOptions.flags = window->window()->flags(); titleBarOptions.state = window->window()->windowState(); bool isMaximized = titleBarOptions.state & Qt::WindowMaximized; // this gets reset when maximized if (titleBarOptions.flags & (Qt::WindowTitleHint)) titleBarOptions.subControls |= SC_TitleBarLabel; if (titleBarOptions.flags & Qt::WindowMaximizeButtonHint) { if (isMaximized) titleBarOptions.subControls |= SC_TitleBarNormalButton; else titleBarOptions.subControls |= SC_TitleBarMaxButton; } if (titleBarOptions.flags & Qt::WindowSystemMenuHint) { titleBarOptions.subControls |= SC_TitleBarCloseButton; titleBarOptions.subControls |= SC_TitleBarSysMenu; } titleBarOptions.palette = QWasmCompositor::makeWindowPalette(); if (window->window()->isActive()) titleBarOptions.palette.setCurrentColorGroup(QPalette::Active); else titleBarOptions.palette.setCurrentColorGroup(QPalette::Inactive); if (window->activeSubControl() != QWasmCompositor::SC_None) titleBarOptions.subControls = window->activeSubControl(); if (!window->window()->title().isEmpty()) titleBarOptions.titleBarOptionsString = window->window()->title(); return titleBarOptions; } void QWasmCompositor::drawWindowDecorations(QOpenGLTextureBlitter *blitter, QWasmScreen *screen, QWasmWindow *window) { int width = window->windowFrameGeometry().width(); int height = window->windowFrameGeometry().height(); qreal dpr = window->devicePixelRatio(); QImage image(QSize(width * dpr, height * dpr), QImage::Format_RGB32); image.setDevicePixelRatio(dpr); QPainter painter(&image); painter.fillRect(QRect(0, 0, width, height), painter.background()); QWasmTitleBarOptions titleBarOptions = makeTitleBarOptions(window); drawTitlebarWindow(titleBarOptions, &painter); QWasmFrameOptions frameOptions; frameOptions.rect = QRect(0, 0, width, height); frameOptions.lineWidth = dpiScaled(4.); drawFrameWindow(frameOptions, &painter); painter.end(); QOpenGLTexture texture(QOpenGLTexture::Target2D); texture.setMinificationFilter(QOpenGLTexture::Nearest); texture.setMagnificationFilter(QOpenGLTexture::Nearest); texture.setWrapMode(QOpenGLTexture::ClampToEdge); texture.setData(image, QOpenGLTexture::DontGenerateMipMaps); texture.create(); texture.bind(); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, image.width(), image.height(), GL_RGBA, GL_UNSIGNED_BYTE, image.constScanLine(0)); blit(blitter, screen, &texture, QRect(window->windowFrameGeometry().topLeft(), QSize(width, height))); } void QWasmCompositor::drawFrameWindow(QWasmFrameOptions options, QPainter *painter) { int x = options.rect.x(); int y = options.rect.y(); int w = options.rect.width(); int h = options.rect.height(); const QColor &c1 = options.palette.light().color(); const QColor &c2 = options.palette.shadow().color(); const QColor &c3 = options.palette.midlight().color(); const QColor &c4 = options.palette.dark().color(); const QBrush *fill = 0; const qreal devicePixelRatio = painter->device()->devicePixelRatioF(); if (!qFuzzyCompare(devicePixelRatio, qreal(1))) { const qreal inverseScale = qreal(1) / devicePixelRatio; painter->scale(inverseScale, inverseScale); x = qRound(devicePixelRatio * x); y = qRound(devicePixelRatio * y); w = qRound(devicePixelRatio * w); h = qRound(devicePixelRatio * h); } QPen oldPen = painter->pen(); QPoint a[3] = { QPoint(x, y+h-2), QPoint(x, y), QPoint(x+w-2, y) }; painter->setPen(c1); painter->drawPolyline(a, 3); QPoint b[3] = { QPoint(x, y+h-1), QPoint(x+w-1, y+h-1), QPoint(x+w-1, y) }; painter->setPen(c2); painter->drawPolyline(b, 3); if (w > 4 && h > 4) { QPoint c[3] = { QPoint(x+1, y+h-3), QPoint(x+1, y+1), QPoint(x+w-3, y+1) }; painter->setPen(c3); painter->drawPolyline(c, 3); QPoint d[3] = { QPoint(x+1, y+h-2), QPoint(x+w-2, y+h-2), QPoint(x+w-2, y+1) }; painter->setPen(c4); painter->drawPolyline(d, 3); if (fill) painter->fillRect(QRect(x+2, y+2, w-4, h-4), *fill); } painter->setPen(oldPen); } //from commonstyle.cpp static QPixmap cachedPixmapFromXPM(const char * const *xpm) { QPixmap result; const QString tag = QString::asprintf("xpm:0x%p", static_cast(xpm)); if (!QPixmapCache::find(tag, &result)) { result = QPixmap(xpm); QPixmapCache::insert(tag, result); } return result; } void QWasmCompositor::drawItemPixmap(QPainter *painter, const QRect &rect, int alignment, const QPixmap &pixmap) const { qreal scale = pixmap.devicePixelRatio(); QSize size = pixmap.size() / scale; int x = rect.x(); int y = rect.y(); int w = size.width(); int h = size.height(); if ((alignment & Qt::AlignVCenter) == Qt::AlignVCenter) y += rect.size().height()/2 - h/2; else if ((alignment & Qt::AlignBottom) == Qt::AlignBottom) y += rect.size().height() - h; if ((alignment & Qt::AlignRight) == Qt::AlignRight) x += rect.size().width() - w; else if ((alignment & Qt::AlignHCenter) == Qt::AlignHCenter) x += rect.size().width()/2 - w/2; QRect aligned = QRect(x, y, w, h); QRect inter = aligned.intersected(rect); painter->drawPixmap(inter.x(), inter.y(), pixmap, inter.x() - aligned.x(), inter.y() - aligned.y(), inter.width() * scale, inter.height() *scale); } void QWasmCompositor::drawTitlebarWindow(QWasmTitleBarOptions tb, QPainter *painter) { QRect ir; if (tb.subControls.testFlag(SC_TitleBarLabel)) { QColor left = tb.palette.highlight().color(); QColor right = tb.palette.base().color(); QBrush fillBrush(left); if (left != right) { QPoint p1(tb.rect.x(), tb.rect.top() + tb.rect.height()/2); QPoint p2(tb.rect.right(), tb.rect.top() + tb.rect.height()/2); QLinearGradient lg(p1, p2); lg.setColorAt(0, left); lg.setColorAt(1, right); fillBrush = lg; } painter->fillRect(tb.rect, fillBrush); ir = titlebarRect(tb, SC_TitleBarLabel); painter->setPen(tb.palette.highlightedText().color()); painter->drawText(ir.x() + 2, ir.y(), ir.width() - 2, ir.height(), Qt::AlignLeft | Qt::AlignVCenter | Qt::TextSingleLine, tb.titleBarOptionsString); } // SC_TitleBarLabel bool down = false; QPixmap pixmap; if (tb.subControls.testFlag(SC_TitleBarCloseButton) && tb.flags & Qt::WindowSystemMenuHint) { ir = titlebarRect(tb, SC_TitleBarCloseButton); down = tb.subControls & SC_TitleBarCloseButton && (tb.state & State_Sunken); pixmap = cachedPixmapFromXPM(qt_close_xpm).scaled(QSize(10, 10)); drawItemPixmap(painter, ir, Qt::AlignCenter, pixmap); } //SC_TitleBarCloseButton if (tb.subControls.testFlag(SC_TitleBarMaxButton) && tb.flags & Qt::WindowMaximizeButtonHint && !(tb.state & Qt::WindowMaximized)) { ir = titlebarRect(tb, SC_TitleBarMaxButton); down = tb.subControls & SC_TitleBarMaxButton && (tb.state & State_Sunken); pixmap = cachedPixmapFromXPM(qt_maximize_xpm).scaled(QSize(10, 10)); drawItemPixmap(painter, ir, Qt::AlignCenter, pixmap); } //SC_TitleBarMaxButton bool drawNormalButton = (tb.subControls & SC_TitleBarNormalButton) && (((tb.flags & Qt::WindowMinimizeButtonHint) && (tb.flags & Qt::WindowMinimized)) || ((tb.flags & Qt::WindowMaximizeButtonHint) && (tb.flags & Qt::WindowMaximized))); if (drawNormalButton) { ir = titlebarRect(tb, SC_TitleBarNormalButton); down = tb.subControls & SC_TitleBarNormalButton && (tb.state & State_Sunken); pixmap = cachedPixmapFromXPM(qt_normalizeup_xpm).scaled( QSize(10, 10)); drawItemPixmap(painter, ir, Qt::AlignCenter, pixmap); } // SC_TitleBarNormalButton if (tb.subControls & SC_TitleBarSysMenu && tb.flags & Qt::WindowSystemMenuHint) { ir = titlebarRect(tb, SC_TitleBarSysMenu); pixmap = cachedPixmapFromXPM(qt_menu_xpm).scaled(QSize(10, 10)); drawItemPixmap(painter, ir, Qt::AlignCenter, pixmap); } } void QWasmCompositor::drawShadePanel(QWasmTitleBarOptions options, QPainter *painter) { int lineWidth = 1; QPalette palette = options.palette; const QBrush *fill = &options.palette.brush(QPalette::Button); int x = options.rect.x(); int y = options.rect.y(); int w = options.rect.width(); int h = options.rect.height(); const qreal devicePixelRatio = painter->device()->devicePixelRatioF(); if (!qFuzzyCompare(devicePixelRatio, qreal(1))) { const qreal inverseScale = qreal(1) / devicePixelRatio; painter->scale(inverseScale, inverseScale); x = qRound(devicePixelRatio * x); y = qRound(devicePixelRatio * y); w = qRound(devicePixelRatio * w); h = qRound(devicePixelRatio * h); lineWidth = qRound(devicePixelRatio * lineWidth); } QColor shade = palette.dark().color(); QColor light = palette.light().color(); if (fill) { if (fill->color() == shade) shade = palette.shadow().color(); if (fill->color() == light) light = palette.midlight().color(); } QPen oldPen = painter->pen(); QVector lines; lines.reserve(2*lineWidth); painter->setPen(light); int x1, y1, x2, y2; int i; x1 = x; y1 = y2 = y; x2 = x + w - 2; for (i = 0; i < lineWidth; i++) // top shadow lines << QLineF(x1, y1++, x2--, y2++); x2 = x1; y1 = y + h - 2; for (i = 0; i < lineWidth; i++) // left shado lines << QLineF(x1++, y1, x2++, y2--); painter->drawLines(lines); lines.clear(); painter->setPen(shade); x1 = x; y1 = y2 = y+h-1; x2 = x+w-1; for (i=0; idrawLines(lines); if (fill) // fill with fill color painter->fillRect(x+lineWidth, y+lineWidth, w-lineWidth*2, h-lineWidth*2, *fill); painter->setPen(oldPen); // restore pen } void QWasmCompositor::drawWindow(QOpenGLTextureBlitter *blitter, QWasmScreen *screen, QWasmWindow *window) { if (window->window()->type() != Qt::Popup && !(window->m_windowState & Qt::WindowFullScreen)) drawWindowDecorations(blitter, screen, window); drawWindowContent(blitter, screen, window); } void QWasmCompositor::frame() { if (!m_needComposit) return; m_needComposit = false; if (!m_isEnabled || m_windowStack.empty() || !screen()) return; QWasmWindow *someWindow = nullptr; for (QWasmWindow *window : qAsConst(m_windowStack)) { if (window->window()->surfaceClass() == QSurface::Window && qt_window_private(static_cast(window->window()))->receivedExpose) { someWindow = window; break; } } if (!someWindow) return; if (m_context.isNull()) { m_context.reset(new QOpenGLContext()); m_context->setFormat(someWindow->window()->requestedFormat()); m_context->setScreen(screen()->screen()); m_context->create(); } bool ok = m_context->makeCurrent(someWindow->window()); if (!ok) return; if (!m_blitter->isCreated()) m_blitter->create(); qreal dpr = screen()->devicePixelRatio(); glViewport(0, 0, screen()->geometry().width() * dpr, screen()->geometry().height() * dpr); m_context->functions()->glClearColor(0.2, 0.2, 0.2, 1.0); m_context->functions()->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); m_blitter->bind(); m_blitter->setRedBlueSwizzle(true); for (QWasmWindow *window : qAsConst(m_windowStack)) { QWasmCompositedWindow &compositedWindow = m_compositedWindows[window]; if (!compositedWindow.visible) continue; drawWindow(m_blitter.data(), screen(), window); } m_blitter->release(); if (someWindow && someWindow->window()->surfaceType() == QSurface::OpenGLSurface) m_context->swapBuffers(someWindow->window()); } void QWasmCompositor::notifyTopWindowChanged(QWasmWindow *window) { QWindow *modalWindow; bool blocked = QGuiApplicationPrivate::instance()->isWindowBlocked(window->window(), &modalWindow); if (blocked) { raise(static_cast(modalWindow->handle())); return; } requestRedraw(); QWindowSystemInterface::handleWindowActivated(window->window()); } QWasmScreen *QWasmCompositor::screen() { return static_cast(parent()); }