新增悬浮按钮管理器及悬浮按钮视图

1. 新增悬浮按钮管理器。其主要作用是负责管理悬浮按钮的可用性及其可见性

2. 新增悬浮按钮视图。其主要作用是负责提供悬浮按钮UI并响应用户输入
This commit is contained in:
hanteng 2023-04-04 11:37:09 +08:00
parent 16285ebbce
commit 126bb24d7f
13 changed files with 519 additions and 0 deletions

10
floatbutton.qrc Normal file
View File

@ -0,0 +1,10 @@
<RCC>
<qresource prefix="/floatbutton">
<file>img/enablefloatbutton.svg</file>
<file>img/disablefloatbutton.svg</file>
<file>img/floatbuttondefault.svg</file>
<file>img/floatbuttonpressed.svg</file>
<file>img/floatbuttonhovered.svg</file>
</qresource>
<qresource prefix="/"/>
</RCC>

View File

@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2 2L14 14" stroke="#262626" stroke-width="1.5" stroke-linecap="round"/>
<path d="M3.76756 3.74718C4.85253 2.66738 6.34833 2 8 2C11.3137 2 14 4.68629 14 8C14 9.64573 13.3374 11.1367 12.2645 12.2207M2.34141 6C2.12031 6.62556 2 7.29873 2 8C2 11.3137 4.68629 14 8 14C8.70127 14 9.37444 13.8797 10 13.6586" stroke="#262626" stroke-width="1.5" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 476 B

View File

@ -0,0 +1,9 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6 11H10" stroke="#262626" stroke-width="1.5" stroke-linecap="round"/>
<circle cx="6.5" cy="5" r="1" fill="#262626"/>
<circle cx="5" cy="8" r="1" fill="#262626"/>
<circle cx="11" cy="8" r="1" fill="#262626"/>
<circle cx="9.5" cy="5" r="1" fill="#262626"/>
<circle cx="8" cy="8" r="6" stroke="#262626" stroke-width="1.5"/>
<circle cx="8" cy="8" r="1" fill="#262626"/>
</svg>

After

Width:  |  Height:  |  Size: 479 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

@ -0,0 +1,2 @@
<!DOCTYPE RCC>
<RCC version="1.0"/>

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.5 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

@ -23,6 +23,8 @@ SOURCES += \
src/virtualkeyboard/virtualkeyboardmanager.cpp \
src/virtualkeyboard/virtualkeyboardmodel.cpp \
src/virtualkeyboard/virtualkeyboardview.cpp \
src/virtualkeyboardentry/floatbutton.cpp \
src/virtualkeyboardentry/floatbuttonmanager.cpp \
src/virtualkeyboardentry/virtualkeyboardtrayicon.cpp
TRANSLATIONS = translations/translation.ts \
@ -31,6 +33,7 @@ TRANSLATIONS = translations/translation.ts \
translations/translation_zh_CN.ts
RESOURCES += \
floatbutton.qrc \
qml.qrc \
translations.qrc
@ -64,6 +67,9 @@ HEADERS += \
src/virtualkeyboard/virtualkeyboardstrategy.h \
src/virtualkeyboard/virtualkeyboardview.h \
src/virtualkeyboardentry/fcitxvirtualkeyboardservice.h \
src/virtualkeyboardentry/floatbutton.h \
src/virtualkeyboardentry/floatbuttonmanager.h \
src/virtualkeyboardentry/floatbuttonstrategy.h \
src/virtualkeyboardentry/virtualkeyboardtrayicon.h
DISTFILES +=

View File

@ -0,0 +1,133 @@
#include "virtualkeyboardentry/floatbutton.h"
#include <QBitmap>
#include <QPainter>
#include <QVariant>
FloatButton::FloatButton(MouseClickedCallback mouseClickedCallback)
: mouseClickedCallback_(std::move(mouseClickedCallback)) {
// 主题框架默认禁用了move消息因此QPushButton需要禁用主题框架
setProperty("useStyleWindowManager", QVariant(false));
initStyle();
}
void FloatButton::move(int x, int y) { QPushButton::move(x, y); }
void FloatButton::resize(int width, int height) {
setFixedSize(width, height);
setIconSize(size());
updateBorderRadius();
QPushButton::resize(width, height);
}
void FloatButton::mousePressEvent(QMouseEvent *event) {
if (event->button() == Qt::LeftButton) {
startX_ = event->pos().x();
startY_ = event->pos().y();
startClickTimer();
}
QPushButton::mousePressEvent(event);
}
bool FloatButton::shouldPerformMouseClick() const {
return !isFloatButtonMoved() && clickTimer_->isActive();
}
void FloatButton::processMouseReleaseEvent() {
emit mouseReleased();
manhattonLength = 0;
startX_ = -1;
startY_ = -1;
}
void FloatButton::processMouseClickEvent() {
if (!mouseClickedCallback_) {
return;
}
mouseClickedCallback_();
}
void FloatButton::mouseReleaseEvent(QMouseEvent *event) {
QPushButton::mouseReleaseEvent(event);
if (event->button() != Qt::LeftButton) {
return;
}
bool couldPerformMouseClick = shouldPerformMouseClick();
stopClickTimer();
if (couldPerformMouseClick) {
processMouseClickEvent();
return;
}
if (isFloatButtonMoved()) {
processMouseReleaseEvent();
}
}
void FloatButton::updateManhattonLength(QMouseEvent *event) {
int offsetX = event->pos().x() - startX_;
int offsetY = event->pos().y() - startY_;
manhattonLength += std::abs(offsetX) + std::abs(offsetY);
}
bool FloatButton::isFloatButtonMoved() const {
return manhattonLength > manhattonLengthThreshold;
}
void FloatButton::processMouseMoveEvent(QMouseEvent *event) {
if (!isFloatButtonMoved()) {
updateManhattonLength(event);
} else {
emit mouseMoved(event->pos().x() - startX_, event->pos().y() - startY_);
}
}
void FloatButton::mouseMoveEvent(QMouseEvent *event) {
if (event->buttons() & Qt::LeftButton) {
processMouseMoveEvent(event);
}
QPushButton::mouseMoveEvent(event);
}
void FloatButton::initStyle() {
setAttribute(Qt::WA_TranslucentBackground);
setStyleSheet("QPushButton{border-image: "
"url(:/floatbutton/img/floatbuttondefault.svg);}"
"QPushButton:hover{border-image: "
"url(:/floatbutton/img/floatbuttonhovered.svg);}"
"QPushButton:pressed{border-image: "
"url(:/floatbutton/img/floatbuttonpressed.svg);}");
}
void FloatButton::updateBorderRadius() {
int w = width();
int h = height();
QBitmap bmp(w, h);
bmp.fill();
QPainter p(&bmp);
p.setPen(Qt::NoPen);
p.setBrush(Qt::black);
p.drawRoundedRect(bmp.rect(), w, h);
setMask(bmp);
}
void FloatButton::startClickTimer() {
clickTimer_.reset(new QTimer());
clickTimer_->setSingleShot(true);
clickTimer_->start(clickTimeThreshold_);
}
void FloatButton::stopClickTimer() {
if (clickTimer_ == nullptr) {
clickTimer_.reset();
}
}

View File

@ -0,0 +1,62 @@
#ifndef FLOATBUTTON_H
#define FLOATBUTTON_H
#include <functional>
#include <memory>
#include <QMouseEvent>
#include <QPushButton>
#include <QSize>
#include <QTimer>
class FloatButton : public QPushButton {
Q_OBJECT
public:
using MouseClickedCallback = std::function<void()>;
public:
explicit FloatButton(MouseClickedCallback mouseClickedCallback);
~FloatButton() override = default;
signals:
void mouseMoved(int x, int y);
void mouseReleased();
public slots:
void move(int x, int y);
void resize(int width, int height);
protected:
void mousePressEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
private:
bool shouldPerformMouseClick() const;
void processMouseReleaseEvent();
void processMouseClickEvent();
void processMouseMoveEvent(QMouseEvent *event);
void updateManhattonLength(QMouseEvent *event);
bool isFloatButtonMoved() const;
void initStyle();
void updateBorderRadius();
void startClickTimer();
void stopClickTimer();
private:
int startX_ = -1;
int startY_ = -1;
int manhattonLength = 0;
constexpr static int manhattonLengthThreshold = 10;
MouseClickedCallback mouseClickedCallback_;
std::unique_ptr<QTimer> clickTimer_ = nullptr;
const int clickTimeThreshold_ = 600;
};
#endif // FLOATBUTTON_H

View File

@ -0,0 +1,162 @@
#include "virtualkeyboardentry/floatbuttonmanager.h"
#include <QEvent>
#include <QGuiApplication>
#include <QIcon>
#include <QMouseEvent>
#include <QScreen>
#include <QTime>
#include "geometrymanager/floatgeometrymanager.h"
#include "geometrymanager/geometrymanager.h"
#include "virtualkeyboardentry/floatbuttonstrategy.h"
// static
const QString floatButtonGroup = "floatButton";
// static
const QString floatButtonEnabledKey = "floatButtonEnabled";
FloatButtonManager::FloatButtonManager(
const VirtualKeyboardManager &virtualKeyboardManager,
const FcitxVirtualKeyboardService &fcitxVirtualKeyboardService)
: virtualKeyboardManager_(virtualKeyboardManager),
fcitxVirtualKeyboardService_(fcitxVirtualKeyboardService) {
initGeometryManager();
initInternalSignalConnections();
initScreenSignalConnections();
}
void FloatButtonManager::loadFloatButtonAvailability() {
const bool floatButtonEnabled =
floatButtonSettings_
.getValue(floatButtonGroup, floatButtonEnabledKey, true)
.value<bool>();
updateFloatButtonEnabled(floatButtonEnabled);
}
void FloatButtonManager::enableFloatButton() { setFloatButtonEnabled(true); }
void FloatButtonManager::disableFloatButton() { setFloatButtonEnabled(false); }
void FloatButtonManager::initGeometryManager() {
geometryManager_.reset(new FloatGeometryManager(
std::unique_ptr<FloatGeometryManager::Strategy>(
new FloatButtonStrategy()),
floatButtonSettings_));
}
void FloatButtonManager::initScreenSignalConnections() {
connect(QGuiApplication::primaryScreen(), &QScreen::geometryChanged, this,
&FloatButtonManager::onScreenResolutionChanged);
}
void FloatButtonManager::initInternalSignalConnections() {
connect(this, &FloatButtonManager::floatButtonEnabled, this,
&FloatButtonManager::initFloatButton);
connect(this, &FloatButtonManager::floatButtonDisabled, this,
&FloatButtonManager::destroyFloatButton);
}
void FloatButtonManager::initFloatButton() {
createFloatButton();
connectFloatButtonSignals();
geometryManager_->updateGeometry();
}
void FloatButtonManager::destroyFloatButton() {
if (floatButton_ == nullptr) {
return;
}
// 销毁之前必须隐藏,否则会导致虚拟键盘进程
// 直接退出
floatButton_->hide();
floatButton_.reset();
}
void FloatButtonManager::onScreenResolutionChanged() {
if (!floatButtonEnabled_ || !floatButton_->isVisible()) {
return;
}
geometryManager_->updateGeometry();
}
void FloatButtonManager::showFloatButton() {
if (!floatButtonEnabled_) {
return;
}
floatButton_->show();
}
void FloatButtonManager::hideFloatButton() {
if (!floatButtonEnabled_) {
return;
}
floatButton_->hide();
}
void FloatButtonManager::createFloatButton() {
floatButton_.reset(new FloatButton(
[this]() { fcitxVirtualKeyboardService_.showVirtualKeyboard(); }));
floatButton_->setWindowFlags(Qt::FramelessWindowHint |
Qt::BypassWindowManagerHint | Qt::Tool);
floatButton_->show();
}
void FloatButtonManager::connectFloatButtonSignals() {
connect(floatButton_.get(), &FloatButton::mouseMoved,
geometryManager_.get(), &FloatGeometryManager::moveBy);
connect(floatButton_.get(), &FloatButton::mouseReleased,
geometryManager_.get(), &FloatGeometryManager::endDrag);
connect(geometryManager_.get(), &FloatGeometryManager::viewMoved,
floatButton_.get(), &FloatButton::move);
connect(geometryManager_.get(), &FloatGeometryManager::viewResized,
floatButton_.get(), &FloatButton::resize);
connect(&virtualKeyboardManager_,
&VirtualKeyboardManager::virtualKeyboardVisibiltyChanged,
floatButton_.get(), [this](bool visible) {
if (visible) {
hideFloatButton();
} else {
showFloatButton();
}
});
connect(floatButtonView_.get(), &QPushButton::clicked, this,
[this]() { fcitxVirtualKeyboardService_.showVirtualKeyboard(); });
}
void FloatButtonManager::updateFloatButtonEnabled(bool enabled) {
floatButtonEnabled_ = enabled;
if (floatButtonEnabled_) {
emit floatButtonEnabled();
} else {
emit floatButtonDisabled();
}
}
void FloatButtonManager::setFloatButtonEnabled(bool enabled) {
if (floatButtonEnabled_ == enabled) {
return;
}
updateFloatButtonEnabled(enabled);
saveFloatButtonAvailablity();
}
void FloatButtonManager::saveFloatButtonAvailablity() {
floatButtonSettings_.setValue(floatButtonGroup, floatButtonEnabledKey,
floatButtonEnabled_);
}

View File

@ -0,0 +1,69 @@
#ifndef FLOATBUTTONMANAGER_H
#define FLOATBUTTONMANAGER_H
#include <memory>
#include <QDateTime>
#include <QObject>
#include <QPushButton>
#include "localsettings/viewlocalsettings.h"
#include "virtualkeyboard/virtualkeyboardmanager.h"
#include "virtualkeyboardentry/fcitxvirtualkeyboardservice.h"
#include "virtualkeyboardentry/floatbutton.h"
class FloatButtonManager : public QObject {
Q_OBJECT
public:
FloatButtonManager(
const VirtualKeyboardManager &virtualKeyboardManager,
const FcitxVirtualKeyboardService &fcitxVirtualKeyboardService);
~FloatButtonManager() override = default;
void loadFloatButtonAvailability();
void enableFloatButton();
void disableFloatButton();
signals:
void floatButtonEnabled();
void floatButtonDisabled();
private slots:
void initFloatButton();
void destroyFloatButton();
void onScreenResolutionChanged();
private:
void initGeometryManager();
void initInternalSignalConnections();
void initScreenSignalConnections();
void createFloatButton();
void connectFloatButtonSignals();
void showFloatButton();
void hideFloatButton();
void updateFloatButtonEnabled(bool enabled);
void setFloatButtonEnabled(bool enabled);
void saveFloatButtonAvailablity();
private:
bool floatButtonEnabled_ = true;
ViewLocalSettings floatButtonSettings_{"kylinsoft", "kylin float button"};
const VirtualKeyboardManager &virtualKeyboardManager_;
const FcitxVirtualKeyboardService &fcitxVirtualKeyboardService_;
std::unique_ptr<FloatButton> floatButton_ = nullptr;
std::unique_ptr<FloatGeometryManager> geometryManager_ = nullptr;
};
#endif // FLOATBUTTONMANAGER_H

View File

@ -0,0 +1,42 @@
#ifndef FLOATBUTTONSTRATEGY_H
#define FLOATBUTTONSTRATEGY_H
#include "geometrymanager/floatgeometrymanager.h"
#include "screenmanager.h"
class FloatButtonStrategy : public FloatGeometryManager::Strategy {
public:
FloatButtonStrategy() = default;
~FloatButtonStrategy() override = default;
int getDefaultRightMargin() const override {
return getUnitWidth() * defaultRightMarginRatio_;
}
int getDefaultBottomMargin() const override {
return getUnitHeight() * defaultBottomMarginRatio_;
}
private:
int getUnitWidth() const override {
const auto viewPortSize = ScreenManager::getPrimaryScreenSize();
return std::max(viewPortSize.width(), viewPortSize.height());
}
int getUnitHeight() const override {
const auto viewPortSize = ScreenManager::getPrimaryScreenSize();
return std::max(viewPortSize.width(), viewPortSize.height());
}
float getViewWidthRatio() const override { return 56.0 / 1620.0; }
float getViewHeightRatio() const override { return 56.0 / 1620.0; }
private:
static constexpr float defaultBottomMarginRatio_ = 0.03f;
static constexpr float defaultRightMarginRatio_ = 0.02f;
};
#endif // FLOATBUTTONSTRATEGY_H