Remove appbase as an external dependency.

This commit is contained in:
fourtf 2019-07-23 22:18:36 +02:00
parent 628c64d138
commit a85e5821ba
65 changed files with 8737 additions and 9 deletions

16
.gitmodules vendored
View file

@ -14,7 +14,15 @@
[submodule "lib/WinToast"]
path = lib/WinToast
url = https://github.com/mohabouje/WinToast.git
[submodule "lib/appbase"]
path = lib/appbase
url = https://github.com/Chatterino/appbase
[submodule "lib/settings"]
path = lib/settings
url = https://github.com/pajlada/settings
[submodule "lib/signals"]
path = lib/signals
url = https://github.com/pajlada/signals
[submodule "lib/serialize"]
path = lib/serialize
url = https://github.com/pajlada/serialize
[submodule "lib/rapidjson"]
path = lib/rapidjson
url = https://github.com/Tencent/rapidjson

View file

@ -19,6 +19,13 @@ useBreakpad {
DEFINES += C_USE_BREAKPAD
}
# use C++17
win32-msvc* {
QMAKE_CXXFLAGS += /std:c++17
} else {
QMAKE_CXXFLAGS += -std=c++17
}
# https://bugreports.qt.io/browse/QTBUG-27018
equals(QMAKE_CXX, "clang++")|equals(QMAKE_CXX, "g++") {
TARGET = bin/chatterino
@ -33,13 +40,21 @@ macx {
}
# Submodules
include(lib/appbase.pri)
include(lib/humanize.pri)
DEFINES += IRC_NAMESPACE=Communi
include(lib/appbase.pri)
include(lib/boost.pri)
include(lib/fmt.pri)
include(lib/humanize.pri)
include(lib/libcommuni.pri)
include(lib/websocketpp.pri)
include(lib/openssl.pri)
include(lib/wintoast.pri)
include(lib/signals.pri)
include(lib/settings.pri)
include(lib/serialize.pri)
include(lib/winsdk.pri)
include(lib/rapidjson.pri)
# Optional feature: QtWebEngine
#exists ($(QTDIR)/include/QtWebEngine/QtWebEngine) {

@ -1 +0,0 @@
Subproject commit d054925734cf26576346a1da856f7ab0d4b6c0a5

View file

@ -1,3 +1,3 @@
# include appbase
include($$PWD/appbase/appbase/main/main.pro)
INCLUDEPATH += $$PWD/appbase/appbase/main
include($$PWD/appbase/main.pro)
INCLUDEPATH += $$PWD/appbase

View file

@ -0,0 +1,128 @@
#include "BaseSettings.hpp"
#include <QDebug>
#include "util/Clamp.hpp"
namespace AB_NAMESPACE {
std::vector<std::weak_ptr<pajlada::Settings::SettingData>> _settings;
AB_SETTINGS_CLASS *AB_SETTINGS_CLASS::instance = nullptr;
void _actuallyRegisterSetting(
std::weak_ptr<pajlada::Settings::SettingData> setting)
{
_settings.push_back(setting);
}
AB_SETTINGS_CLASS::AB_SETTINGS_CLASS(const QString &settingsDirectory)
{
AB_SETTINGS_CLASS::instance = this;
QString settingsPath = settingsDirectory + "/settings.json";
// get global instance of the settings library
auto settingsInstance = pajlada::Settings::SettingManager::getInstance();
settingsInstance->load(qPrintable(settingsPath));
settingsInstance->setBackupEnabled(true);
settingsInstance->setBackupSlots(9);
settingsInstance->saveMethod =
pajlada::Settings::SettingManager::SaveMethod::SaveOnExit;
}
void AB_SETTINGS_CLASS::saveSnapshot()
{
rapidjson::Document *d = new rapidjson::Document(rapidjson::kObjectType);
rapidjson::Document::AllocatorType &a = d->GetAllocator();
for (const auto &weakSetting : _settings)
{
auto setting = weakSetting.lock();
if (!setting)
{
continue;
}
rapidjson::Value key(setting->getPath().c_str(), a);
auto curVal = setting->unmarshalJSON();
if (curVal == nullptr)
{
continue;
}
rapidjson::Value val;
val.CopyFrom(*curVal, a);
d->AddMember(key.Move(), val.Move(), a);
}
// log("Snapshot state: {}", rj::stringify(*d));
this->snapshot_.reset(d);
}
void AB_SETTINGS_CLASS::restoreSnapshot()
{
if (!this->snapshot_)
{
return;
}
const auto &snapshot = *(this->snapshot_.get());
if (!snapshot.IsObject())
{
return;
}
for (const auto &weakSetting : _settings)
{
auto setting = weakSetting.lock();
if (!setting)
{
continue;
}
const char *path = setting->getPath().c_str();
if (!snapshot.HasMember(path))
{
continue;
}
setting->marshalJSON(snapshot[path]);
}
}
float AB_SETTINGS_CLASS::getClampedUiScale() const
{
return clamp<float>(this->uiScale.getValue(), 0.1, 10);
}
void AB_SETTINGS_CLASS::setClampedUiScale(float value)
{
this->uiScale.setValue(clamp<float>(value, 0.1, 10));
}
#ifndef AB_CUSTOM_SETTINGS
Settings *getSettings()
{
static_assert(std::is_same_v<AB_SETTINGS_CLASS, Settings>,
"`AB_SETTINGS_CLASS` must be the same as `Settings`");
assert(AB_SETTINGS_CLASS::instance);
return AB_SETTINGS_CLASS::instance;
}
#endif
AB_SETTINGS_CLASS *getABSettings()
{
assert(AB_SETTINGS_CLASS::instance);
return AB_SETTINGS_CLASS::instance;
}
} // namespace AB_NAMESPACE

View file

@ -0,0 +1,52 @@
#ifndef AB_SETTINGS_H
#define AB_SETTINGS_H
#include <rapidjson/document.h>
#include <QString>
#include <memory>
#include <pajlada/settings/settingdata.hpp>
#include "common/ChatterinoSetting.hpp"
#ifdef AB_CUSTOM_SETTINGS
# define AB_SETTINGS_CLASS ABSettings
#else
# define AB_SETTINGS_CLASS Settings
#endif
namespace AB_NAMESPACE {
class Settings;
void _actuallyRegisterSetting(
std::weak_ptr<pajlada::Settings::SettingData> setting);
class AB_SETTINGS_CLASS
{
public:
AB_SETTINGS_CLASS(const QString &settingsDirectory);
void saveSnapshot();
void restoreSnapshot();
static AB_SETTINGS_CLASS *instance;
FloatSetting uiScale = {"/appearance/uiScale2", 1};
BoolSetting windowTopMost = {"/appearance/windowAlwaysOnTop", false};
float getClampedUiScale() const;
void setClampedUiScale(float value);
private:
std::unique_ptr<rapidjson::Document> snapshot_;
};
Settings *getSettings();
AB_SETTINGS_CLASS *getABSettings();
} // namespace AB_NAMESPACE
#ifdef CHATTERINO
# include "singletons/Settings.hpp"
#endif
#endif

226
lib/appbase/BaseTheme.cpp Normal file
View file

@ -0,0 +1,226 @@
#include "BaseTheme.hpp"
namespace AB_NAMESPACE {
namespace {
double getMultiplierByTheme(const QString &themeName)
{
if (themeName == "Light")
{
return 0.8;
}
else if (themeName == "White")
{
return 1.0;
}
else if (themeName == "Black")
{
return -1.0;
}
else if (themeName == "Dark")
{
return -0.8;
}
/*
else if (themeName == "Custom")
{
return getSettings()->customThemeMultiplier.getValue();
}
*/
return -0.8;
}
} // namespace
bool AB_THEME_CLASS::isLightTheme() const
{
return this->isLight_;
}
void AB_THEME_CLASS::update()
{
this->actuallyUpdate(this->themeHue,
getMultiplierByTheme(this->themeName.getValue()));
this->updated.invoke();
}
void AB_THEME_CLASS::actuallyUpdate(double hue, double multiplier)
{
this->isLight_ = multiplier > 0;
bool lightWin = isLight_;
// QColor themeColor = QColor::fromHslF(hue, 0.43, 0.5);
QColor themeColor = QColor::fromHslF(hue, 0.8, 0.5);
QColor themeColorNoSat = QColor::fromHslF(hue, 0, 0.5);
qreal sat = 0;
// 0.05;
auto getColor = [multiplier](double h, double s, double l, double a = 1.0) {
return QColor::fromHslF(h, s, ((l - 0.5) * multiplier) + 0.5, a);
};
/// WINDOW
{
QColor bg =
#ifdef Q_OS_LINUX
this->window.background = lightWin ? "#fff" : QColor(61, 60, 56);
#else
this->window.background = lightWin ? "#fff" : "#111";
#endif
QColor fg = this->window.text = lightWin ? "#000" : "#eee";
this->window.borderFocused = lightWin ? "#ccc" : themeColor;
this->window.borderUnfocused = lightWin ? "#ccc" : themeColorNoSat;
// Ubuntu style
// TODO: add setting for this
// TabText = QColor(210, 210, 210);
// TabBackground = QColor(61, 60, 56);
// TabHoverText = QColor(210, 210, 210);
// TabHoverBackground = QColor(73, 72, 68);
// message (referenced later)
this->messages.textColors.caret = //
this->messages.textColors.regular = isLight_ ? "#000" : "#fff";
QColor highlighted = lightWin ? QColor("#ff0000") : QColor("#ee6166");
/// TABS
if (lightWin)
{
this->tabs.regular = {
QColor("#444"),
{QColor("#fff"), QColor("#eee"), QColor("#fff")},
{QColor("#fff"), QColor("#fff"), QColor("#fff")}};
this->tabs.newMessage = {
QColor("#222"),
{QColor("#fff"), QColor("#eee"), QColor("#fff")},
{QColor("#bbb"), QColor("#bbb"), QColor("#bbb")}};
this->tabs.highlighted = {
fg,
{QColor("#fff"), QColor("#eee"), QColor("#fff")},
{highlighted, highlighted, highlighted}};
this->tabs.selected = {
QColor("#000"),
{QColor("#b4d7ff"), QColor("#b4d7ff"), QColor("#b4d7ff")},
{QColor("#00aeef"), QColor("#00aeef"), QColor("#00aeef")}};
}
else
{
this->tabs.regular = {
QColor("#aaa"),
{QColor("#252525"), QColor("#252525"), QColor("#252525")},
{QColor("#444"), QColor("#444"), QColor("#444")}};
this->tabs.newMessage = {
fg,
{QColor("#252525"), QColor("#252525"), QColor("#252525")},
{QColor("#888"), QColor("#888"), QColor("#888")}};
this->tabs.highlighted = {
fg,
{QColor("#252525"), QColor("#252525"), QColor("#252525")},
{highlighted, highlighted, highlighted}};
this->tabs.selected = {
QColor("#fff"),
{QColor("#555555"), QColor("#555555"), QColor("#555555")},
{QColor("#00aeef"), QColor("#00aeef"), QColor("#00aeef")}};
}
// scrollbar
this->scrollbars.highlights.highlight = QColor("#ee6166");
this->scrollbars.highlights.subscription = QColor("#C466FF");
// this->tabs.newMessage = {
// fg,
// {QBrush(blendColors(themeColor, "#ccc", 0.9), Qt::FDiagPattern),
// QBrush(blendColors(themeColor, "#ccc", 0.9), Qt::FDiagPattern),
// QBrush(blendColors(themeColorNoSat, "#ccc", 0.9),
// Qt::FDiagPattern)}};
// this->tabs.newMessage = {
// fg,
// {QBrush(blendColors(themeColor, "#666", 0.7),
// Qt::FDiagPattern),
// QBrush(blendColors(themeColor, "#666", 0.5),
// Qt::FDiagPattern),
// QBrush(blendColors(themeColorNoSat, "#666", 0.7),
// Qt::FDiagPattern)}};
// this->tabs.highlighted = {fg, {QColor("#777"),
// QColor("#777"), QColor("#666")}};
this->tabs.bottomLine = this->tabs.selected.backgrounds.regular.color();
} // namespace AB_NAMESPACE
// Split
bool flat = isLight_;
// Message
this->messages.textColors.link =
isLight_ ? QColor(66, 134, 244) : QColor(66, 134, 244);
this->messages.textColors.system = QColor(140, 127, 127);
this->messages.backgrounds.regular = getColor(0, sat, 1);
this->messages.backgrounds.alternate = getColor(0, sat, 0.96);
if (isLight_)
{
this->messages.backgrounds.highlighted =
blendColors(themeColor, this->messages.backgrounds.regular, 0.8);
}
else
{
// REMOVED
// this->messages.backgrounds.highlighted =
// QColor(getSettings()->highlightColor);
}
this->messages.backgrounds.subscription =
blendColors(QColor("#C466FF"), this->messages.backgrounds.regular, 0.7);
// this->messages.backgrounds.resub
// this->messages.backgrounds.whisper
this->messages.disabled = getColor(0, sat, 1, 0.6);
// this->messages.seperator =
// this->messages.seperatorInner =
// Scrollbar
this->scrollbars.background = QColor(0, 0, 0, 0);
// this->scrollbars.background = splits.background;
// this->scrollbars.background.setAlphaF(qreal(0.2));
this->scrollbars.thumb = getColor(0, sat, 0.70);
this->scrollbars.thumbSelected = getColor(0, sat, 0.65);
// tooltip
this->tooltip.background = QColor(0, 0, 0);
this->tooltip.text = QColor(255, 255, 255);
// Selection
this->messages.selection =
isLightTheme() ? QColor(0, 0, 0, 64) : QColor(255, 255, 255, 64);
}
QColor AB_THEME_CLASS::blendColors(const QColor &color1, const QColor &color2,
qreal ratio)
{
int r = int(color1.red() * (1 - ratio) + color2.red() * ratio);
int g = int(color1.green() * (1 - ratio) + color2.green() * ratio);
int b = int(color1.blue() * (1 - ratio) + color2.blue() * ratio);
return QColor(r, g, b, 255);
}
#ifndef AB_CUSTOM_THEME
Theme *getTheme()
{
static auto theme = [] {
auto theme = new Theme();
theme->update();
return theme;
}();
return theme;
}
#endif
} // namespace AB_NAMESPACE

117
lib/appbase/BaseTheme.hpp Normal file
View file

@ -0,0 +1,117 @@
#ifndef AB_THEME_H
#define AB_THEME_H
#include <QBrush>
#include <QColor>
#include <common/ChatterinoSetting.hpp>
#ifdef AB_CUSTOM_THEME
# define AB_THEME_CLASS BaseTheme
#else
# define AB_THEME_CLASS Theme
#endif
namespace AB_NAMESPACE {
class Theme;
class AB_THEME_CLASS
{
public:
bool isLightTheme() const;
struct TabColors {
QColor text;
struct {
QBrush regular;
QBrush hover;
QBrush unfocused;
} backgrounds;
struct {
QColor regular;
QColor hover;
QColor unfocused;
} line;
};
/// WINDOW
struct {
QColor background;
QColor text;
QColor borderUnfocused;
QColor borderFocused;
} window;
/// TABS
struct {
TabColors regular;
TabColors newMessage;
TabColors highlighted;
TabColors selected;
QColor border;
QColor bottomLine;
} tabs;
/// MESSAGES
struct {
struct {
QColor regular;
QColor caret;
QColor link;
QColor system;
} textColors;
struct {
QColor regular;
QColor alternate;
QColor highlighted;
QColor subscription;
// QColor whisper;
} backgrounds;
QColor disabled;
// QColor seperator;
// QColor seperatorInner;
QColor selection;
} messages;
/// SCROLLBAR
struct {
QColor background;
QColor thumb;
QColor thumbSelected;
struct {
QColor highlight;
QColor subscription;
} highlights;
} scrollbars;
/// TOOLTIP
struct {
QColor text;
QColor background;
} tooltip;
void update();
virtual void actuallyUpdate(double hue, double multiplier);
QColor blendColors(const QColor &color1, const QColor &color2, qreal ratio);
pajlada::Signals::NoArgSignal updated;
QStringSetting themeName{"/appearance/theme/name", "Dark"};
DoubleSetting themeHue{"/appearance/theme/hue", 0.0};
private:
bool isLight_ = false;
};
// Implemented in parent project if AB_CUSTOM_THEME is set.
// Otherwise implemented in BaseThemecpp
Theme *getTheme();
} // namespace AB_NAMESPACE
#ifdef CHATTERINO
# include "singletons/Theme.hpp"
#endif
#endif

View file

@ -0,0 +1,36 @@
#pragma once
#include <QHash>
#include <QString>
#include <functional>
#define QStringAlias(name) \
namespace chatterino { \
struct name { \
QString string; \
bool operator==(const name &other) const \
{ \
return this->string == other.string; \
} \
bool operator!=(const name &other) const \
{ \
return this->string != other.string; \
} \
}; \
} /* namespace chatterino */ \
namespace std { \
template <> \
struct hash<chatterino::name> { \
size_t operator()(const chatterino::name &s) const \
{ \
return qHash(s.string); \
} \
}; \
} /* namespace std */
QStringAlias(UserName);
QStringAlias(UserId);
QStringAlias(Url);
QStringAlias(Tooltip);
QStringAlias(EmoteId);
QStringAlias(EmoteName);

View file

@ -0,0 +1,12 @@
#include "common/ChatterinoSetting.hpp"
#include "BaseSettings.hpp"
namespace AB_NAMESPACE {
void _registerSetting(std::weak_ptr<pajlada::Settings::SettingData> setting)
{
_actuallyRegisterSetting(setting);
}
} // namespace AB_NAMESPACE

View file

@ -0,0 +1,54 @@
#pragma once
#include <QString>
#include <pajlada/settings.hpp>
namespace AB_NAMESPACE {
void _registerSetting(std::weak_ptr<pajlada::Settings::SettingData> setting);
template <typename Type>
class ChatterinoSetting : public pajlada::Settings::Setting<Type>
{
public:
ChatterinoSetting(const std::string &path)
: pajlada::Settings::Setting<Type>(path)
{
_registerSetting(this->getData());
}
ChatterinoSetting(const std::string &path, const Type &defaultValue)
: pajlada::Settings::Setting<Type>(path, defaultValue)
{
_registerSetting(this->getData());
}
template <typename T2>
ChatterinoSetting &operator=(const T2 &newValue)
{
this->setValue(newValue);
return *this;
}
ChatterinoSetting &operator=(Type &&newValue) noexcept
{
pajlada::Settings::Setting<Type>::operator=(newValue);
return *this;
}
using pajlada::Settings::Setting<Type>::operator==;
using pajlada::Settings::Setting<Type>::operator!=;
using pajlada::Settings::Setting<Type>::operator Type;
};
using BoolSetting = ChatterinoSetting<bool>;
using FloatSetting = ChatterinoSetting<float>;
using DoubleSetting = ChatterinoSetting<double>;
using IntSetting = ChatterinoSetting<int>;
using StringSetting = ChatterinoSetting<std::string>;
using QStringSetting = ChatterinoSetting<QString>;
} // namespace AB_NAMESPACE

View file

@ -0,0 +1,82 @@
#pragma once
#include <type_traits>
namespace chatterino {
template <typename T, typename Q = typename std::underlying_type<T>::type>
class FlagsEnum
{
public:
FlagsEnum()
: value_(static_cast<T>(0))
{
}
FlagsEnum(T value)
: value_(value)
{
}
FlagsEnum(std::initializer_list<T> flags)
{
for (auto flag : flags)
{
this->set(flag);
}
}
bool operator==(const FlagsEnum<T> &other)
{
return this->value_ == other.value_;
}
bool operator!=(const FlagsEnum &other)
{
return this->value_ != other.value_;
}
void set(T flag)
{
reinterpret_cast<Q &>(this->value_) |= static_cast<Q>(flag);
}
void unset(T flag)
{
reinterpret_cast<Q &>(this->value_) &= ~static_cast<Q>(flag);
}
void set(T flag, bool value)
{
if (value)
this->set(flag);
else
this->unset(flag);
}
bool has(T flag) const
{
return static_cast<Q>(this->value_) & static_cast<Q>(flag);
}
bool hasAny(FlagsEnum flags) const
{
return static_cast<Q>(this->value_) & static_cast<Q>(flags.value_);
}
bool hasAll(FlagsEnum<T> flags) const
{
return (static_cast<Q>(this->value_) & static_cast<Q>(flags.value_)) &&
static_cast<Q>(flags->value);
}
bool hasNone(std::initializer_list<T> flags) const
{
return !this->hasAny(flags);
}
private:
T value_{};
};
} // namespace chatterino

View file

@ -0,0 +1,51 @@
#pragma once
namespace chatterino {
struct SuccessTag {
};
struct FailureTag {
};
const SuccessTag Success{};
const FailureTag Failure{};
class Outcome
{
public:
Outcome(SuccessTag)
: success_(true)
{
}
Outcome(FailureTag)
: success_(false)
{
}
explicit operator bool() const
{
return this->success_;
}
bool operator!() const
{
return !this->success_;
}
bool operator==(const Outcome &other) const
{
return this->success_ == other.success_;
}
bool operator!=(const Outcome &other) const
{
return !this->operator==(other);
}
private:
bool success_;
};
} // namespace chatterino

View file

@ -0,0 +1,26 @@
#pragma once
#include <boost/noncopyable.hpp>
namespace AB_NAMESPACE {
class Settings;
class Paths;
class Singleton : boost::noncopyable
{
public:
virtual ~Singleton() = default;
virtual void initialize(Settings &settings, Paths &paths)
{
(void)(settings);
(void)(paths);
}
virtual void save()
{
}
};
} // namespace AB_NAMESPACE

View file

@ -0,0 +1,16 @@
#pragma once
#include <QCoreApplication>
#include <QThread>
#include <cassert>
namespace AB_NAMESPACE {
static void assertInGuiThread()
{
#ifdef _DEBUG
assert(QCoreApplication::instance()->thread() == QThread::currentThread());
#endif
}
} // namespace AB_NAMESPACE

View file

@ -0,0 +1,21 @@
#include "Benchmark.hpp"
namespace AB_NAMESPACE {
BenchmarkGuard::BenchmarkGuard(const QString &_name)
: name_(_name)
{
timer_.start();
}
BenchmarkGuard::~BenchmarkGuard()
{
log("{} {} ms", this->name_, float(timer_.nsecsElapsed()) / 1000000.0f);
}
qreal BenchmarkGuard::getElapsedMs()
{
return qreal(timer_.nsecsElapsed()) / 1000000.0;
}
} // namespace AB_NAMESPACE

View file

@ -0,0 +1,22 @@
#pragma once
#include "debug/Log.hpp"
#include <QElapsedTimer>
#include <boost/noncopyable.hpp>
namespace AB_NAMESPACE {
class BenchmarkGuard : boost::noncopyable
{
public:
BenchmarkGuard(const QString &_name);
~BenchmarkGuard();
qreal getElapsedMs();
private:
QElapsedTimer timer_;
QString name_;
};
} // namespace AB_NAMESPACE

29
lib/appbase/debug/Log.hpp Normal file
View file

@ -0,0 +1,29 @@
#pragma once
#include "util/Helpers.hpp"
#include <QDebug>
#include <QTime>
namespace AB_NAMESPACE {
template <typename... Args>
inline void log(const std::string &formatString, Args &&... args)
{
qDebug().noquote() << QTime::currentTime().toString("hh:mm:ss.zzz")
<< fS(formatString, std::forward<Args>(args)...).c_str();
}
template <typename... Args>
inline void log(const char *formatString, Args &&... args)
{
log(std::string(formatString), std::forward<Args>(args)...);
}
template <typename... Args>
inline void log(const QString &formatString, Args &&... args)
{
log(formatString.toStdString(), std::forward<Args>(args)...);
}
} // namespace AB_NAMESPACE

31
lib/appbase/main.cpp Normal file
View file

@ -0,0 +1,31 @@
#include <QApplication>
#include <QDebug>
#include <QDir>
#include <QStandardPaths>
#include "ABSettings.hpp"
#include "ABTheme.hpp"
#include "singletons/Fonts.hpp"
#include "widgets/BaseWindow.hpp"
int main(int argc, char *argv[])
{
using namespace AB_NAMESPACE;
QApplication a(argc, argv);
auto path =
QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
qDebug() << path;
QDir(path).mkdir(".");
new Settings(path);
new Fonts();
BaseWindow widget(nullptr, BaseWindow::EnableCustomFrame);
widget.setWindowTitle("asdf");
widget.show();
return a.exec();
}

108
lib/appbase/main.pro Normal file
View file

@ -0,0 +1,108 @@
#-------------------------------------------------
#
# Project created by QtCreator 2018-11-19T19:03:22
#
#-------------------------------------------------
!AB_NOT_STANDALONE {
message(appbase standalone)
QT += core gui widgets
TARGET = main
TEMPLATE = app
SOURCES += main.cpp
# https://bugreports.qt.io/browse/QTBUG-27018
equals(QMAKE_CXX, "clang++")|equals(QMAKE_CXX, "g++") {
TARGET = bin/appbase
}
}
#DEFINES += QT_DEPRECATED_WARNINGS
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000
macx {
# osx (Tested on macOS Mojave and High Sierra)
CONFIG += c++17
} else {
CONFIG += c++17
win32-msvc* {
# win32 msvc
QMAKE_CXXFLAGS += /std:c++17
} else {
# clang/gcc on linux or win32
QMAKE_CXXFLAGS += -std=c++17
}
}
include(../warnings.pri)
include(../lib/winsdk.pri)
include(../lib/fmt.pri)
include(../lib/boost.pri)
include(../lib/rapidjson.pri)
include(../lib/serialize.pri)
include(../lib/signals.pri)
include(../lib/settings.pri)
debug {
DEFINES += QT_DEBUG
}
linux {
LIBS += -lrt
QMAKE_LFLAGS += -lrt
}
macx {
INCLUDEPATH += /usr/local/include
INCLUDEPATH += /usr/local/opt/openssl/include
LIBS += -L/usr/local/opt/openssl/lib
}
SOURCES += \
$$PWD/BaseSettings.cpp \
$$PWD/BaseTheme.cpp \
$$PWD/common/ChatterinoSetting.cpp \
$$PWD/debug/Benchmark.cpp \
$$PWD/singletons/Fonts.cpp \
$$PWD/util/FunctionEventFilter.cpp \
$$PWD/util/FuzzyConvert.cpp \
$$PWD/util/WindowsHelper.cpp \
$$PWD/widgets/BaseWidget.cpp \
$$PWD/widgets/BaseWindow.cpp \
$$PWD/widgets/Label.cpp \
$$PWD/widgets/TooltipWidget.cpp \
$$PWD/widgets/helper/Button.cpp \
$$PWD/widgets/helper/EffectLabel.cpp \
$$PWD/widgets/helper/SignalLabel.cpp \
$$PWD/widgets/helper/TitlebarButton.cpp \
HEADERS += \
$$PWD/BaseSettings.hpp \
$$PWD/BaseTheme.hpp \
$$PWD/common/ChatterinoSetting.hpp \
$$PWD/common/FlagsEnum.hpp \
$$PWD/common/Outcome.hpp \
$$PWD/common/Singleton.hpp \
$$PWD/debug/AssertInGuiThread.hpp \
$$PWD/debug/Benchmark.hpp \
$$PWD/debug/Log.hpp \
$$PWD/singletons/Fonts.hpp \
$$PWD/util/Clamp.hpp \
$$PWD/util/CombinePath.hpp \
$$PWD/util/DistanceBetweenPoints.hpp \
$$PWD/util/FunctionEventFilter.hpp \
$$PWD/util/FuzzyConvert.hpp \
$$PWD/util/Helpers.hpp \
$$PWD/util/LayoutHelper.hpp \
$$PWD/util/PostToThread.hpp \
$$PWD/util/RapidJsonSerializeQString.hpp \
$$PWD/util/Shortcut.hpp \
$$PWD/util/WindowsHelper.hpp \
$$PWD/widgets/BaseWidget.hpp \
$$PWD/widgets/BaseWindow.hpp \
$$PWD/widgets/Label.hpp \
$$PWD/widgets/TooltipWidget.hpp \
$$PWD/widgets/helper/Button.hpp \
$$PWD/widgets/helper/EffectLabel.hpp \
$$PWD/widgets/helper/SignalLabel.hpp \
$$PWD/widgets/helper/TitlebarButton.hpp \

View file

@ -0,0 +1,179 @@
#include "singletons/Fonts.hpp"
#include "BaseSettings.hpp"
#include "debug/AssertInGuiThread.hpp"
#include <QDebug>
#include <QtGlobal>
#ifdef CHATTERINO
# include "Application.hpp"
# include "singletons/WindowManager.hpp"
#endif
#ifdef Q_OS_WIN32
# define DEFAULT_FONT_FAMILY "Segoe UI"
# define DEFAULT_FONT_SIZE 10
#else
# ifdef Q_OS_MACOS
# define DEFAULT_FONT_FAMILY "Helvetica Neue"
# define DEFAULT_FONT_SIZE 12
# else
# define DEFAULT_FONT_FAMILY "Arial"
# define DEFAULT_FONT_SIZE 11
# endif
#endif
namespace AB_NAMESPACE {
namespace {
int getBoldness()
{
#ifdef CHATTERINO
return getSettings()->boldScale.getValue();
#else
return QFont::Bold;
#endif
}
} // namespace
Fonts *Fonts::instance = nullptr;
Fonts::Fonts()
: chatFontFamily("/appearance/currentFontFamily", DEFAULT_FONT_FAMILY)
, chatFontSize("/appearance/currentFontSize", DEFAULT_FONT_SIZE)
{
Fonts::instance = this;
this->fontsByType_.resize(size_t(FontStyle::EndType));
}
void Fonts::initialize(Settings &, Paths &)
{
this->chatFontFamily.connect(
[this]() {
assertInGuiThread();
for (auto &map : this->fontsByType_)
{
map.clear();
}
this->fontChanged.invoke();
},
false);
this->chatFontSize.connect(
[this]() {
assertInGuiThread();
for (auto &map : this->fontsByType_)
{
map.clear();
}
this->fontChanged.invoke();
},
false);
#ifdef CHATTERINO
getSettings()->boldScale.connect(
[this]() {
assertInGuiThread();
// REMOVED
getApp()->windows->incGeneration();
for (auto &map : this->fontsByType_)
{
map.clear();
}
this->fontChanged.invoke();
},
false);
#endif
} // namespace AB_NAMESPACE
QFont Fonts::getFont(FontStyle type, float scale)
{
return this->getOrCreateFontData(type, scale).font;
}
QFontMetrics Fonts::getFontMetrics(FontStyle type, float scale)
{
return this->getOrCreateFontData(type, scale).metrics;
}
Fonts::FontData &Fonts::getOrCreateFontData(FontStyle type, float scale)
{
assertInGuiThread();
assert(type < FontStyle::EndType);
auto &map = this->fontsByType_[size_t(type)];
// find element
auto it = map.find(scale);
if (it != map.end())
{
// return if found
return it->second;
}
// emplace new element
auto result = map.emplace(scale, this->createFontData(type, scale));
assert(result.second);
return result.first->second;
}
Fonts::FontData Fonts::createFontData(FontStyle type, float scale)
{
// check if it's a chat (scale the setting)
if (type >= FontStyle::ChatStart && type <= FontStyle::ChatEnd)
{
static std::unordered_map<FontStyle, ChatFontData> sizeScale{
{FontStyle::ChatSmall, {0.6f, false, QFont::Normal}},
{FontStyle::ChatMediumSmall, {0.8f, false, QFont::Normal}},
{FontStyle::ChatMedium, {1, false, QFont::Normal}},
{FontStyle::ChatMediumBold,
{1, false, QFont::Weight(getBoldness())}},
{FontStyle::ChatMediumItalic, {1, true, QFont::Normal}},
{FontStyle::ChatLarge, {1.2f, false, QFont::Normal}},
{FontStyle::ChatVeryLarge, {1.4f, false, QFont::Normal}},
};
sizeScale[FontStyle::ChatMediumBold] = {1, false,
QFont::Weight(getBoldness())};
auto data = sizeScale[type];
return FontData(
QFont(this->chatFontFamily.getValue(),
int(this->chatFontSize.getValue() * data.scale * scale),
data.weight, data.italic));
}
// normal Ui font (use pt size)
{
#ifdef Q_OS_MAC
constexpr float multiplier = 0.8f;
#else
constexpr float multiplier = 1.f;
#endif
static std::unordered_map<FontStyle, UiFontData> defaultSize{
{FontStyle::Tiny, {8, "Monospace", false, QFont::Normal}},
{FontStyle::UiMedium,
{int(9 * multiplier), DEFAULT_FONT_FAMILY, false, QFont::Normal}},
{FontStyle::UiTabs,
{int(9 * multiplier), DEFAULT_FONT_FAMILY, false, QFont::Normal}},
};
UiFontData &data = defaultSize[type];
QFont font(data.name, int(data.size * scale), data.weight, data.italic);
return FontData(font);
}
}
Fonts *getFonts()
{
return Fonts::instance;
}
} // namespace AB_NAMESPACE

View file

@ -0,0 +1,92 @@
#pragma once
#include "common/ChatterinoSetting.hpp"
#include "common/Singleton.hpp"
#include <QFont>
#include <QFontDatabase>
#include <QFontMetrics>
#include <boost/noncopyable.hpp>
#include <pajlada/signals/signal.hpp>
#include <array>
#include <unordered_map>
namespace AB_NAMESPACE {
class Settings;
class Paths;
enum class FontStyle : uint8_t {
Tiny,
ChatSmall,
ChatMediumSmall,
ChatMedium,
ChatMediumBold,
ChatMediumItalic,
ChatLarge,
ChatVeryLarge,
UiMedium,
UiTabs,
// don't remove this value
EndType,
// make sure to update these values accordingly!
ChatStart = ChatSmall,
ChatEnd = ChatVeryLarge,
};
class Fonts final : public Singleton
{
public:
Fonts();
virtual void initialize(Settings &settings, Paths &paths) override;
// font data gets set in createFontData(...)
QFont getFont(FontStyle type, float scale);
QFontMetrics getFontMetrics(FontStyle type, float scale);
QStringSetting chatFontFamily;
IntSetting chatFontSize;
pajlada::Signals::NoArgSignal fontChanged;
static Fonts *instance;
private:
struct FontData {
FontData(const QFont &_font)
: font(_font)
, metrics(_font)
{
}
const QFont font;
const QFontMetrics metrics;
};
struct ChatFontData {
float scale;
bool italic;
QFont::Weight weight;
};
struct UiFontData {
float size;
const char *name;
bool italic;
QFont::Weight weight;
};
FontData &getOrCreateFontData(FontStyle type, float scale);
FontData createFontData(FontStyle type, float scale);
std::vector<std::unordered_map<float, FontData>> fontsByType_;
};
Fonts *getFonts();
} // namespace AB_NAMESPACE

View file

@ -0,0 +1,13 @@
#pragma once
namespace AB_NAMESPACE {
// http://en.cppreference.com/w/cpp/algorithm/clamp
template <class T>
constexpr const T &clamp(const T &v, const T &lo, const T &hi)
{
return assert(!(hi < lo)), (v < lo) ? lo : (hi < v) ? hi : v;
}
} // namespace AB_NAMESPACE

View file

@ -0,0 +1,14 @@
#pragma once
#include <QDir>
#include <QString>
namespace chatterino {
// https://stackoverflow.com/a/13014491
inline QString combinePath(const QString &a, const QString &b)
{
return QDir::cleanPath(a + QDir::separator() + b);
}
} // namespace chatterino

View file

@ -0,0 +1,20 @@
#pragma once
#include <QPointF>
#include <cmath>
namespace chatterino {
inline float distanceBetweenPoints(const QPointF &p1, const QPointF &p2)
{
QPointF tmp = p1 - p2;
float distance = 0.f;
distance += tmp.x() * tmp.x();
distance += tmp.y() * tmp.y();
return sqrt(distance);
}
} // namespace chatterino

View file

@ -0,0 +1,17 @@
#include "FunctionEventFilter.hpp"
namespace AB_NAMESPACE {
FunctionEventFilter::FunctionEventFilter(
QObject *parent, std::function<bool(QObject *, QEvent *)> function)
: QObject(parent)
, function_(std::move(function))
{
}
bool FunctionEventFilter::eventFilter(QObject *watched, QEvent *event)
{
return this->function_(watched, event);
}
} // namespace AB_NAMESPACE

View file

@ -0,0 +1,24 @@
#pragma once
#include <QEvent>
#include <QObject>
#include <functional>
namespace AB_NAMESPACE {
class FunctionEventFilter : public QObject
{
Q_OBJECT
public:
FunctionEventFilter(QObject *parent,
std::function<bool(QObject *, QEvent *)> function);
protected:
bool eventFilter(QObject *watched, QEvent *event) override;
private:
std::function<bool(QObject *, QEvent *)> function_;
};
} // namespace AB_NAMESPACE

View file

@ -0,0 +1,33 @@
#include "FuzzyConvert.hpp"
#include <QRegularExpression>
namespace chatterino {
int fuzzyToInt(const QString &str, int default_)
{
static auto intFinder = QRegularExpression("[0-9]+");
auto match = intFinder.match(str);
if (match.hasMatch())
{
return match.captured().toInt();
}
return default_;
}
float fuzzyToFloat(const QString &str, float default_)
{
static auto floatFinder = QRegularExpression("[0-9]+(\\.[0-9]+)?");
auto match = floatFinder.match(str);
if (match.hasMatch())
{
return match.captured().toFloat();
}
return default_;
}
} // namespace chatterino

View file

@ -0,0 +1,10 @@
#pragma once
#include <QString>
namespace chatterino {
int fuzzyToInt(const QString &str, int default_);
float fuzzyToFloat(const QString &str, float default_);
} // namespace chatterino

View file

@ -0,0 +1,56 @@
#pragma once
#include <fmt/format.h>
#include <QUuid>
namespace AB_NAMESPACE {
template <typename... Args>
auto fS(Args &&... args)
{
return fmt::format(std::forward<Args>(args)...);
}
static QString CreateUUID()
{
auto uuid = QUuid::createUuid();
return uuid.toString();
}
static QString createLink(const QString &url, bool file = false)
{
return QString("<a href=\"") + (file ? "file:///" : "") + url + "\">" +
url + "</a>";
}
static QString createNamedLink(const QString &url, const QString &name,
bool file = false)
{
return QString("<a href=\"") + (file ? "file:///" : "") + url + "\">" +
name + "</a>";
}
static QString shortenString(const QString &str, unsigned maxWidth = 50)
{
auto shortened = QString(str);
if (str.size() > int(maxWidth))
{
shortened.resize(int(maxWidth));
shortened += "...";
}
return shortened;
}
} // namespace AB_NAMESPACE
namespace fmt {
// format_arg for QString
inline void format_arg(BasicFormatter<char> &f, const char *&, const QString &v)
{
f.writer().write("{}", v.toStdString());
}
} // namespace fmt

View file

@ -0,0 +1,42 @@
#pragma once
#include <QLayout>
#include <QWidget>
#include <boost/variant.hpp>
namespace chatterino {
using LayoutItem = boost::variant<QWidget *, QLayout *>;
template <typename T>
T *makeLayout(std::initializer_list<LayoutItem> items)
{
auto t = new T;
for (auto &item : items)
{
switch (item.which())
{
case 0:
t->addItem(new QWidgetItem(boost::get<QWidget *>(item)));
break;
case 1:
t->addItem(boost::get<QLayout *>(item));
break;
}
}
return t;
}
template <typename T, typename With>
T *makeWidget(With with)
{
auto t = new T;
with(t);
return t;
}
} // namespace chatterino

View file

@ -0,0 +1,59 @@
#pragma once
#include <QCoreApplication>
#include <QRunnable>
#include <QThreadPool>
#include <functional>
#define async_exec(a) \
QThreadPool::globalInstance()->start(new LambdaRunnable(a));
namespace AB_NAMESPACE {
class LambdaRunnable : public QRunnable
{
public:
LambdaRunnable(std::function<void()> action)
{
this->action_ = action;
}
void run()
{
this->action_();
}
private:
std::function<void()> action_;
};
// Taken from
// https://stackoverflow.com/questions/21646467/how-to-execute-a-functor-or-a-lambda-in-a-given-thread-in-qt-gcd-style
// Qt 5/4 - preferred, has least allocations
template <typename F>
static void postToThread(F &&fun, QObject *obj = qApp)
{
struct Event : public QEvent {
using Fun = typename std::decay<F>::type;
Fun fun;
Event(Fun &&fun)
: QEvent(QEvent::None)
, fun(std::move(fun))
{
}
Event(const Fun &fun)
: QEvent(QEvent::None)
, fun(fun)
{
}
~Event() override
{
fun();
}
};
QCoreApplication::postEvent(obj, new Event(std::forward<F>(fun)));
}
} // namespace AB_NAMESPACE

View file

@ -0,0 +1,45 @@
#pragma once
#include <QString>
#include <pajlada/serialize.hpp>
namespace pajlada {
template <>
struct Serialize<QString> {
static rapidjson::Value get(const QString &value,
rapidjson::Document::AllocatorType &a)
{
return rapidjson::Value(value.toUtf8(), a);
}
};
template <>
struct Deserialize<QString> {
static QString get(const rapidjson::Value &value, bool *error = nullptr)
{
if (!value.IsString())
{
PAJLADA_REPORT_ERROR(error)
return QString{};
}
try
{
return QString::fromUtf8(value.GetString(),
value.GetStringLength());
}
catch (const std::exception &)
{
// int x = 5;
}
catch (...)
{
// int y = 5;
}
return QString{};
}
};
} // namespace pajlada

View file

@ -0,0 +1,24 @@
#pragma once
#include <QShortcut>
#include <QWidget>
namespace AB_NAMESPACE {
template <typename WidgetType, typename Func>
inline void createShortcut(WidgetType *w, const char *key, Func func)
{
auto s = new QShortcut(QKeySequence(key), w);
s->setContext(Qt::WidgetWithChildrenShortcut);
QObject::connect(s, &QShortcut::activated, w, func);
}
template <typename WidgetType, typename Func>
inline void createWindowShortcut(WidgetType *w, const char *key, Func func)
{
auto s = new QShortcut(QKeySequence(key), w);
s->setContext(Qt::WindowShortcut);
QObject::connect(s, &QShortcut::activated, w, func);
}
} // namespace AB_NAMESPACE

View file

@ -0,0 +1,86 @@
#include "WindowsHelper.hpp"
#include <QSettings>
#ifdef USEWINSDK
namespace AB_NAMESPACE {
typedef enum MONITOR_DPI_TYPE {
MDT_EFFECTIVE_DPI = 0,
MDT_ANGULAR_DPI = 1,
MDT_RAW_DPI = 2,
MDT_DEFAULT = MDT_EFFECTIVE_DPI
} MONITOR_DPI_TYPE;
typedef HRESULT(CALLBACK *GetDpiForMonitor_)(HMONITOR, MONITOR_DPI_TYPE, UINT *,
UINT *);
boost::optional<UINT> getWindowDpi(HWND hwnd)
{
static HINSTANCE shcore = LoadLibrary(L"Shcore.dll");
if (shcore != nullptr)
{
if (auto getDpiForMonitor =
GetDpiForMonitor_(GetProcAddress(shcore, "GetDpiForMonitor")))
{
HMONITOR monitor =
MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
UINT xScale, yScale;
getDpiForMonitor(monitor, MDT_DEFAULT, &xScale, &yScale);
return xScale;
}
}
return boost::none;
}
typedef HRESULT(CALLBACK *OleFlushClipboard_)();
void flushClipboard()
{
static HINSTANCE ole32 = LoadLibrary(L"Ole32.dll");
if (ole32 != nullptr)
{
if (auto oleFlushClipboard =
OleFlushClipboard_(GetProcAddress(ole32, "OleFlushClipboard")))
{
oleFlushClipboard();
}
}
}
constexpr const char *runKey =
"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Run";
bool isRegisteredForStartup()
{
QSettings settings(runKey, QSettings::NativeFormat);
return !settings.value("Chatterino").toString().isEmpty();
}
void setRegisteredForStartup(bool isRegistered)
{
QSettings settings(runKey, QSettings::NativeFormat);
if (isRegistered)
{
auto exePath = QFileInfo(QCoreApplication::applicationFilePath())
.absoluteFilePath()
.replace('/', '\\');
settings.setValue("Chatterino", "\"" + exePath + "\" --autorun");
}
else
{
settings.remove("Chatterino");
}
}
} // namespace AB_NAMESPACE
#endif

View file

@ -0,0 +1,18 @@
#pragma once
#ifdef USEWINSDK
# include <Windows.h>
# include <boost/optional.hpp>
namespace AB_NAMESPACE {
boost::optional<UINT> getWindowDpi(HWND hwnd);
void flushClipboard();
bool isRegisteredForStartup();
void setRegisteredForStartup(bool isRegistered);
} // namespace AB_NAMESPACE
#endif

View file

@ -0,0 +1,164 @@
#include "widgets/BaseWidget.hpp"
#include "BaseSettings.hpp"
#include "BaseTheme.hpp"
#include "debug/Log.hpp"
#include "widgets/BaseWindow.hpp"
#include <QChildEvent>
#include <QDebug>
#include <QIcon>
#include <QLayout>
#include <QtGlobal>
namespace AB_NAMESPACE {
BaseWidget::BaseWidget(QWidget *parent, Qt::WindowFlags f)
: QWidget(parent, f)
{
// REMOVED
this->theme = getTheme();
this->signalHolder_.managedConnect(this->theme->updated, [this]() {
this->themeChangedEvent();
this->update();
});
}
float BaseWidget::scale() const
{
if (this->overrideScale_)
{
return this->overrideScale_.get();
}
else if (auto baseWidget = dynamic_cast<BaseWidget *>(this->window()))
{
return baseWidget->scale_;
}
else
{
return 1.f;
}
}
void BaseWidget::setScale(float value)
{
// update scale value
this->scale_ = value;
this->scaleChangedEvent(this->scale());
this->scaleChanged.invoke(this->scale());
this->setScaleIndependantSize(this->scaleIndependantSize());
}
void BaseWidget::setOverrideScale(boost::optional<float> value)
{
this->overrideScale_ = value;
this->setScale(this->scale());
}
boost::optional<float> BaseWidget::overrideScale() const
{
return this->overrideScale_;
}
QSize BaseWidget::scaleIndependantSize() const
{
return this->scaleIndependantSize_;
}
int BaseWidget::scaleIndependantWidth() const
{
return this->scaleIndependantSize_.width();
}
int BaseWidget::scaleIndependantHeight() const
{
return this->scaleIndependantSize_.height();
}
void BaseWidget::setScaleIndependantSize(int width, int height)
{
this->setScaleIndependantSize(QSize(width, height));
}
void BaseWidget::setScaleIndependantSize(QSize size)
{
this->scaleIndependantSize_ = size;
if (size.width() > 0)
{
this->setFixedWidth(int(size.width() * this->scale()));
}
if (size.height() > 0)
{
this->setFixedHeight(int(size.height() * this->scale()));
}
}
void BaseWidget::setScaleIndependantWidth(int value)
{
this->setScaleIndependantSize(
QSize(value, this->scaleIndependantSize_.height()));
}
void BaseWidget::setScaleIndependantHeight(int value)
{
this->setScaleIndependantSize(
QSize(this->scaleIndependantSize_.width(), value));
}
float BaseWidget::qtFontScale() const
{
if (auto window = dynamic_cast<BaseWindow *>(this->window()))
{
return this->scale() / window->nativeScale_;
}
else
{
return this->scale();
}
}
void BaseWidget::childEvent(QChildEvent *event)
{
if (event->added())
{
// add element if it's a basewidget
if (auto widget = dynamic_cast<BaseWidget *>(event->child()))
{
this->widgets_.push_back(widget);
}
}
else if (event->removed())
{
// find element to be removed
auto it = std::find_if(this->widgets_.begin(), this->widgets_.end(),
[&](auto &&x) { return x == event->child(); });
// remove if found
if (it != this->widgets_.end())
{
this->widgets_.erase(it);
}
}
}
void BaseWidget::showEvent(QShowEvent *)
{
this->setScale(this->scale());
this->themeChangedEvent();
}
void BaseWidget::scaleChangedEvent(float newDpi)
{
}
void BaseWidget::themeChangedEvent()
{
// Do any color scheme updates here
}
} // namespace AB_NAMESPACE

View file

@ -0,0 +1,59 @@
#pragma once
#include <QWidget>
#include <boost/optional.hpp>
#include <pajlada/signals/signal.hpp>
#include <pajlada/signals/signalholder.hpp>
namespace AB_NAMESPACE {
class Theme;
class BaseWindow;
class BaseWidget : public QWidget
{
Q_OBJECT
public:
explicit BaseWidget(QWidget *parent, Qt::WindowFlags f = Qt::WindowFlags());
virtual float scale() const;
pajlada::Signals::Signal<float> scaleChanged;
boost::optional<float> overrideScale() const;
void setOverrideScale(boost::optional<float>);
QSize scaleIndependantSize() const;
int scaleIndependantWidth() const;
int scaleIndependantHeight() const;
void setScaleIndependantSize(int width, int height);
void setScaleIndependantSize(QSize);
void setScaleIndependantWidth(int value);
void setScaleIndependantHeight(int value);
float qtFontScale() const;
protected:
virtual void childEvent(QChildEvent *) override;
virtual void showEvent(QShowEvent *) override;
virtual void scaleChangedEvent(float newScale);
virtual void themeChangedEvent();
void setScale(float value);
Theme *theme;
private:
float scale_{1.f};
boost::optional<float> overrideScale_;
QSize scaleIndependantSize_;
std::vector<BaseWidget *> widgets_;
pajlada::Signals::SignalHolder signalHolder_;
friend class BaseWindow;
};
} // namespace AB_NAMESPACE

View file

@ -0,0 +1,917 @@
#include "BaseWindow.hpp"
#include "BaseSettings.hpp"
#include "BaseTheme.hpp"
#include "boost/algorithm/algorithm.hpp"
#include "debug/Log.hpp"
#include "util/PostToThread.hpp"
#include "util/Shortcut.hpp"
#include "util/WindowsHelper.hpp"
#include "widgets/Label.hpp"
#include "widgets/TooltipWidget.hpp"
#include "widgets/helper/EffectLabel.hpp"
#include <QApplication>
#include <QDebug>
#include <QDesktopWidget>
#include <QFont>
#include <QIcon>
#include <functional>
#ifdef CHATTERINO
# include "Application.hpp"
# include "singletons/WindowManager.hpp"
#endif
#ifdef USEWINSDK
# include <ObjIdl.h>
# include <VersionHelpers.h>
# include <Windows.h>
# include <dwmapi.h>
# include <gdiplus.h>
# include <windowsx.h>
//#include <ShellScalingApi.h>
# pragma comment(lib, "Dwmapi.lib")
# include <QHBoxLayout>
# include <QVBoxLayout>
# define WM_DPICHANGED 0x02E0
#endif
#include "widgets/helper/TitlebarButton.hpp"
namespace AB_NAMESPACE {
BaseWindow::BaseWindow(QWidget *parent, Flags _flags)
: BaseWidget(parent,
Qt::Window | ((_flags & TopMost) ? Qt::WindowStaysOnTopHint
: Qt::WindowFlags()))
, enableCustomFrame_(_flags & EnableCustomFrame)
, frameless_(_flags & Frameless)
, flags_(_flags)
{
if (this->frameless_)
{
this->enableCustomFrame_ = false;
this->setWindowFlag(Qt::FramelessWindowHint);
}
this->init();
getSettings()->uiScale.connect(
[this]() { postToThread([this] { this->updateScale(); }); },
this->connections_);
this->updateScale();
createWindowShortcut(this, "CTRL+0",
[] { getSettings()->uiScale.setValue(1); });
// QTimer::this->scaleChangedEvent(this->getScale());
this->resize(300, 300);
}
float BaseWindow::scale() const
{
return this->overrideScale().value_or(this->scale_);
}
float BaseWindow::qtFontScale() const
{
return this->scale() / this->nativeScale_;
}
BaseWindow::Flags BaseWindow::getFlags()
{
return this->flags_;
}
void BaseWindow::init()
{
this->setWindowIcon(QIcon(":/images/icon.png"));
#ifdef USEWINSDK
if (this->hasCustomWindowFrame())
{
// CUSTOM WINDOW FRAME
QVBoxLayout *layout = new QVBoxLayout();
this->ui_.windowLayout = layout;
layout->setContentsMargins(0, 1, 0, 0);
layout->setSpacing(0);
this->setLayout(layout);
{
if (!this->frameless_)
{
QHBoxLayout *buttonLayout = this->ui_.titlebarBox =
new QHBoxLayout();
buttonLayout->setMargin(0);
layout->addLayout(buttonLayout);
// title
Label *title = new Label;
QObject::connect(
this, &QWidget::windowTitleChanged,
[title](const QString &text) { title->setText(text); });
QSizePolicy policy(QSizePolicy::Ignored,
QSizePolicy::Preferred);
policy.setHorizontalStretch(1);
title->setSizePolicy(policy);
buttonLayout->addWidget(title);
this->ui_.titleLabel = title;
// buttons
TitleBarButton *_minButton = new TitleBarButton;
_minButton->setButtonStyle(TitleBarButtonStyle::Minimize);
TitleBarButton *_maxButton = new TitleBarButton;
_maxButton->setButtonStyle(TitleBarButtonStyle::Maximize);
TitleBarButton *_exitButton = new TitleBarButton;
_exitButton->setButtonStyle(TitleBarButtonStyle::Close);
QObject::connect(_minButton, &TitleBarButton::leftClicked, this,
[this] {
this->setWindowState(Qt::WindowMinimized |
this->windowState());
});
QObject::connect(_maxButton, &TitleBarButton::leftClicked, this,
[this, _maxButton] {
this->setWindowState(
_maxButton->getButtonStyle() !=
TitleBarButtonStyle::Maximize
? Qt::WindowActive
: Qt::WindowMaximized);
});
QObject::connect(_exitButton, &TitleBarButton::leftClicked,
this, [this] { this->close(); });
this->ui_.minButton = _minButton;
this->ui_.maxButton = _maxButton;
this->ui_.exitButton = _exitButton;
this->ui_.buttons.push_back(_minButton);
this->ui_.buttons.push_back(_maxButton);
this->ui_.buttons.push_back(_exitButton);
// buttonLayout->addStretch(1);
buttonLayout->addWidget(_minButton);
buttonLayout->addWidget(_maxButton);
buttonLayout->addWidget(_exitButton);
buttonLayout->setSpacing(0);
}
}
this->ui_.layoutBase = new BaseWidget(this);
layout->addWidget(this->ui_.layoutBase);
}
// DPI
// auto dpi = getWindowDpi(this->winId());
// if (dpi) {
// this->scale = dpi.value() / 96.f;
// }
#endif
#ifdef USEWINSDK
// fourtf: don't ask me why we need to delay this
if (!(this->flags_ & Flags::TopMost))
{
QTimer::singleShot(1, this, [this] {
getSettings()->windowTopMost.connect(
[this](bool topMost, auto) {
::SetWindowPos(HWND(this->winId()),
topMost ? HWND_TOPMOST : HWND_NOTOPMOST, 0,
0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
},
this->managedConnections_);
});
}
#else
// if (getSettings()->windowTopMost.getValue()) {
// this->setWindowFlag(Qt::WindowStaysOnTopHint);
// }
#endif
}
void BaseWindow::setStayInScreenRect(bool value)
{
this->stayInScreenRect_ = value;
this->moveIntoDesktopRect(this);
}
bool BaseWindow::getStayInScreenRect() const
{
return this->stayInScreenRect_;
}
void BaseWindow::setActionOnFocusLoss(ActionOnFocusLoss value)
{
this->actionOnFocusLoss_ = value;
}
BaseWindow::ActionOnFocusLoss BaseWindow::getActionOnFocusLoss() const
{
return this->actionOnFocusLoss_;
}
QWidget *BaseWindow::getLayoutContainer()
{
if (this->hasCustomWindowFrame())
{
return this->ui_.layoutBase;
}
else
{
return this;
}
}
bool BaseWindow::hasCustomWindowFrame()
{
#ifdef USEWINSDK
static bool isWin8 = IsWindows8OrGreater();
return isWin8 && this->enableCustomFrame_;
#else
return false;
#endif
}
void BaseWindow::themeChangedEvent()
{
if (this->hasCustomWindowFrame())
{
QPalette palette;
palette.setColor(QPalette::Background, QColor(0, 0, 0, 0));
palette.setColor(QPalette::Foreground, this->theme->window.text);
this->setPalette(palette);
if (this->ui_.titleLabel)
{
QPalette palette_title;
palette_title.setColor(
QPalette::Foreground,
this->theme->isLightTheme() ? "#333" : "#ccc");
this->ui_.titleLabel->setPalette(palette_title);
}
for (Button *button : this->ui_.buttons)
{
button->setMouseEffectColor(this->theme->window.text);
}
}
else
{
QPalette palette;
palette.setColor(QPalette::Background, this->theme->window.background);
palette.setColor(QPalette::Foreground, this->theme->window.text);
this->setPalette(palette);
}
}
bool BaseWindow::event(QEvent *event)
{
if (event->type() ==
QEvent::WindowDeactivate /*|| event->type() == QEvent::FocusOut*/)
{
this->onFocusLost();
}
return QWidget::event(event);
}
void BaseWindow::wheelEvent(QWheelEvent *event)
{
if (event->orientation() != Qt::Vertical)
{
return;
}
if (event->modifiers() & Qt::ControlModifier)
{
if (event->delta() > 0)
{
getSettings()->setClampedUiScale(
getSettings()->getClampedUiScale() + 0.1);
}
else
{
getSettings()->setClampedUiScale(
getSettings()->getClampedUiScale() - 0.1);
}
}
}
void BaseWindow::onFocusLost()
{
switch (this->getActionOnFocusLoss())
{
case Delete:
{
this->deleteLater();
}
break;
case Close:
{
this->close();
}
break;
case Hide:
{
this->hide();
}
break;
default:;
}
}
void BaseWindow::mousePressEvent(QMouseEvent *event)
{
#ifndef Q_OS_WIN
if (this->flags_ & FramelessDraggable)
{
this->movingRelativePos = event->localPos();
if (auto widget =
this->childAt(event->localPos().x(), event->localPos().y()))
{
std::function<bool(QWidget *)> recursiveCheckMouseTracking;
recursiveCheckMouseTracking = [&](QWidget *widget) {
if (widget == nullptr)
{
return false;
}
if (widget->hasMouseTracking())
{
return true;
}
return recursiveCheckMouseTracking(widget->parentWidget());
};
if (!recursiveCheckMouseTracking(widget))
{
log("Start moving");
this->moving = true;
}
}
}
#endif
BaseWidget::mousePressEvent(event);
}
void BaseWindow::mouseReleaseEvent(QMouseEvent *event)
{
#ifndef Q_OS_WIN
if (this->flags_ & FramelessDraggable)
{
if (this->moving)
{
log("Stop moving");
this->moving = false;
}
}
#endif
BaseWidget::mouseReleaseEvent(event);
}
void BaseWindow::mouseMoveEvent(QMouseEvent *event)
{
#ifndef Q_OS_WIN
if (this->flags_ & FramelessDraggable)
{
if (this->moving)
{
const auto &newPos = event->screenPos() - this->movingRelativePos;
this->move(newPos.x(), newPos.y());
}
}
#endif
BaseWidget::mouseMoveEvent(event);
}
TitleBarButton *BaseWindow::addTitleBarButton(const TitleBarButtonStyle &style,
std::function<void()> onClicked)
{
TitleBarButton *button = new TitleBarButton;
button->setScaleIndependantSize(30, 30);
this->ui_.buttons.push_back(button);
this->ui_.titlebarBox->insertWidget(1, button);
button->setButtonStyle(style);
QObject::connect(button, &TitleBarButton::leftClicked, this,
[onClicked] { onClicked(); });
return button;
}
EffectLabel *BaseWindow::addTitleBarLabel(std::function<void()> onClicked)
{
EffectLabel *button = new EffectLabel;
button->setScaleIndependantHeight(30);
this->ui_.buttons.push_back(button);
this->ui_.titlebarBox->insertWidget(1, button);
QObject::connect(button, &EffectLabel::leftClicked, this,
[onClicked] { onClicked(); });
return button;
}
void BaseWindow::changeEvent(QEvent *)
{
if (this->isVisible())
{
TooltipWidget::getInstance()->hide();
}
#ifdef USEWINSDK
if (this->ui_.maxButton)
{
this->ui_.maxButton->setButtonStyle(
this->windowState() & Qt::WindowMaximized
? TitleBarButtonStyle::Unmaximize
: TitleBarButtonStyle::Maximize);
}
#endif
#ifndef Q_OS_WIN
this->update();
#endif
}
void BaseWindow::leaveEvent(QEvent *)
{
TooltipWidget::getInstance()->hide();
}
void BaseWindow::moveTo(QWidget *parent, QPoint point, bool offset)
{
if (offset)
{
point.rx() += 16;
point.ry() += 16;
}
this->move(point);
this->moveIntoDesktopRect(parent);
}
void BaseWindow::resizeEvent(QResizeEvent *)
{
// Queue up save because: Window resized
#ifdef CHATTERINO
getApp()->windows->queueSave();
#endif
this->moveIntoDesktopRect(this);
this->calcButtonsSizes();
}
void BaseWindow::moveEvent(QMoveEvent *event)
{
// Queue up save because: Window position changed
#ifdef CHATTERINO
getApp()->windows->queueSave();
#endif
BaseWidget::moveEvent(event);
}
void BaseWindow::closeEvent(QCloseEvent *)
{
this->closing.invoke();
}
void BaseWindow::moveIntoDesktopRect(QWidget *parent)
{
if (!this->stayInScreenRect_)
return;
// move the widget into the screen geometry if it's not already in there
QDesktopWidget *desktop = QApplication::desktop();
QRect s = desktop->availableGeometry(parent);
QPoint p = this->pos();
if (p.x() < s.left())
{
p.setX(s.left());
}
if (p.y() < s.top())
{
p.setY(s.top());
}
if (p.x() + this->width() > s.right())
{
p.setX(s.right() - this->width());
}
if (p.y() + this->height() > s.bottom())
{
p.setY(s.bottom() - this->height());
}
if (p != this->pos())
this->move(p);
}
bool BaseWindow::nativeEvent(const QByteArray &eventType, void *message,
long *result)
{
#ifdef USEWINSDK
# if (QT_VERSION == QT_VERSION_CHECK(5, 11, 1))
MSG *msg = *reinterpret_cast<MSG **>(message);
# else
MSG *msg = reinterpret_cast<MSG *>(message);
# endif
bool returnValue = false;
switch (msg->message)
{
case WM_DPICHANGED:
returnValue = handleDPICHANGED(msg);
break;
case WM_SHOWWINDOW:
returnValue = this->handleSHOWWINDOW(msg);
break;
case WM_NCCALCSIZE:
returnValue = this->handleNCCALCSIZE(msg, result);
break;
case WM_SIZE:
returnValue = this->handleSIZE(msg);
break;
case WM_NCHITTEST:
returnValue = this->handleNCHITTEST(msg, result);
break;
default:
return QWidget::nativeEvent(eventType, message, result);
}
QWidget::nativeEvent(eventType, message, result);
return returnValue;
#else
return QWidget::nativeEvent(eventType, message, result);
#endif
}
void BaseWindow::scaleChangedEvent(float scale)
{
#ifdef USEWINSDK
this->calcButtonsSizes();
#endif
this->setFont(getFonts()->getFont(FontStyle::UiTabs, this->qtFontScale()));
}
void BaseWindow::paintEvent(QPaintEvent *)
{
QPainter painter(this);
if (this->frameless_)
{
painter.setPen(QColor("#999"));
painter.drawRect(0, 0, this->width() - 1, this->height() - 1);
}
this->drawCustomWindowFrame(painter);
}
void BaseWindow::updateScale()
{
auto scale =
this->nativeScale_ * (this->flags_ & DisableCustomScaling
? 1
: getABSettings()->getClampedUiScale());
this->setScale(scale);
for (auto child : this->findChildren<BaseWidget *>())
{
child->setScale(scale);
}
}
void BaseWindow::calcButtonsSizes()
{
if (!this->shown_)
{
return;
}
if ((this->width() / this->scale()) < 300)
{
if (this->ui_.minButton)
this->ui_.minButton->setScaleIndependantSize(30, 30);
if (this->ui_.maxButton)
this->ui_.maxButton->setScaleIndependantSize(30, 30);
if (this->ui_.exitButton)
this->ui_.exitButton->setScaleIndependantSize(30, 30);
}
else
{
if (this->ui_.minButton)
this->ui_.minButton->setScaleIndependantSize(46, 30);
if (this->ui_.maxButton)
this->ui_.maxButton->setScaleIndependantSize(46, 30);
if (this->ui_.exitButton)
this->ui_.exitButton->setScaleIndependantSize(46, 30);
}
}
void BaseWindow::drawCustomWindowFrame(QPainter &painter)
{
#ifdef USEWINSDK
if (this->hasCustomWindowFrame())
{
QPainter painter(this);
QColor bg = this->overrideBackgroundColor_.value_or(
this->theme->window.background);
painter.fillRect(QRect(0, 1, this->width() - 0, this->height() - 0),
bg);
}
#endif
}
bool BaseWindow::handleDPICHANGED(MSG *msg)
{
#ifdef USEWINSDK
int dpi = HIWORD(msg->wParam);
float _scale = dpi / 96.f;
static bool firstResize = true;
if (!firstResize)
{
auto *prcNewWindow = reinterpret_cast<RECT *>(msg->lParam);
SetWindowPos(msg->hwnd, nullptr, prcNewWindow->left, prcNewWindow->top,
prcNewWindow->right - prcNewWindow->left,
prcNewWindow->bottom - prcNewWindow->top,
SWP_NOZORDER | SWP_NOACTIVATE);
}
firstResize = false;
this->nativeScale_ = _scale;
this->updateScale();
return true;
#else
return false;
#endif
}
bool BaseWindow::handleSHOWWINDOW(MSG *msg)
{
#ifdef USEWINSDK
if (auto dpi = getWindowDpi(msg->hwnd))
{
this->nativeScale_ = dpi.get() / 96.f;
this->updateScale();
}
if (!this->shown_ && this->isVisible() && this->hasCustomWindowFrame())
{
this->shown_ = true;
const MARGINS shadow = {8, 8, 8, 8};
DwmExtendFrameIntoClientArea(HWND(this->winId()), &shadow);
}
this->calcButtonsSizes();
return true;
#else
return false;
#endif
}
bool BaseWindow::handleNCCALCSIZE(MSG *msg, long *result)
{
#ifdef USEWINSDK
if (this->hasCustomWindowFrame())
{
// int cx = GetSystemMetrics(SM_CXSIZEFRAME);
// int cy = GetSystemMetrics(SM_CYSIZEFRAME);
if (msg->wParam == TRUE)
{
NCCALCSIZE_PARAMS *ncp =
(reinterpret_cast<NCCALCSIZE_PARAMS *>(msg->lParam));
ncp->lppos->flags |= SWP_NOREDRAW;
RECT *clientRect = &ncp->rgrc[0];
clientRect->left += 1;
clientRect->top += 0;
clientRect->right -= 1;
clientRect->bottom -= 1;
}
*result = 0;
return true;
}
return false;
#else
return false;
#endif
}
bool BaseWindow::handleSIZE(MSG *msg)
{
#ifdef USEWINSDK
if (this->ui_.windowLayout)
{
if (this->frameless_)
{
//
}
else if (this->hasCustomWindowFrame())
{
if (msg->wParam == SIZE_MAXIMIZED)
{
auto offset = int(this->scale() * 8);
this->ui_.windowLayout->setContentsMargins(offset, offset,
offset, offset);
}
else
{
this->ui_.windowLayout->setContentsMargins(0, 1, 0, 0);
}
}
}
return false;
#else
return false;
#endif
}
bool BaseWindow::handleNCHITTEST(MSG *msg, long *result)
{
#ifdef USEWINSDK
const LONG border_width = 8; // in pixels
RECT winrect;
GetWindowRect(HWND(winId()), &winrect);
long x = GET_X_LPARAM(msg->lParam);
long y = GET_Y_LPARAM(msg->lParam);
QPoint point(x - winrect.left, y - winrect.top);
if (this->hasCustomWindowFrame())
{
*result = 0;
bool resizeWidth = minimumWidth() != maximumWidth();
bool resizeHeight = minimumHeight() != maximumHeight();
if (resizeWidth)
{
// left border
if (x < winrect.left + border_width)
{
*result = HTLEFT;
}
// right border
if (x >= winrect.right - border_width)
{
*result = HTRIGHT;
}
}
if (resizeHeight)
{
// bottom border
if (y >= winrect.bottom - border_width)
{
*result = HTBOTTOM;
}
// top border
if (y < winrect.top + border_width)
{
*result = HTTOP;
}
}
if (resizeWidth && resizeHeight)
{
// bottom left corner
if (x >= winrect.left && x < winrect.left + border_width &&
y < winrect.bottom && y >= winrect.bottom - border_width)
{
*result = HTBOTTOMLEFT;
}
// bottom right corner
if (x < winrect.right && x >= winrect.right - border_width &&
y < winrect.bottom && y >= winrect.bottom - border_width)
{
*result = HTBOTTOMRIGHT;
}
// top left corner
if (x >= winrect.left && x < winrect.left + border_width &&
y >= winrect.top && y < winrect.top + border_width)
{
*result = HTTOPLEFT;
}
// top right corner
if (x < winrect.right && x >= winrect.right - border_width &&
y >= winrect.top && y < winrect.top + border_width)
{
*result = HTTOPRIGHT;
}
}
if (*result == 0)
{
bool client = false;
for (QWidget *widget : this->ui_.buttons)
{
if (widget->geometry().contains(point))
{
client = true;
}
}
if (this->ui_.layoutBase->geometry().contains(point))
{
client = true;
}
if (client)
{
*result = HTCLIENT;
}
else
{
*result = HTCAPTION;
}
}
return true;
}
else if (this->flags_ & FramelessDraggable)
{
*result = 0;
bool client = false;
if (auto widget = this->childAt(point))
{
std::function<bool(QWidget *)> recursiveCheckMouseTracking;
recursiveCheckMouseTracking = [&](QWidget *widget) {
if (widget == nullptr)
{
return false;
}
if (widget->hasMouseTracking())
{
return true;
}
return recursiveCheckMouseTracking(widget->parentWidget());
};
if (recursiveCheckMouseTracking(widget))
{
client = true;
}
}
if (client)
{
*result = HTCLIENT;
}
else
{
*result = HTCAPTION;
}
return true;
}
return false;
#else
return false;
#endif
}
} // namespace AB_NAMESPACE

View file

@ -0,0 +1,123 @@
#pragma once
#include "widgets/BaseWidget.hpp"
#include <functional>
#include <pajlada/signals/signalholder.hpp>
class QHBoxLayout;
struct tagMSG;
typedef struct tagMSG MSG;
namespace AB_NAMESPACE {
class Button;
class EffectLabel;
class TitleBarButton;
enum class TitleBarButtonStyle;
class BaseWindow : public BaseWidget
{
Q_OBJECT
public:
enum Flags {
None = 0,
EnableCustomFrame = 1,
Frameless = 2,
TopMost = 4,
DisableCustomScaling = 8,
FramelessDraggable = 16,
};
enum ActionOnFocusLoss { Nothing, Delete, Close, Hide };
explicit BaseWindow(QWidget *parent = nullptr, Flags flags_ = None);
QWidget *getLayoutContainer();
bool hasCustomWindowFrame();
TitleBarButton *addTitleBarButton(const TitleBarButtonStyle &style,
std::function<void()> onClicked);
EffectLabel *addTitleBarLabel(std::function<void()> onClicked);
void setStayInScreenRect(bool value);
bool getStayInScreenRect() const;
void setActionOnFocusLoss(ActionOnFocusLoss value);
ActionOnFocusLoss getActionOnFocusLoss() const;
void moveTo(QWidget *widget, QPoint point, bool offset = true);
virtual float scale() const override;
float qtFontScale() const;
Flags getFlags();
pajlada::Signals::NoArgSignal closing;
protected:
virtual bool nativeEvent(const QByteArray &eventType, void *message,
long *result) override;
virtual void scaleChangedEvent(float) override;
virtual void paintEvent(QPaintEvent *) override;
virtual void changeEvent(QEvent *) override;
virtual void leaveEvent(QEvent *) override;
virtual void resizeEvent(QResizeEvent *) override;
virtual void moveEvent(QMoveEvent *) override;
virtual void closeEvent(QCloseEvent *) override;
virtual void themeChangedEvent() override;
virtual bool event(QEvent *event) override;
virtual void wheelEvent(QWheelEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
QPointF movingRelativePos;
bool moving{};
void updateScale();
boost::optional<QColor> overrideBackgroundColor_;
private:
void init();
void moveIntoDesktopRect(QWidget *parent);
void calcButtonsSizes();
void drawCustomWindowFrame(QPainter &painter);
void onFocusLost();
bool handleDPICHANGED(MSG *msg);
bool handleSHOWWINDOW(MSG *msg);
bool handleNCCALCSIZE(MSG *msg, long *result);
bool handleSIZE(MSG *msg);
bool handleNCHITTEST(MSG *msg, long *result);
bool enableCustomFrame_;
ActionOnFocusLoss actionOnFocusLoss_ = Nothing;
bool frameless_;
bool stayInScreenRect_ = false;
bool shown_ = false;
Flags flags_;
float nativeScale_ = 1;
struct {
QLayout *windowLayout = nullptr;
QHBoxLayout *titlebarBox = nullptr;
QWidget *titleLabel = nullptr;
TitleBarButton *minButton = nullptr;
TitleBarButton *maxButton = nullptr;
TitleBarButton *exitButton = nullptr;
QWidget *layoutBase = nullptr;
std::vector<Button *> buttons;
} ui_;
pajlada::Signals::SignalHolder connections_;
std::vector<pajlada::Signals::ScopedConnection> managedConnections_;
friend class BaseWidget;
};
} // namespace AB_NAMESPACE

View file

@ -0,0 +1,130 @@
#include "Label.hpp"
#include <QPainter>
namespace AB_NAMESPACE {
Label::Label(QString text, FontStyle style)
: Label(nullptr, text, style)
{
}
Label::Label(BaseWidget *parent, QString text, FontStyle style)
: BaseWidget(parent)
, text_(text)
, fontStyle_(style)
{
this->connections_.managedConnect(getFonts()->fontChanged,
[this] { this->updateSize(); });
}
const QString &Label::getText() const
{
return this->text_;
}
void Label::setText(const QString &text)
{
if (this->text_ != text)
{
this->text_ = text;
this->updateSize();
this->update();
}
}
FontStyle Label::getFontStyle() const
{
return this->fontStyle_;
}
bool Label::getCentered() const
{
return this->centered_;
}
void Label::setCentered(bool centered)
{
this->centered_ = centered;
this->updateSize();
}
bool Label::getHasOffset() const
{
return this->hasOffset_;
}
void Label::setHasOffset(bool hasOffset)
{
this->hasOffset_ = hasOffset;
this->updateSize();
}
void Label::setFontStyle(FontStyle style)
{
this->fontStyle_ = style;
this->updateSize();
}
void Label::scaleChangedEvent(float scale)
{
this->updateSize();
}
QSize Label::sizeHint() const
{
return this->preferedSize_;
}
QSize Label::minimumSizeHint() const
{
return this->preferedSize_;
}
void Label::paintEvent(QPaintEvent *)
{
QPainter painter(this);
QFontMetrics metrics = getFonts()->getFontMetrics(
this->getFontStyle(),
this->scale() * 96.f / this->logicalDpiX() * this->devicePixelRatioF());
painter.setFont(getFonts()->getFont(
this->getFontStyle(), this->scale() * 96.f / this->logicalDpiX() *
this->devicePixelRatioF()));
int offset = this->getOffset();
// draw text
QRect textRect(offset, 0, this->width() - offset - offset, this->height());
int width = metrics.width(this->text_);
Qt::Alignment alignment = !this->centered_ || width > textRect.width()
? Qt::AlignLeft | Qt::AlignVCenter
: Qt::AlignCenter;
QTextOption option(alignment);
option.setWrapMode(QTextOption::NoWrap);
painter.drawText(textRect, this->text_, option);
#if 0
painter.setPen(QColor(255, 0, 0));
painter.drawRect(0, 0, this->width() - 1, this->height() - 1);
#endif
}
void Label::updateSize()
{
QFontMetrics metrics =
getFonts()->getFontMetrics(this->fontStyle_, this->scale());
int width = metrics.width(this->text_) + (2 * this->getOffset());
int height = metrics.height();
this->preferedSize_ = QSize(width, height);
this->updateGeometry();
}
int Label::getOffset()
{
return this->hasOffset_ ? int(8 * this->scale()) : 0;
}
} // namespace AB_NAMESPACE

View file

@ -0,0 +1,50 @@
#pragma once
#include "singletons/Fonts.hpp"
#include "widgets/BaseWidget.hpp"
#include <pajlada/signals/signalholder.hpp>
namespace AB_NAMESPACE {
class Label : public BaseWidget
{
public:
explicit Label(QString text = QString(),
FontStyle style = FontStyle::UiMedium);
explicit Label(BaseWidget *parent, QString text = QString(),
FontStyle style = FontStyle::UiMedium);
const QString &getText() const;
void setText(const QString &text);
FontStyle getFontStyle() const;
void setFontStyle(FontStyle style);
bool getCentered() const;
void setCentered(bool centered);
bool getHasOffset() const;
void setHasOffset(bool hasOffset);
protected:
virtual void scaleChangedEvent(float scale_) override;
virtual void paintEvent(QPaintEvent *) override;
virtual QSize sizeHint() const override;
virtual QSize minimumSizeHint() const override;
private:
void updateSize();
int getOffset();
QString text_;
FontStyle fontStyle_;
QSize preferedSize_;
bool centered_ = false;
bool hasOffset_ = true;
pajlada::Signals::SignalHolder connections_;
};
} // namespace AB_NAMESPACE

View file

@ -0,0 +1,112 @@
#include "TooltipWidget.hpp"
#include "BaseTheme.hpp"
#include "singletons/Fonts.hpp"
#include <QDebug>
#include <QDesktopWidget>
#include <QStyle>
#include <QVBoxLayout>
#ifdef USEWINSDK
# include <Windows.h>
#endif
namespace AB_NAMESPACE {
TooltipWidget *TooltipWidget::getInstance()
{
static TooltipWidget *tooltipWidget = new TooltipWidget();
return tooltipWidget;
}
TooltipWidget::TooltipWidget(BaseWidget *parent)
: BaseWindow(parent, BaseWindow::TopMost)
, displayImage_(new QLabel())
, displayText_(new QLabel())
{
this->setStyleSheet("color: #fff; background: #000");
this->setWindowOpacity(0.8);
this->updateFont();
this->setStayInScreenRect(true);
this->setAttribute(Qt::WA_ShowWithoutActivating);
this->setWindowFlags(Qt::Tool | Qt::FramelessWindowHint |
Qt::X11BypassWindowManagerHint |
Qt::BypassWindowManagerHint);
displayImage_->hide();
displayImage_->setAlignment(Qt::AlignHCenter);
displayText_->setAlignment(Qt::AlignHCenter);
displayText_->setText("tooltip text");
auto layout = new QVBoxLayout();
layout->setContentsMargins(10, 5, 10, 5);
layout->addWidget(displayImage_);
layout->addWidget(displayText_);
this->setLayout(layout);
this->fontChangedConnection_ =
getFonts()->fontChanged.connect([this] { this->updateFont(); });
}
TooltipWidget::~TooltipWidget()
{
this->fontChangedConnection_.disconnect();
}
#ifdef USEWINSDK
void TooltipWidget::raise()
{
::SetWindowPos(HWND(this->winId()), HWND_TOPMOST, 0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
}
#endif
void TooltipWidget::themeChangedEvent()
{
this->setStyleSheet("color: #fff; background: #000");
}
void TooltipWidget::scaleChangedEvent(float)
{
this->updateFont();
}
void TooltipWidget::updateFont()
{
this->setFont(
getFonts()->getFont(FontStyle::ChatMediumSmall, this->scale()));
}
void TooltipWidget::setText(QString text)
{
this->displayText_->setText(text);
}
void TooltipWidget::setWordWrap(bool wrap)
{
this->displayText_->setWordWrap(wrap);
}
void TooltipWidget::clearImage()
{
this->displayImage_->hide();
}
void TooltipWidget::setImage(QPixmap image)
{
this->displayImage_->show();
this->displayImage_->setPixmap(image);
}
void TooltipWidget::changeEvent(QEvent *)
{
// clear parents event
}
void TooltipWidget::leaveEvent(QEvent *)
{
// clear parents event
}
} // namespace AB_NAMESPACE

View file

@ -0,0 +1,44 @@
#pragma once
#include "widgets/BaseWindow.hpp"
#include <QLabel>
#include <QWidget>
#include <pajlada/signals/signal.hpp>
namespace AB_NAMESPACE {
class TooltipWidget : public BaseWindow
{
Q_OBJECT
public:
static TooltipWidget *getInstance();
TooltipWidget(BaseWidget *parent = nullptr);
~TooltipWidget() override;
void setText(QString text);
void setWordWrap(bool wrap);
void clearImage();
void setImage(QPixmap image);
#ifdef USEWINSDK
void raise();
#endif
protected:
void changeEvent(QEvent *) override;
void leaveEvent(QEvent *) override;
void themeChangedEvent() override;
void scaleChangedEvent(float) override;
private:
void updateFont();
QLabel *displayImage_;
QLabel *displayText_;
pajlada::Signals::Connection fontChangedConnection_;
};
} // namespace AB_NAMESPACE

View file

@ -0,0 +1,338 @@
#include "Button.hpp"
#include <QApplication>
#include <QDebug>
#include <QDesktopWidget>
#include <QPainter>
#include "BaseTheme.hpp"
#include "util/FunctionEventFilter.hpp"
namespace AB_NAMESPACE {
Button::Button(BaseWidget *parent)
: BaseWidget(parent)
{
connect(&effectTimer_, &QTimer::timeout, this,
&Button::onMouseEffectTimeout);
this->effectTimer_.setInterval(20);
this->effectTimer_.start();
this->setMouseTracking(true);
}
void Button::setMouseEffectColor(boost::optional<QColor> color)
{
this->mouseEffectColor_ = color;
}
void Button::setPixmap(const QPixmap &_pixmap)
{
this->pixmap_ = _pixmap;
this->update();
}
const QPixmap &Button::getPixmap() const
{
return this->pixmap_;
}
void Button::setDim(bool value)
{
this->dimPixmap_ = value;
this->update();
}
bool Button::getDim() const
{
return this->dimPixmap_;
}
void Button::setEnable(bool value)
{
this->enabled_ = value;
this->update();
}
bool Button::getEnable() const
{
return this->enabled_;
}
void Button::setEnableMargin(bool value)
{
this->enableMargin_ = value;
this->update();
}
bool Button::getEnableMargin() const
{
return this->enableMargin_;
}
qreal Button::getCurrentDimAmount() const
{
return this->dimPixmap_ && !this->mouseOver_ ? 0.7 : 1;
}
void Button::setBorderColor(const QColor &color)
{
this->borderColor_ = color;
this->update();
}
const QColor &Button::getBorderColor() const
{
return this->borderColor_;
}
void Button::setMenu(std::unique_ptr<QMenu> menu)
{
this->menu_ = std::move(menu);
this->menu_->installEventFilter(
new FunctionEventFilter(this, [this](QObject *, QEvent *event) {
if (event->type() == QEvent::Hide)
{
QTimer::singleShot(20, this,
[this] { this->menuVisible_ = false; });
}
return false;
}));
}
void Button::paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.setRenderHint(QPainter::SmoothPixmapTransform);
if (!this->pixmap_.isNull())
{
if (!this->mouseOver_ && this->dimPixmap_ && this->enabled_)
{
painter.setOpacity(this->getCurrentDimAmount());
}
QRect rect = this->rect();
int s = this->enableMargin_ ? int(6 * this->scale()) : 0;
rect.moveLeft(s);
rect.setRight(rect.right() - s - s);
rect.moveTop(s);
rect.setBottom(rect.bottom() - s - s);
painter.drawPixmap(rect, this->pixmap_);
painter.setOpacity(1);
}
this->fancyPaint(painter);
if (this->borderColor_.isValid())
{
painter.setRenderHint(QPainter::Antialiasing, false);
painter.setPen(this->borderColor_);
painter.drawRect(0, 0, this->width() - 1, this->height() - 1);
}
}
void Button::fancyPaint(QPainter &painter)
{
if (!this->enabled_)
{
return;
}
painter.setRenderHint(QPainter::HighQualityAntialiasing);
painter.setRenderHint(QPainter::Antialiasing);
QColor c;
if (this->mouseEffectColor_)
{
c = this->mouseEffectColor_.get();
}
else
{
c = this->theme->isLightTheme() ? QColor(0, 0, 0)
: QColor(255, 255, 255);
}
if (this->hoverMultiplier_ > 0)
{
QRadialGradient gradient(QPointF(mousePos_), this->width() / 2);
gradient.setColorAt(0, QColor(c.red(), c.green(), c.blue(),
int(50 * this->hoverMultiplier_)));
gradient.setColorAt(1, QColor(c.red(), c.green(), c.blue(),
int(40 * this->hoverMultiplier_)));
painter.fillRect(this->rect(), gradient);
}
for (auto effect : this->clickEffects_)
{
QRadialGradient gradient(effect.position.x(), effect.position.y(),
effect.progress * qreal(width()) * 2,
effect.position.x(), effect.position.y());
gradient.setColorAt(0, QColor(c.red(), c.green(), c.blue(),
int((1 - effect.progress) * 95)));
gradient.setColorAt(0.9999, QColor(c.red(), c.green(), c.blue(),
int((1 - effect.progress) * 95)));
gradient.setColorAt(1, QColor(c.red(), c.green(), c.blue(), int(0)));
painter.fillRect(this->rect(), gradient);
}
}
void Button::enterEvent(QEvent *)
{
this->mouseOver_ = true;
}
void Button::leaveEvent(QEvent *)
{
this->mouseOver_ = false;
}
void Button::mousePressEvent(QMouseEvent *event)
{
if (!this->enabled_)
{
return;
}
if (event->button() != Qt::LeftButton)
{
return;
}
this->clickEffects_.push_back(ClickEffect(event->pos()));
this->mouseDown_ = true;
emit this->leftMousePress();
if (this->menu_ && !this->menuVisible_)
{
QTimer::singleShot(80, this, [this] { this->showMenu(); });
this->mouseDown_ = false;
this->mouseOver_ = false;
}
}
void Button::mouseReleaseEvent(QMouseEvent *event)
{
if (!this->enabled_)
return;
if (event->button() == Qt::LeftButton)
{
this->mouseDown_ = false;
if (this->rect().contains(event->pos()))
emit leftClicked();
}
emit clicked(event->button());
}
void Button::mouseMoveEvent(QMouseEvent *event)
{
if (this->enabled_)
{
this->mousePos_ = event->pos();
this->update();
}
}
void Button::onMouseEffectTimeout()
{
bool performUpdate = false;
if (selected_)
{
if (this->hoverMultiplier_ != 0)
{
this->hoverMultiplier_ =
std::max(0.0, this->hoverMultiplier_ - 0.1);
performUpdate = true;
}
}
else if (mouseOver_)
{
if (this->hoverMultiplier_ != 1)
{
this->hoverMultiplier_ =
std::min(1.0, this->hoverMultiplier_ + 0.5);
performUpdate = true;
}
}
else
{
if (this->hoverMultiplier_ != 0)
{
this->hoverMultiplier_ =
std::max(0.0, this->hoverMultiplier_ - 0.3);
performUpdate = true;
}
}
if (this->clickEffects_.size() != 0)
{
performUpdate = true;
for (auto it = this->clickEffects_.begin();
it != this->clickEffects_.end();)
{
it->progress += mouseDown_ ? 0.02 : 0.07;
if (it->progress >= 1.0)
{
it = this->clickEffects_.erase(it);
}
else
{
it++;
}
}
}
if (performUpdate)
{
update();
}
}
void Button::showMenu()
{
if (!this->menu_)
return;
auto point = [this] {
auto bounds = QApplication::desktop()->availableGeometry(this);
auto point = this->mapToGlobal(
QPoint(this->width() - this->menu_->width(), this->height()));
if (point.y() + this->menu_->height() > bounds.bottom())
{
point.setY(point.y() - this->menu_->height() - this->height());
}
return point;
};
this->menu_->popup(point());
this->menu_->move(point());
this->menuVisible_ = true;
}
} // namespace AB_NAMESPACE

View file

@ -0,0 +1,89 @@
#pragma once
#include <boost/optional.hpp>
#include "widgets/BaseWidget.hpp"
#include <QMenu>
#include <QMouseEvent>
#include <QPainter>
#include <QPoint>
#include <QTimer>
#include <QWidget>
namespace AB_NAMESPACE {
class Button : public BaseWidget
{
Q_OBJECT
struct ClickEffect {
double progress = 0.0;
QPoint position;
ClickEffect(QPoint _position)
: position(_position)
{
}
};
public:
Button(BaseWidget *parent = nullptr);
void setMouseEffectColor(boost::optional<QColor> color);
void setPixmap(const QPixmap &pixmap_);
const QPixmap &getPixmap() const;
void setDim(bool value);
bool getDim() const;
qreal getCurrentDimAmount() const;
void setEnable(bool value);
bool getEnable() const;
void setEnableMargin(bool value);
bool getEnableMargin() const;
void setBorderColor(const QColor &color);
const QColor &getBorderColor() const;
void setMenu(std::unique_ptr<QMenu> menu);
signals:
void leftClicked();
void clicked(Qt::MouseButton button);
void leftMousePress();
protected:
virtual void paintEvent(QPaintEvent *) override;
virtual void enterEvent(QEvent *) override;
virtual void leaveEvent(QEvent *) override;
virtual void mousePressEvent(QMouseEvent *event) override;
virtual void mouseReleaseEvent(QMouseEvent *event) override;
virtual void mouseMoveEvent(QMouseEvent *event) override;
void fancyPaint(QPainter &painter);
bool enabled_{true};
bool selected_{false};
bool mouseOver_{false};
bool mouseDown_{false};
bool menuVisible_{false};
private:
void onMouseEffectTimeout();
void showMenu();
QColor borderColor_{};
QPixmap pixmap_{};
bool dimPixmap_{true};
bool enableMargin_{true};
QPoint mousePos_{};
double hoverMultiplier_{0.0};
QTimer effectTimer_{};
std::vector<ClickEffect> clickEffects_{};
boost::optional<QColor> mouseEffectColor_{};
std::unique_ptr<QMenu> menu_{};
};
} // namespace AB_NAMESPACE

View file

@ -0,0 +1,43 @@
#include "widgets/helper/EffectLabel.hpp"
#include <QBrush>
#include <QPainter>
namespace AB_NAMESPACE {
EffectLabel::EffectLabel(BaseWidget *parent, int spacing)
: Button(parent)
, label_(this)
{
setLayout(&this->hbox_);
this->label_.setAlignment(Qt::AlignCenter);
this->hbox_.setMargin(0);
this->hbox_.addSpacing(spacing);
this->hbox_.addWidget(&this->label_);
this->hbox_.addSpacing(spacing);
}
EffectLabel2::EffectLabel2(BaseWidget *parent, int padding)
: Button(parent)
, label_(this)
{
auto *hbox = new QHBoxLayout(this);
this->setLayout(hbox);
// this->label_.setAlignment(Qt::AlignCenter);
this->label_.setCentered(true);
hbox->setMargin(0);
// hbox.addSpacing(spacing);
hbox->addWidget(&this->label_);
// hbox.addSpacing(spacing);
}
Label &EffectLabel2::getLabel()
{
return this->label_;
}
} // namespace AB_NAMESPACE

View file

@ -0,0 +1,41 @@
#pragma once
#include "widgets/BaseWidget.hpp"
#include "widgets/Label.hpp"
#include "widgets/helper/Button.hpp"
#include "widgets/helper/SignalLabel.hpp"
#include <QHBoxLayout>
#include <QLabel>
#include <QPaintEvent>
#include <QWidget>
namespace AB_NAMESPACE {
class EffectLabel : public Button
{
public:
explicit EffectLabel(BaseWidget *parent = nullptr, int spacing = 6);
SignalLabel &getLabel()
{
return this->label_;
}
private:
QHBoxLayout hbox_;
SignalLabel label_;
};
class EffectLabel2 : public Button
{
public:
explicit EffectLabel2(BaseWidget *parent = nullptr, int padding = 6);
Label &getLabel();
private:
Label label_;
};
} // namespace AB_NAMESPACE

View file

@ -0,0 +1,41 @@
#include "widgets/helper/SignalLabel.hpp"
namespace AB_NAMESPACE {
SignalLabel::SignalLabel(QWidget *parent, Qt::WindowFlags f)
: QLabel(parent, f)
{
}
void SignalLabel::mouseDoubleClickEvent(QMouseEvent *ev)
{
emit this->mouseDoubleClick(ev);
}
void SignalLabel::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton)
{
emit mouseDown();
}
event->ignore();
}
void SignalLabel::mouseReleaseEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton)
{
emit mouseUp();
}
event->ignore();
}
void SignalLabel::mouseMoveEvent(QMouseEvent *event)
{
emit this->mouseMove(event);
event->ignore();
}
} // namespace AB_NAMESPACE

View file

@ -0,0 +1,32 @@
#pragma once
#include <QFlags>
#include <QLabel>
#include <QMouseEvent>
#include <QWidget>
namespace AB_NAMESPACE {
class SignalLabel : public QLabel
{
Q_OBJECT
public:
explicit SignalLabel(QWidget *parent = nullptr, Qt::WindowFlags f = 0);
virtual ~SignalLabel() override = default;
signals:
void mouseDoubleClick(QMouseEvent *ev);
void mouseDown();
void mouseUp();
void mouseMove(QMouseEvent *event);
protected:
void mouseDoubleClickEvent(QMouseEvent *ev) override;
void mousePressEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
};
} // namespace AB_NAMESPACE

View file

@ -0,0 +1,134 @@
#include "TitlebarButton.hpp"
#include "BaseTheme.hpp"
namespace AB_NAMESPACE {
TitleBarButton::TitleBarButton()
: Button(nullptr)
{
}
TitleBarButtonStyle TitleBarButton::getButtonStyle() const
{
return this->style_;
}
void TitleBarButton::setButtonStyle(TitleBarButtonStyle _style)
{
this->style_ = _style;
this->update();
}
void TitleBarButton::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
painter.setOpacity(this->getCurrentDimAmount());
QColor color = this->theme->window.text;
QColor background = this->theme->window.background;
int xD = this->height() / 3;
int centerX = this->width() / 2;
painter.setRenderHint(QPainter::Antialiasing, false);
switch (this->style_)
{
case TitleBarButtonStyle::Minimize:
{
painter.fillRect(centerX - xD / 2, xD * 3 / 2, xD, 1, color);
break;
}
case TitleBarButtonStyle::Maximize:
{
painter.setPen(color);
painter.drawRect(centerX - xD / 2, xD, xD - 1, xD - 1);
break;
}
case TitleBarButtonStyle::Unmaximize:
{
int xD2 = xD * 1 / 5;
int xD3 = xD * 4 / 5;
painter.drawRect(centerX - xD / 2 + xD2, xD, xD3, xD3);
painter.fillRect(centerX - xD / 2, xD + xD2, xD3, xD3,
this->theme->window.background);
painter.drawRect(centerX - xD / 2, xD + xD2, xD3, xD3);
break;
}
case TitleBarButtonStyle::Close:
{
QRect rect(centerX - xD / 2, xD, xD - 1, xD - 1);
painter.setPen(QPen(color, 1));
painter.drawLine(rect.topLeft(), rect.bottomRight());
painter.drawLine(rect.topRight(), rect.bottomLeft());
break;
}
case TitleBarButtonStyle::User:
{
color = "#999";
painter.setRenderHint(QPainter::Antialiasing);
painter.setRenderHint(QPainter::HighQualityAntialiasing);
auto a = xD / 3;
QPainterPath path;
painter.save();
painter.translate(3, 3);
path.arcMoveTo(a, 4 * a, 6 * a, 6 * a, 0);
path.arcTo(a, 4 * a, 6 * a, 6 * a, 0, 180);
painter.fillPath(path, color);
painter.setBrush(background);
painter.drawEllipse(2 * a, 1 * a, 4 * a, 4 * a);
painter.setBrush(color);
painter.drawEllipse(2.5 * a, 1.5 * a, 3 * a + 1, 3 * a);
painter.restore();
break;
}
case TitleBarButtonStyle::Settings:
{
color = "#999";
painter.setRenderHint(QPainter::Antialiasing);
painter.setRenderHint(QPainter::HighQualityAntialiasing);
painter.save();
painter.translate(3, 3);
auto a = xD / 3;
QPainterPath path;
path.arcMoveTo(a, a, 6 * a, 6 * a, 0 - (360 / 32.0));
for (int i = 0; i < 8; i++)
{
path.arcTo(a, a, 6 * a, 6 * a, i * (360 / 8.0) - (360 / 32.0),
(360 / 32.0));
path.arcTo(2 * a, 2 * a, 4 * a, 4 * a,
i * (360 / 8.0) + (360 / 32.0), (360 / 32.0));
}
painter.strokePath(path, color);
painter.fillPath(path, color);
painter.setBrush(background);
painter.drawEllipse(3 * a, 3 * a, 2 * a, 2 * a);
painter.restore();
break;
}
default:;
}
Button::paintEvent(event);
// this->fancyPaint(painter);
}
} // namespace AB_NAMESPACE

View file

@ -0,0 +1,32 @@
#pragma once
#include "widgets/helper/Button.hpp"
namespace AB_NAMESPACE {
enum class TitleBarButtonStyle {
None = 0,
Minimize = 1,
Maximize = 2,
Unmaximize = 4,
Close = 8,
User = 16,
Settings = 32
};
class TitleBarButton : public Button
{
public:
TitleBarButton();
TitleBarButtonStyle getButtonStyle() const;
void setButtonStyle(TitleBarButtonStyle style_);
protected:
void paintEvent(QPaintEvent *) override;
private:
TitleBarButtonStyle style_;
};
} // namespace AB_NAMESPACE

21
lib/boost.pri Normal file
View file

@ -0,0 +1,21 @@
pajlada {
BOOST_DIRECTORY = C:\dev\projects\boost_1_66_0\
}
win32 {
isEmpty(BOOST_DIRECTORY) {
message(Using default boost directory C:\\local\\boost\\)
BOOST_DIRECTORY = C:\local\boost\
}
INCLUDEPATH += $$BOOST_DIRECTORY
isEmpty(BOOST_LIB_SUFFIX) {
message(Using default boost lib directory suffix lib)
BOOST_LIB_SUFFIX = lib
}
LIBS += -L$$BOOST_DIRECTORY\\$$BOOST_LIB_SUFFIX
} else {
LIBS += -lboost_system -lboost_filesystem
}

4
lib/fmt.pri Normal file
View file

@ -0,0 +1,4 @@
# fmt
SOURCES += $$PWD/fmt/fmt/format.cpp
INCLUDEPATH += $$PWD/fmt/

535
lib/fmt/fmt/format.cpp Normal file
View file

@ -0,0 +1,535 @@
/*
Formatting library for C++
Copyright (c) 2012 - 2016, Victor Zverovich
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "format.h"
#include <string.h>
#include <cctype>
#include <cerrno>
#include <climits>
#include <cmath>
#include <cstdarg>
#include <cstddef> // for std::ptrdiff_t
#if defined(_WIN32) && defined(__MINGW32__)
# include <cstring>
#endif
#if FMT_USE_WINDOWS_H
# if !defined(FMT_HEADER_ONLY) && !defined(WIN32_LEAN_AND_MEAN)
# define WIN32_LEAN_AND_MEAN
# endif
# if defined(NOMINMAX) || defined(FMT_WIN_MINMAX)
# include <windows.h>
# else
# define NOMINMAX
# include <windows.h>
# undef NOMINMAX
# endif
#endif
#if FMT_EXCEPTIONS
# define FMT_TRY try
# define FMT_CATCH(x) catch (x)
#else
# define FMT_TRY if (true)
# define FMT_CATCH(x) if (false)
#endif
#ifdef _MSC_VER
# pragma warning(push)
# pragma warning(disable: 4127) // conditional expression is constant
# pragma warning(disable: 4702) // unreachable code
// Disable deprecation warning for strerror. The latter is not called but
// MSVC fails to detect it.
# pragma warning(disable: 4996)
#endif
// Dummy implementations of strerror_r and strerror_s called if corresponding
// system functions are not available.
static inline fmt::internal::Null<> strerror_r(int, char *, ...) {
return fmt::internal::Null<>();
}
static inline fmt::internal::Null<> strerror_s(char *, std::size_t, ...) {
return fmt::internal::Null<>();
}
namespace fmt {
FMT_FUNC internal::RuntimeError::~RuntimeError() FMT_DTOR_NOEXCEPT {}
FMT_FUNC FormatError::~FormatError() FMT_DTOR_NOEXCEPT {}
FMT_FUNC SystemError::~SystemError() FMT_DTOR_NOEXCEPT {}
namespace {
#ifndef _MSC_VER
# define FMT_SNPRINTF snprintf
#else // _MSC_VER
inline int fmt_snprintf(char *buffer, size_t size, const char *format, ...) {
va_list args;
va_start(args, format);
int result = vsnprintf_s(buffer, size, _TRUNCATE, format, args);
va_end(args);
return result;
}
# define FMT_SNPRINTF fmt_snprintf
#endif // _MSC_VER
#if defined(_WIN32) && defined(__MINGW32__) && !defined(__NO_ISOCEXT)
# define FMT_SWPRINTF snwprintf
#else
# define FMT_SWPRINTF swprintf
#endif // defined(_WIN32) && defined(__MINGW32__) && !defined(__NO_ISOCEXT)
const char RESET_COLOR[] = "\x1b[0m";
typedef void (*FormatFunc)(Writer &, int, StringRef);
// Portable thread-safe version of strerror.
// Sets buffer to point to a string describing the error code.
// This can be either a pointer to a string stored in buffer,
// or a pointer to some static immutable string.
// Returns one of the following values:
// 0 - success
// ERANGE - buffer is not large enough to store the error message
// other - failure
// Buffer should be at least of size 1.
int safe_strerror(
int error_code, char *&buffer, std::size_t buffer_size) FMT_NOEXCEPT {
FMT_ASSERT(buffer != 0 && buffer_size != 0, "invalid buffer");
class StrError {
private:
int error_code_;
char *&buffer_;
std::size_t buffer_size_;
// A noop assignment operator to avoid bogus warnings.
void operator=(const StrError &) {}
// Handle the result of XSI-compliant version of strerror_r.
int handle(int result) {
// glibc versions before 2.13 return result in errno.
return result == -1 ? errno : result;
}
// Handle the result of GNU-specific version of strerror_r.
int handle(char *message) {
// If the buffer is full then the message is probably truncated.
if (message == buffer_ && strlen(buffer_) == buffer_size_ - 1)
return ERANGE;
buffer_ = message;
return 0;
}
// Handle the case when strerror_r is not available.
int handle(internal::Null<>) {
return fallback(strerror_s(buffer_, buffer_size_, error_code_));
}
// Fallback to strerror_s when strerror_r is not available.
int fallback(int result) {
// If the buffer is full then the message is probably truncated.
return result == 0 && strlen(buffer_) == buffer_size_ - 1 ?
ERANGE : result;
}
// Fallback to strerror if strerror_r and strerror_s are not available.
int fallback(internal::Null<>) {
errno = 0;
buffer_ = strerror(error_code_);
return errno;
}
public:
StrError(int err_code, char *&buf, std::size_t buf_size)
: error_code_(err_code), buffer_(buf), buffer_size_(buf_size) {}
int run() {
// Suppress a warning about unused strerror_r.
strerror_r(0, FMT_NULL, "");
return handle(strerror_r(error_code_, buffer_, buffer_size_));
}
};
return StrError(error_code, buffer, buffer_size).run();
}
void format_error_code(Writer &out, int error_code,
StringRef message) FMT_NOEXCEPT {
// Report error code making sure that the output fits into
// INLINE_BUFFER_SIZE to avoid dynamic memory allocation and potential
// bad_alloc.
out.clear();
static const char SEP[] = ": ";
static const char ERROR_STR[] = "error ";
// Subtract 2 to account for terminating null characters in SEP and ERROR_STR.
std::size_t error_code_size = sizeof(SEP) + sizeof(ERROR_STR) - 2;
typedef internal::IntTraits<int>::MainType MainType;
MainType abs_value = static_cast<MainType>(error_code);
if (internal::is_negative(error_code)) {
abs_value = 0 - abs_value;
++error_code_size;
}
error_code_size += internal::count_digits(abs_value);
if (message.size() <= internal::INLINE_BUFFER_SIZE - error_code_size)
out << message << SEP;
out << ERROR_STR << error_code;
assert(out.size() <= internal::INLINE_BUFFER_SIZE);
}
void report_error(FormatFunc func, int error_code,
StringRef message) FMT_NOEXCEPT {
MemoryWriter full_message;
func(full_message, error_code, message);
// Use Writer::data instead of Writer::c_str to avoid potential memory
// allocation.
std::fwrite(full_message.data(), full_message.size(), 1, stderr);
std::fputc('\n', stderr);
}
} // namespace
FMT_FUNC void SystemError::init(
int err_code, CStringRef format_str, ArgList args) {
error_code_ = err_code;
MemoryWriter w;
format_system_error(w, err_code, format(format_str, args));
std::runtime_error &base = *this;
base = std::runtime_error(w.str());
}
template <typename T>
int internal::CharTraits<char>::format_float(
char *buffer, std::size_t size, const char *format,
unsigned width, int precision, T value) {
if (width == 0) {
return precision < 0 ?
FMT_SNPRINTF(buffer, size, format, value) :
FMT_SNPRINTF(buffer, size, format, precision, value);
}
return precision < 0 ?
FMT_SNPRINTF(buffer, size, format, width, value) :
FMT_SNPRINTF(buffer, size, format, width, precision, value);
}
template <typename T>
int internal::CharTraits<wchar_t>::format_float(
wchar_t *buffer, std::size_t size, const wchar_t *format,
unsigned width, int precision, T value) {
if (width == 0) {
return precision < 0 ?
FMT_SWPRINTF(buffer, size, format, value) :
FMT_SWPRINTF(buffer, size, format, precision, value);
}
return precision < 0 ?
FMT_SWPRINTF(buffer, size, format, width, value) :
FMT_SWPRINTF(buffer, size, format, width, precision, value);
}
template <typename T>
const char internal::BasicData<T>::DIGITS[] =
"0001020304050607080910111213141516171819"
"2021222324252627282930313233343536373839"
"4041424344454647484950515253545556575859"
"6061626364656667686970717273747576777879"
"8081828384858687888990919293949596979899";
#define FMT_POWERS_OF_10(factor) \
factor * 10, \
factor * 100, \
factor * 1000, \
factor * 10000, \
factor * 100000, \
factor * 1000000, \
factor * 10000000, \
factor * 100000000, \
factor * 1000000000
template <typename T>
const uint32_t internal::BasicData<T>::POWERS_OF_10_32[] = {
0, FMT_POWERS_OF_10(1)
};
template <typename T>
const uint64_t internal::BasicData<T>::POWERS_OF_10_64[] = {
0,
FMT_POWERS_OF_10(1),
FMT_POWERS_OF_10(ULongLong(1000000000)),
// Multiply several constants instead of using a single long long constant
// to avoid warnings about C++98 not supporting long long.
ULongLong(1000000000) * ULongLong(1000000000) * 10
};
FMT_FUNC void internal::report_unknown_type(char code, const char *type) {
(void)type;
if (std::isprint(static_cast<unsigned char>(code))) {
FMT_THROW(FormatError(
format("unknown format code '{}' for {}", code, type)));
}
FMT_THROW(FormatError(
format("unknown format code '\\x{:02x}' for {}",
static_cast<unsigned>(code), type)));
}
#if FMT_USE_WINDOWS_H
FMT_FUNC internal::UTF8ToUTF16::UTF8ToUTF16(StringRef s) {
static const char ERROR_MSG[] = "cannot convert string from UTF-8 to UTF-16";
if (s.size() > INT_MAX)
FMT_THROW(WindowsError(ERROR_INVALID_PARAMETER, ERROR_MSG));
int s_size = static_cast<int>(s.size());
int length = MultiByteToWideChar(
CP_UTF8, MB_ERR_INVALID_CHARS, s.data(), s_size, FMT_NULL, 0);
if (length == 0)
FMT_THROW(WindowsError(GetLastError(), ERROR_MSG));
buffer_.resize(length + 1);
length = MultiByteToWideChar(
CP_UTF8, MB_ERR_INVALID_CHARS, s.data(), s_size, &buffer_[0], length);
if (length == 0)
FMT_THROW(WindowsError(GetLastError(), ERROR_MSG));
buffer_[length] = 0;
}
FMT_FUNC internal::UTF16ToUTF8::UTF16ToUTF8(WStringRef s) {
if (int error_code = convert(s)) {
FMT_THROW(WindowsError(error_code,
"cannot convert string from UTF-16 to UTF-8"));
}
}
FMT_FUNC int internal::UTF16ToUTF8::convert(WStringRef s) {
if (s.size() > INT_MAX)
return ERROR_INVALID_PARAMETER;
int s_size = static_cast<int>(s.size());
int length = WideCharToMultiByte(
CP_UTF8, 0, s.data(), s_size, FMT_NULL, 0, FMT_NULL, FMT_NULL);
if (length == 0)
return GetLastError();
buffer_.resize(length + 1);
length = WideCharToMultiByte(
CP_UTF8, 0, s.data(), s_size, &buffer_[0], length, FMT_NULL, FMT_NULL);
if (length == 0)
return GetLastError();
buffer_[length] = 0;
return 0;
}
FMT_FUNC void WindowsError::init(
int err_code, CStringRef format_str, ArgList args) {
error_code_ = err_code;
MemoryWriter w;
internal::format_windows_error(w, err_code, format(format_str, args));
std::runtime_error &base = *this;
base = std::runtime_error(w.str());
}
FMT_FUNC void internal::format_windows_error(
Writer &out, int error_code, StringRef message) FMT_NOEXCEPT {
FMT_TRY {
MemoryBuffer<wchar_t, INLINE_BUFFER_SIZE> buffer;
buffer.resize(INLINE_BUFFER_SIZE);
for (;;) {
wchar_t *system_message = &buffer[0];
int result = FormatMessageW(
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
FMT_NULL, error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
system_message, static_cast<uint32_t>(buffer.size()), FMT_NULL);
if (result != 0) {
UTF16ToUTF8 utf8_message;
if (utf8_message.convert(system_message) == ERROR_SUCCESS) {
out << message << ": " << utf8_message;
return;
}
break;
}
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
break; // Can't get error message, report error code instead.
buffer.resize(buffer.size() * 2);
}
} FMT_CATCH(...) {}
fmt::format_error_code(out, error_code, message); // 'fmt::' is for bcc32.
}
#endif // FMT_USE_WINDOWS_H
FMT_FUNC void format_system_error(
Writer &out, int error_code, StringRef message) FMT_NOEXCEPT {
FMT_TRY {
internal::MemoryBuffer<char, internal::INLINE_BUFFER_SIZE> buffer;
buffer.resize(internal::INLINE_BUFFER_SIZE);
for (;;) {
char *system_message = &buffer[0];
int result = safe_strerror(error_code, system_message, buffer.size());
if (result == 0) {
out << message << ": " << system_message;
return;
}
if (result != ERANGE)
break; // Can't get error message, report error code instead.
buffer.resize(buffer.size() * 2);
}
} FMT_CATCH(...) {}
fmt::format_error_code(out, error_code, message); // 'fmt::' is for bcc32.
}
template <typename Char>
void internal::ArgMap<Char>::init(const ArgList &args) {
if (!map_.empty())
return;
typedef internal::NamedArg<Char> NamedArg;
const NamedArg *named_arg = FMT_NULL;
bool use_values =
args.type(ArgList::MAX_PACKED_ARGS - 1) == internal::Arg::NONE;
if (use_values) {
for (unsigned i = 0;/*nothing*/; ++i) {
internal::Arg::Type arg_type = args.type(i);
switch (arg_type) {
case internal::Arg::NONE:
return;
case internal::Arg::NAMED_ARG:
named_arg = static_cast<const NamedArg*>(args.values_[i].pointer);
map_.push_back(Pair(named_arg->name, *named_arg));
break;
default:
/*nothing*/;
}
}
return;
}
for (unsigned i = 0; i != ArgList::MAX_PACKED_ARGS; ++i) {
internal::Arg::Type arg_type = args.type(i);
if (arg_type == internal::Arg::NAMED_ARG) {
named_arg = static_cast<const NamedArg*>(args.args_[i].pointer);
map_.push_back(Pair(named_arg->name, *named_arg));
}
}
for (unsigned i = ArgList::MAX_PACKED_ARGS;/*nothing*/; ++i) {
switch (args.args_[i].type) {
case internal::Arg::NONE:
return;
case internal::Arg::NAMED_ARG:
named_arg = static_cast<const NamedArg*>(args.args_[i].pointer);
map_.push_back(Pair(named_arg->name, *named_arg));
break;
default:
/*nothing*/;
}
}
}
template <typename Char>
void internal::FixedBuffer<Char>::grow(std::size_t) {
FMT_THROW(std::runtime_error("buffer overflow"));
}
FMT_FUNC internal::Arg internal::FormatterBase::do_get_arg(
unsigned arg_index, const char *&error) {
internal::Arg arg = args_[arg_index];
switch (arg.type) {
case internal::Arg::NONE:
error = "argument index out of range";
break;
case internal::Arg::NAMED_ARG:
arg = *static_cast<const internal::Arg*>(arg.pointer);
break;
default:
/*nothing*/;
}
return arg;
}
FMT_FUNC void report_system_error(
int error_code, fmt::StringRef message) FMT_NOEXCEPT {
// 'fmt::' is for bcc32.
report_error(format_system_error, error_code, message);
}
#if FMT_USE_WINDOWS_H
FMT_FUNC void report_windows_error(
int error_code, fmt::StringRef message) FMT_NOEXCEPT {
// 'fmt::' is for bcc32.
report_error(internal::format_windows_error, error_code, message);
}
#endif
FMT_FUNC void print(std::FILE *f, CStringRef format_str, ArgList args) {
MemoryWriter w;
w.write(format_str, args);
std::fwrite(w.data(), 1, w.size(), f);
}
FMT_FUNC void print(CStringRef format_str, ArgList args) {
print(stdout, format_str, args);
}
FMT_FUNC void print_colored(Color c, CStringRef format, ArgList args) {
char escape[] = "\x1b[30m";
escape[3] = static_cast<char>('0' + c);
std::fputs(escape, stdout);
print(format, args);
std::fputs(RESET_COLOR, stdout);
}
#ifndef FMT_HEADER_ONLY
template struct internal::BasicData<void>;
// Explicit instantiations for char.
template void internal::FixedBuffer<char>::grow(std::size_t);
template void internal::ArgMap<char>::init(const ArgList &args);
template FMT_API int internal::CharTraits<char>::format_float(
char *buffer, std::size_t size, const char *format,
unsigned width, int precision, double value);
template FMT_API int internal::CharTraits<char>::format_float(
char *buffer, std::size_t size, const char *format,
unsigned width, int precision, long double value);
// Explicit instantiations for wchar_t.
template void internal::FixedBuffer<wchar_t>::grow(std::size_t);
template void internal::ArgMap<wchar_t>::init(const ArgList &args);
template FMT_API int internal::CharTraits<wchar_t>::format_float(
wchar_t *buffer, std::size_t size, const wchar_t *format,
unsigned width, int precision, double value);
template FMT_API int internal::CharTraits<wchar_t>::format_float(
wchar_t *buffer, std::size_t size, const wchar_t *format,
unsigned width, int precision, long double value);
#endif // FMT_HEADER_ONLY
} // namespace fmt
#ifdef _MSC_VER
# pragma warning(pop)
#endif

4012
lib/fmt/fmt/format.h Normal file

File diff suppressed because it is too large Load diff

1
lib/rapidjson Submodule

@ -0,0 +1 @@
Subproject commit d87b698d0fcc10a5f632ecbc80a9cb2a8fa094a5

2
lib/rapidjson.pri Normal file
View file

@ -0,0 +1,2 @@
# rapidjson
INCLUDEPATH += $$PWD/rapidjson/include/

1
lib/serialize Submodule

@ -0,0 +1 @@
Subproject commit 130ffc3ec722284ca454a1e70c5478a75f380144

2
lib/serialize.pri Normal file
View file

@ -0,0 +1,2 @@
# serialize
INCLUDEPATH += $$PWD/serialize/include/

1
lib/settings Submodule

@ -0,0 +1 @@
Subproject commit a5040463c01e6b0e562eab82e0decb29cab9b450

8
lib/settings.pri Normal file
View file

@ -0,0 +1,8 @@
# settings
DEFINES += PAJLADA_SETTINGS_BOOST_FILESYSTEM
SOURCES += \
$$PWD/settings/src/settings/settingdata.cpp \
$$PWD/settings/src/settings/settingmanager.cpp
INCLUDEPATH += $$PWD/settings/include/

1
lib/signals Submodule

@ -0,0 +1 @@
Subproject commit 1c38746b05d9311e73c8c8acdfdc4d36c9c551be

2
lib/signals.pri Normal file
View file

@ -0,0 +1,2 @@
# signals
INCLUDEPATH += $$PWD/signals/include/

24
lib/winsdk.pri Normal file
View file

@ -0,0 +1,24 @@
win32 {
LIBS += -luser32
}
# Optional dependency on Windows SDK 7
!contains(QMAKE_TARGET.arch, x86_64) {
win32:exists(C:\Program Files\Microsoft SDKs\Windows\v7.1\Include\Windows.h) {
LIBS += -L"C:\Program Files\Microsoft SDKs\Windows\v7.1\Lib" -ldwmapi
DEFINES += "USEWINSDK"
message(Using Windows SDK 7)
}
}
# Optional dependency on Windows SDK 10
contains(QMAKE_TARGET.arch, x86_64) {
WIN_SDK_VERSION = $$(WindowsSDKVersion)
!isEmpty(WIN_SDK_VERSION) {
!equals(WIN_SDK_VERSION, "\\") {
DEFINES += "USEWINSDK"
message(Using Windows SDK 10)
}
}
}