Open probonopd opened 3 years ago
I think you only need to split the Menu Bar and Options menu. This should solve the problem.
Thinking a bit more about it, Filer could probably render the shadow as part of when it renders the wallpaper.
Can you let Filter render the wallpaper to temporarily hide the menu bar while rendering the shadow, and then move the Menu bar from top to bottom with special effects?
Just like this, it can temporarily hide the problem when the Menu bar renders the shadow. https://user-images.githubusercontent.com/44593430/136714815-940a0114-5a6e-49fb-a6b2-d5148966a7c2.mp4
Right, the animation. If we want the animation then having Filer involved in rendering the shadow is a bad idea. We need to find a way for Menu to render the shadow itself. Most likely by using a "dummy window" (m_fakeWidget
) which is "behind" the real Menu window.
The m_fakeWidget
"dummy window" - how can we get it "behind" the real menu bar?
/*
* Copyright (C) 2020 PandaOS Team.
*
* Author: rekols <revenmartin@gmail.com>
* Portions: probono <probono@puredarwin.org>
*
* 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
* 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 "mainwindow.h"
#include <QApplication>
#include <QHBoxLayout>
#include <QScreen>
#include <QPainter>
#include <QPainterPath>
#include <QDebug>
#include <QApplication>
#include <QLibraryInfo>
#include <KF5/KWindowSystem/KWindowSystem>
#include <QX11Info>
#include <QScreen>
#include <xcb/xcb.h>
#include <X11/Xlib.h>
MainWindow::MainWindow(QWidget *parent)
: QFrame(parent),
m_fakeWidget(new QWidget(nullptr)),
m_mainPanel(new MainPanel)
{
this->setObjectName("menuBar");
// Install the translations built-into Qt itself
qtTranslator.load("qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath));
qApp->installTranslator(&qtTranslator);
// Install our own translations
translator1.load("menubar_" + QLocale::system().name(), QCoreApplication::applicationDirPath() + QString("/../share/menubar/translations/")); // probono: FHS-like path relative to main binary
qApp->installTranslator(&translator1);
translator2.load("menubar_" + QLocale::system().name(), QCoreApplication::applicationDirPath()); // probono: When qm files are next to the executable ("uninstalled"), useful during development
qApp->installTranslator(&translator2);
QHBoxLayout *layout = new QHBoxLayout;
layout->addSpacing(10);
layout->addWidget(m_mainPanel);
layout->addSpacing(10);
layout->setMargin(0);
layout->setSpacing(0);
setLayout(layout);
// m_fakeWidget->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowDoesNotAcceptFocus | Qt::SplashScreen);
m_fakeWidget->setWindowFlags(Qt::WindowDoesNotAcceptFocus);
// m_fakeWidget->setAttribute(Qt::WA_TranslucentBackground);
// Prevent menubar from becoming faded/translucent if we use a compositing manager
// that fades/makes translucent inactive windows
m_mainPanel->setWindowFlags(Qt::WindowDoesNotAcceptFocus);
setAttribute(Qt::WA_NoSystemBackground, false);
// setAttribute(Qt::WA_TranslucentBackground);
setWindowFlags(Qt::FramelessWindowHint);
KWindowSystem::setOnDesktop(effectiveWinId(), NET::OnAllDesktops);
// "Indicates a toplevel menu (AKA macmenu).
// This is a KDE extension to the _NET_WM_WINDOW_TYPE mechanism."
// Source:
// https://api.kde.org/frameworks/kwindowsystem/html/classNET.html#a4b3115c0f40e7bc8e38119cc44dd60e0
// Can be inspected with: xwininfo -wm, it contains "Window type: Kde Net Wm Window Type Topmenu"
// This should allow e.g., picom to set different settings regarding shadows and transparency
KWindowSystem::setType(winId(), NET::TopMenu);
//TODO:
//Call this when the user sets the primary display via xrandr
initSize();
//subscribe to changes on our display like if we change the screen resolution, orientation etc..
connect(qApp->primaryScreen(), &QScreen::geometryChanged, this, &MainWindow::initSize);
connect(qApp->primaryScreen(), &QScreen::orientationChanged, this, &MainWindow::initSize);
connect(qApp->primaryScreen(), &QScreen::virtualGeometryChanged, this, &MainWindow::initSize);
connect(qApp->primaryScreen(), &QScreen::availableGeometryChanged, this, &MainWindow::initSize);
connect(qApp->primaryScreen(), &QScreen::logicalDotsPerInchChanged, this, &MainWindow::initSize);
connect(qApp->primaryScreen(), &QScreen::physicalDotsPerInchChanged, this, &MainWindow::initSize);
connect(qApp->primaryScreen(), &QScreen::physicalSizeChanged, this, &MainWindow::initSize);
connect(qApp->primaryScreen(), &QScreen::primaryOrientationChanged, this, &MainWindow::initSize);
// Appear with an animation
QPropertyAnimation *animation = new QPropertyAnimation(this, "pos");
animation->setDuration(1500);
animation->setStartValue(QPoint(qApp->primaryScreen()->geometry().x(), -2 * qApp->primaryScreen()->geometry().height()));
animation->setEndValue(QPoint(qApp->primaryScreen()->geometry().x(),qApp->primaryScreen()->geometry().y()));
animation->setEasingCurve(QEasingCurve::OutCubic);
animation->start(QPropertyAnimation::DeleteWhenStopped);
this->activateWindow(); // probono: Ensure that we have the focus when menu is launched so that one can enter text in the search box
m_mainPanel->raise(); // probono: Trying to give typing focus to the search box that is in there. Needed? Does not seem tp hurt
}
MainWindow::~MainWindow()
{
}
void MainWindow::paintEvent(QPaintEvent *e)
{
// probono: Draw black rounded corners on the top edges
QPainter p(this);
p.setRenderHint(QPainter::Antialiasing);
p.setPen(Qt::NoPen);
int round_pixels = 5; // like /usr/local/etc/xdg/picom.conf // probono: Make this relative to the height of the MainWindow?
// QPainterPath::subtracted() takes InnerPath and subtracts it from OuterPath to produce the final shape
QPainterPath OuterPath;
OuterPath.addRect(0, 0, qApp->primaryScreen()->geometry().width(), 2*round_pixels);
QPainterPath InnerPath;
InnerPath.addRoundedRect(QRect(0, 0, qApp->primaryScreen()->geometry().width(), 4*round_pixels), round_pixels, round_pixels);
QPainterPath FillPath;
FillPath = OuterPath.subtracted(InnerPath);
p.fillPath(FillPath, Qt::black);
// Draw the other widgets
QWidget::paintEvent(e);
}
void MainWindow::initSize()
{
QRect primaryRect = qApp->primaryScreen()->geometry();
setFixedWidth(primaryRect.width());
// probono: Construct a populated(!) QMenuBar so that we can determine
// its height and use the same height for the MainWindow. Is there a better way?
QMenuBar *dummyMenuBar = new QMenuBar;
dummyMenuBar->setContentsMargins(0, 0, 0, 0);
dummyMenuBar->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Expanding);
QMenu *dummyMenu = new QMenu;
dummyMenuBar->addMenu(dummyMenu);
qDebug() << "probono: dummyMenu->sizeHint().height():" << dummyMenu->sizeHint().height();
setFixedHeight(dummyMenuBar->sizeHint().height());
//move this to the active screen and xrandr position
move(qApp->primaryScreen()->geometry().x(), qApp->primaryScreen()->geometry().y());
setStrutPartial();
KWindowSystem::setState(winId(), NET::SkipTaskbar); // Do not show in Dock
KWindowSystem::setState(winId(), NET::StaysOnTop);
KWindowSystem::setState(winId(), NET::SkipPager);
KWindowSystem::setState(winId(), NET::SkipSwitcher);
// How can we set _NET_WM_STATE_ABOVE? KDE krunner has it set
// https://stackoverflow.com/a/27964691
// "window should be of type _NET_WM_TYPE_DOCK and you must first map it then move it
// to position, otherwise the WM may sometimes place it outside of it own strut."
// _NET_WM_WINDOW_TYPE_DOCK
KWindowSystem::setType(winId(), NET::Dock);
// probono: Set background gradient
// Commenting this out because possibly this interferes with theming via a QSS file via QtPlugin?
// this->setStyleSheet( "MainWindow { background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #fff, stop: 0.1 #eee, stop: 0.39 #eee, stop: 0.4 #ddd, stop: 1 #eee); }");
}
void MainWindow::setStrutPartial()
{
//不清真的作法,kwin设置blur后设置程序支撑导致模糊无效
//TRANSLATED Unclear practice, setting program support after kwin set blur causes blur invalid
QRect r(geometry());
m_fakeWidget->setFixedHeight(this->height());
m_fakeWidget->setFixedWidth(this->width());
m_fakeWidget->move(10,10);
// m_fakeWidget->setGeometry(r);
m_fakeWidget->setVisible(true);
const QRect windowRect = this->rect();
NETExtendedStrut strut;
strut.top_width = height(); // + 1; // 1 pixel between menu bar and maximized window not needed if we have a shadow
strut.top_start = x();
strut.top_end = x() + width();
KWindowSystem::setExtendedStrut(m_fakeWidget->winId(),
strut.left_width,
strut.left_start,
strut.left_end,
strut.right_width,
strut.right_start,
strut.right_end,
strut.top_width,
strut.top_start,
strut.top_end,
strut.bottom_width,
strut.bottom_start,
strut.bottom_end);
}
It should be "forced to be ranked first" like a right-click menu or a sticky note. On the Menubar Menu.
So would always on top be better or does kwin do that with _NET_WM_DOCK?
The real menu is always on top. But we don't want the shadow to be rendered by that window, otherwise it would be rendered over windows that are close to the menu. Hence, we want another (dummy) window behind the real window, and that should always be at the bottom and have the shadow.
If the shadow is a transparent gradient image under the window layer instead of being generated by drawing, I think this will save the memory usage of global menu shadow generation.
But pay attention to this tip about drawing.
The solution is quite simple if you think about it.
Let the code that draws the desktop also draw the shadow for the menu. Since per the Human Interface Guidelines the menu must never go away (except for an application being in fullscreen, in which case you won't see the desktop), this is a perfectly acceptable solution.
Code along the lines of (not quite working yet but you should get the idea):
#include <QGraphicsEffect>
QRect shadowRect(QPoint(0, 22), QSize(1600, 10)); // TODO: Screen width instead of 1600
QLinearGradient alphaGradient(shadowRect.topLeft(), shadowRect.bottomLeft());
alphaGradient.setColorAt(0.0, Qt::black);
alphaGradient.setColorAt(1.0, Qt::transparent);
QGraphicsOpacityEffect *effect = new QGraphicsOpacityEffect;
effect->setOpacityMask(alphaGradient);
painter->fillRect(menuRect, effect);
But where to put it in?
desktopitemdelegate.cpp
has a ::paint
method where we can put it in, desktopwindow.cpp
hasn't.
The following is obviously not the correct way to do it but it is a working except for color gradients instead of transparency gradients being used.
void DesktopItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const {
Q_ASSERT(index.isValid());
QStyleOptionViewItem opt = option;
initStyleOption(&opt, index);
QPen origPen = painter->pen();
QRect menuRect(QPoint(0, 22), QSize(1600, 10));
QLinearGradient m_gradient(0,22,0,32);
m_gradient.setColorAt(0.0, Qt::darkGray);
m_gradient.setColorAt(1.0, Qt::white);
painter->fillRect(menuRect, m_gradient);
painter->setPen(origPen);
painter->save();
painter->setClipRect(option.rect);
Who knows enough about Qt to help get it done?
Now using the proper transparency gradient. Actually easy, just define a QColor with R, G, B, Alpha.
Adding the following to desktopwindow.cpp
:
void DesktopWindow::paintEvent(QPaintEvent *)
{
// This gets drawn but BEHIND the wallpaper. FIXME: Draw above wallpaper
qDebug() << Q_FUNC_INFO;
QPainter painter(this);
QPen origPen = painter.pen();
QRect menuRect(QPoint(0, 22), QSize(1600, 30));
QLinearGradient m_gradient(0,22,0,52);
m_gradient.setColorAt(0.0, QColor(128, 128, 128, 255));
m_gradient.setColorAt(1.0, QColor(128, 128, 128, 0));
painter.fillRect(menuRect, m_gradient);
painter.setPen(origPen);
painter.save();
}
The object gets drawn, but behind the wallpaper. Hence it is only visible if the wallpaper is set to transparent.
We need to do this differently.
Maybe draw at the end of DesktopWindow::updateWallpaper()
? Doesnt seem to work...
Same if I put it into void FolderView::paintEvent(QPaintEvent *)
of folderview.cpp
.
Maybe we need to create a pixmap that contains the wallpaper and the shadow, and use the combined pixmap instead of just the wallpaper image.
As a temporary workaround, using a wallpaper image that has the shadow "built in":
https://github.com/helloSystem/ISO/releases/download/assets/graphite_shadow.jpg
Kinda crude but effective.
Let's not forget to undo that workaround once we found a proper solution for drawing the shadow using Qt.
This is it:
QPen origPen = painter->pen();
QRect shadowRect(QPoint(0, 0), QSize(1600, 33));
QLinearGradient linearGradient(0,0,0,33);
linearGradient.setColorAt(0.00, QColor::fromRgbF(0, 0, 0, 0.3));
linearGradient.setColorAt(0.33, QColor::fromRgbF(0, 0, 0, 0.2));
linearGradient.setColorAt(1.00, QColor::fromRgbF(0, 0, 0, 0.0));
painter->fillRect(shadowRect, linearGradient);
painter->setPen(origPen);
painter->save();
painter->setClipRect(option.rect);
The only remaining question: Where to put this.
@probonopd I recently found how to solve the problem of shadow coverage, Ubuntu Unity also has this problem, but it is well adjusted, that is, to reduce the spread of Kwin shadows. This also reduces system power consumption in window shadow generation.
The question is: How can we get the window for
m_fakeWidget
(or another window) displayed behind the real menu window, but in a way that its drop shadow gets rendered by KWin.