7 Commits

Author SHA1 Message Date
221e0bc8c2 Merge branch 'experimental/gui_cleanup' 2026-01-06 16:32:05 +01:00
e5f7ba569a Again, added missing classes. 2026-01-06 16:31:47 +01:00
a57b45e21a Fixed dummy progress bar. 2026-01-06 16:21:59 +01:00
1a2d815634 Cleanups. 2026-01-06 15:59:57 +01:00
6c15d99119 More fancyfications. 2026-01-06 13:58:02 +01:00
c21bb2cf4e First steps. 2026-01-06 13:05:19 +01:00
5c52b1e936 Gui cleanups, part I. 2026-01-06 10:53:15 +01:00
56 changed files with 798 additions and 502 deletions

View File

@@ -51,6 +51,7 @@ SOURCES += \
bcdriver.cpp \
bcdrivertinycan.cpp \
bcguihelpers.cpp \
bcsliderstyle.cpp \
bctransmitter.cpp \
bcvalue.cpp \
bcvaluemodel.cpp \
@@ -68,6 +69,7 @@ HEADERS += \
bcdrivertinycan.h \
bcguihelpers.h \
bcmainwindow.h \
bcsliderstyle.h \
bctransmitter.h \
bcvalue.h \
bcvaluemodel.h \

View File

@@ -181,54 +181,31 @@ QSize BCAnimatedDelegate::sizeHint(const QStyleOptionViewItem &option, const QMo
void BCAnimatedDelegate::paint(QPainter *painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
// 1. Standard-Zeichnen (Text, Hintergrund, Selection) durchführen
// Standard-Zeichnen (Text, Hintergrund, Selection) durchführen
QStyledItemDelegate::paint(painter, option, index);
//QPen pen(QColor(255, 165, 0)); // Orange
/*
if (index.row() == _highlightedRow && _opacity > 0.0)
{
painter->save();
qDebug() << " --- is highlight: " << index.row();
QColor highlightColor( 0xFF9800 );
highlightColor.setAlphaF(_opacity);
QPen pen(highlightColor, 3);
painter->setPen(pen);
painter->setBrush(Qt::NoBrush);
painter->drawRoundedRect(option.rect.adjusted(2, 2, -2, -2), 8, 8);
painter->restore();
}
*/
int row = index.row();
if (index.column() == 1 )
int col = index.column();
switch (col)
{
case 1:
if( m_rowOpacities.contains(row))
{
paintHighlightRow(painter,option,index);
}
break;
case 2:
if( row>-1 && row <= _valueList.size() )
paintSliderIndicator(painter,option,index);
default:
break;
}
}
/*
qreal opacity = m_rowOpacities.value(row);
if (opacity > 0.01)
{
painter->save();
painter->setOpacity(opacity);
painter->fillRect(option.rect, QColor(255, 140, 0, 120));
painter->restore();
}
*/
void BCAnimatedDelegate::paintHighlightRow(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
painter->save();
@@ -239,33 +216,70 @@ void BCAnimatedDelegate::paintHighlightRow(QPainter* painter, const QStyleOption
// Margin von 4px
QRect itemRect = option.rect.adjusted(3, 3, -3, -3);
// Border von 2px berücksichtigen (nach innen)
//QRect contentRect = itemRect.adjusted(2, 2, -2, -2);
// painter->fillRect(contentRect,Qt::green);
/*
// Hintergrund (weiß)
painter->setBrush(Qt::white);
painter->setPen(Qt::NoPen);
painter->drawRoundedRect(itemRect, 8, 8);
*/
// Border (2px solid #2196F3)
QPen borderPen( Qt::red, 1);
painter->setPen(borderPen);
painter->setBrush(Qt::NoBrush);
painter->drawRoundedRect(itemRect, 2, 2);
// Padding von 8px für den Content
//QRect textRect = contentRect.adjusted(8, 8, -8, -8);
/*
// Text zeichnen
painter->setPen(Qt::black); // oder option.palette.color(QPalette::Text)
QString text = index.data(Qt::DisplayRole).toString();
painter->drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, text);
*/
painter->restore();
}
void BCAnimatedDelegate::paintSliderIndicator(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
const BCValue& valueX = *(_valueList[ index.row()].get());
int value = 50;index.model()->data(index, Qt::DisplayRole).toInt();
// Hintergrund
if (option.state & QStyle::State_Selected)
{
painter->fillRect(option.rect, option.palette.highlight());
}
else if (index.row() % 2 == 1)
{
painter->fillRect(option.rect, QColor(0xFAFAFA));
}
else
{
painter->fillRect(option.rect, Qt::white);
}
// Text und kleiner Slider-Indikator zeichnen
painter->save();
painter->setRenderHint(QPainter::Antialiasing);
//QRect textRect = option.rect.adjusted(8, 0, -120, 0);
QRect barRect = option.rect.adjusted
(
8,
option.rect.height() / 2 - 2,
-8,
-option.rect.height() / 2 + 2
);
//QRect barRect = option.rect;
// Text
//painter->setPen(option.state & QStyle::State_Selected ? option.palette.highlightedText().color() : Qt::black);
//painter->drawText(textRect, Qt::AlignVCenter | Qt::AlignLeft,
// QString::number(value));
// Mini Progress Bar
painter->setPen(Qt::NoPen);
painter->setBrush(QColor(0xE0E0E0));
painter->drawRoundedRect(barRect, 2, 2);
QRect fillRect = barRect;
fillRect.setWidth(barRect.width() * value / 100);
painter->setBrush(QColor(0x0078D4));
painter->drawRoundedRect(fillRect, 2, 2);
painter->restore();
}
void BCAnimatedDelegate::onHighlightRow(int row)
{

View File

@@ -61,21 +61,6 @@ public:
QSize sizeHint(const QStyleOptionViewItem &option,const QModelIndex& index) const override;
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex& index) const override;
/*
qreal highlightOpacity() const
{
return _opacity;
}
void setHighlightOpacity(qreal opacity)
{
_opacity = opacity;
//qDebug() << " --- opa: " << opacity;
// __fix! unsinn!
emit viewUpdateNeeded();
}
*/
void clearAllHighlights();
public slots:
@@ -91,13 +76,11 @@ private:
void updateRow(int row);
void paintHighlightRow(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const;
void paintSliderIndicator(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const;
const BCValueList& _valueList;
QTableView* _view{};
//int _highlightedRow{-1};
//qreal _opacity{1.0};
QPropertyAnimation* _animation{};
private:

View File

@@ -30,6 +30,8 @@
***************************************************************************/
#include <QHeaderView>
#include <bcdeviceview.h>
#include <bcanimateddelegate.h>
@@ -44,17 +46,12 @@ BCDeviceView::BCDeviceView(QWidget *parent)
// __fix! ziemlich wildes ge-pointere, hier
_itemDelegate = new BCAnimatedDelegate( _valueModel.getValueList(), this);
setItemDelegate( _itemDelegate );
qDebug() << " --- View size I: " << this->size();
}
void BCDeviceView::setDeviceID( BCDevice::ID deviceID )
{
qDebug() << " --- View size II: " << this->size();
_devideID = deviceID;
}
@@ -64,7 +61,9 @@ BCDevice::ID BCDeviceView::getDeviceID() const
}
/**
* @brief Gibt eine Referenz auf der ValueList zurück.
*/
const BCValueList& BCDeviceView::getValueListX()
{
@@ -72,8 +71,10 @@ const BCValueList& BCDeviceView::getValueListX()
}
// __FIX ist das ok so?
/**
* @brief SLOT, der aufgerufen wird, wenn die ValueList vom XML-Lader fertig geladen wurde.
* Die DeviceView nimmt die ValueList dann in Besitz.
*/
void BCDeviceView::onValueListReady( BCDevice::ID deviceID, BCValueList valueList )
{
qDebug() << " --- onValueListReady: " << deviceID << ": " << valueList.size();
@@ -81,11 +82,28 @@ void BCDeviceView::onValueListReady( BCDevice::ID deviceID, BCValueList valueLis
_valueModel.takeValueList( valueList );
}
/**
* @brief SLOT, der aufgerufen wird, wenn ein Value geändert wurde. Gibt dem ItemDelegate Bescheid.
*/
void BCDeviceView::onValueUpdated(int index, BCValue::State state, const QString& newVisibleValue )
{
_valueModel.onValueUpdated( index, state, newVisibleValue);
_itemDelegate->onHighlightRow( index );
}
void BCDeviceView::resizeEvent(QResizeEvent *event)
{
// Zuerst die Basisklasse aufrufen (Wichtig für Layouts!)
QWidget::resizeEvent(event);
// Berechnung: 40% der aktuellen Breite
// Tipp: viewport()->width() ist genauer als width(), da es Scrollbars rausrechnet!
int totalWidth = viewport()->width();
int col0Width = static_cast<int>(totalWidth * 0.60);
// Setzen der Breite
horizontalHeader()->resizeSection(0, col0Width);
}

View File

@@ -64,6 +64,8 @@ public slots:
protected:
void resizeEvent(QResizeEvent *event) override;
BCDevice::ID _devideID{BCDevice::ID::Invalid};
BCValueModel _valueModel;
BCAnimatedDelegate* _itemDelegate{};

View File

@@ -43,23 +43,25 @@ BCThemeSwitchButton::BCThemeSwitchButton(QWidget *parent )
// CSS: Transparent, damit es sich nahtlos in den StatusBar einfügt
// Schriftgröße etwas erhöhen, damit die Emojis gut erkennbar sind
/*
setStyleSheet(R"(
QPushButton {
BCThemeSwitchButton
{
border: none;
background-color: transparent;
background-color: green;
font-size: 11pt;
}
QPushButton:hover {
background-color: rgba(128, 128, 128, 30); // Leichter Hover-Effekt
BCThemeSwitchButton:Hover
{
background-color: rgba(128, 128, 128, 30);
border-radius: 24px;
}
)");
*/
// Initialer Status (Startet im Dark Mode -> zeigt Mond)
updateIcon();
connect(this, &QPushButton::clicked, this, &BCThemeSwitchButton::toggle);
connect(this, &QPushButton::clicked, this, &BCThemeSwitchButton::toggleMode);
}

View File

@@ -52,8 +52,6 @@ BCMainWindow::BCMainWindow(QWidget *parent)
setupUi(this);
// den pimp-my-ride-button schalten wir vorerst aus.
_pimpButton->hide();
// Wir schreiben den 'initMainWindow()' Aufruf mit Hilfe des
// timers in die Event-Queue, damit er erst ausgeführt wird,
@@ -111,14 +109,11 @@ void BCMainWindow::initMainWindow()
_devicePanels[BCDevice::ID::Console] = _consolePanel;
_devicePanels[BCDevice::ID::Battery] = _batteryPanel;
_devicePanels[BCDevice::ID::Motor] = _motorPanel;
//_devicePanels[BCDevice::ID::Pimp] = _pimpPanel;
// Die actions an die Buttons binden
configureAction(_motorButton, _motorAction, BCDevice::ID::Motor );
configureAction(_consoleButton, _consoleAction, BCDevice::ID::Console );
configureAction(_batteryButton, _batteryAction, BCDevice::ID::Battery );
//configureAction(_pimpButton, _pimpAction, BCDevice::ID::Pimp );
/*
bool m_isDarkMode = false;
@@ -149,9 +144,10 @@ void BCMainWindow::initMainWindow()
// also: emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole, ValueRole});
connect( _connectButton, &QToolButton::clicked, &_transmitter, &BCTransmitter::onToggleDriverConnection );
connect( _syncButton, &QToolButton::clicked, this, &BCMainWindow::onSyncDeviceView );
connect( &_transmitter, &BCTransmitter::valueUpdated, this, &BCMainWindow::onValueUpdated );
connect( _exitButton, &QToolButton::clicked, qApp, &QCoreApplication::quit );
connect(this, &BCMainWindow::requestValueUpdate, &_transmitter, &BCTransmitter::onEnqueueValue);
connect( &_transmitter, &BCTransmitter::valueUpdated, this, &BCMainWindow::onValueUpdated );
connect( this, &BCMainWindow::requestValueUpdate, &_transmitter, &BCTransmitter::onUpdateValue);
connect( &_worker, &QThread::finished, &_transmitter, &QObject::deleteLater);
connect( &_transmitter, &BCTransmitter::driverStateChanged, this, &BCMainWindow::onDriverStateChanged );
@@ -161,9 +157,15 @@ void BCMainWindow::initMainWindow()
// die Daten des eBikes laden
_dataManager.loadXmlBikeData(":/bikeinfo.xml"_L1);
//_consoleAction->trigger();
_batteryAction->trigger();
_consoleAction->trigger();
//_batteryAction->trigger();
// Dummy sync beim starten
QTimer::singleShot(1000, this, [this]()
{
onSyncDeviceView();
});
}
/*
@@ -191,15 +193,19 @@ void BCMainWindow::initStatusBar()
statBar->addPermanentWidget(themeBtn);
connect(themeBtn, &BCThemeSwitchButton::themeChanged, this, [this](bool isDark)
{
QString message = isDark ? "Dark Mode Activated" : "Light Mode Activated";
statusBar()->showMessage( message, 3000);
setApplicationStyleSheet( isDark ? ":/claude_dark_mode.qss"_L1 : ":/claude_light_mode.qss"_L1 );
setApplicationStyleSheet( isDark ? ":claude_dark_mode.qss"_L1 : ":claude_light_mode.qss"_L1 );
});
// Wir starten im light mode
//themeBtn->setDarkMode( false );
statBar->showMessage("Ready");
setApplicationStyleSheet(":bionxcontrol.qss"_L1);
}
/*
@@ -322,7 +328,7 @@ void BCMainWindow::onSyncDeviceView()
// wir setzen auf 'lesen'
value->state.setFlag( BCValue::State::ReadMe );
// statt '_transmitter.onEnqueueValue( value )' müssen wir hier
// statt '_transmitter.onUpdateValue( value )' müssen wir hier
// über emit requestValueUpdate() zur Thread sysnchronisation
// entkoppeln,

View File

@@ -71,9 +71,15 @@
<height>64</height>
</size>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
<attribute name="buttonGroup">
<string notr="true">_buttonGroup</string>
</attribute>
</widget>
</item>
<item alignment="Qt::AlignmentFlag::AlignHCenter">
@@ -93,9 +99,15 @@
<height>64</height>
</size>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
<attribute name="buttonGroup">
<string notr="true">_buttonGroup</string>
</attribute>
</widget>
</item>
<item alignment="Qt::AlignmentFlag::AlignHCenter">
@@ -115,31 +127,18 @@
<height>64</height>
</size>
</property>
<property name="autoRaise">
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item alignment="Qt::AlignmentFlag::AlignHCenter">
<widget class="QToolButton" name="_pimpButton">
<property name="minimumSize">
<size>
<width>64</width>
<height>64</height>
</size>
</property>
<property name="text">
<string>...</string>
</property>
<property name="iconSize">
<size>
<width>48</width>
<height>48</height>
</size>
<property name="checked">
<bool>true</bool>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
<attribute name="buttonGroup">
<string notr="true">_buttonGroup</string>
</attribute>
</widget>
</item>
<item>
@@ -157,17 +156,78 @@
</item>
<item alignment="Qt::AlignmentFlag::AlignHCenter">
<widget class="QToolButton" name="_syncButton">
<property name="minimumSize">
<size>
<width>64</width>
<height>64</height>
</size>
</property>
<property name="text">
<string>Sync</string>
<string/>
</property>
<property name="icon">
<iconset resource="bionxcontrol.qrc">
<normaloff>:/sync_yellow.svg</normaloff>:/sync_yellow.svg</iconset>
</property>
<property name="iconSize">
<size>
<width>48</width>
<height>48</height>
</size>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<item alignment="Qt::AlignmentFlag::AlignHCenter">
<widget class="QToolButton" name="_connectButton">
<property name="text">
<string>Connect</string>
<property name="minimumSize">
<size>
<width>64</width>
<height>64</height>
</size>
</property>
<property name="checkable">
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="bionxcontrol.qrc">
<normaloff>:/connect.svg</normaloff>:/connect.svg</iconset>
</property>
<property name="iconSize">
<size>
<width>48</width>
<height>48</height>
</size>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item alignment="Qt::AlignmentFlag::AlignHCenter">
<widget class="QToolButton" name="_exitButton">
<property name="minimumSize">
<size>
<width>64</width>
<height>64</height>
</size>
</property>
<property name="text">
<string>Quit</string>
</property>
<property name="icon">
<iconset resource="bionxcontrol.qrc">
<normaloff>:/exit_red.svg</normaloff>:/exit_red.svg</iconset>
</property>
<property name="iconSize">
<size>
<width>48</width>
<height>48</height>
</size>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
@@ -190,7 +250,7 @@
<number>0</number>
</property>
<property name="currentIndex">
<number>3</number>
<number>2</number>
</property>
<widget class="BCDeviceView" name="_consolePanel">
<property name="frameShape">
@@ -279,61 +339,13 @@
<bool>false</bool>
</attribute>
</widget>
<widget class="BCDeviceView" name="_pimpPanel">
<property name="frameShape">
<enum>QFrame::Shape::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Shadow::Plain</enum>
</property>
<property name="lineWidth">
<number>0</number>
</property>
<property name="showGrid">
<bool>false</bool>
</property>
<property name="gridStyle">
<enum>Qt::PenStyle::NoPen</enum>
</property>
<property name="BCHeaderLabel" stdset="0">
<string>Pimp my Ride ...</string>
</property>
<attribute name="horizontalHeaderVisible">
<bool>false</bool>
</attribute>
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
</widget>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QStatusBar" name="_statusbar">
<property name="styleSheet">
<string notr="true">background-color: #DADADA</string>
</property>
</widget>
<action name="_pimpAction">
<property name="checkable">
<bool>true</bool>
</property>
<property name="icon">
<iconset>
<normaloff>:restart.png</normaloff>:restart.png</iconset>
</property>
<property name="text">
<string>pimp</string>
</property>
<property name="toolTip">
<string>Pimp my Ride</string>
</property>
</action>
<widget class="QStatusBar" name="_statusbar"/>
<action name="_motorAction">
<property name="icon">
<iconset>
@@ -343,7 +355,7 @@
<string>motor</string>
</property>
<property name="toolTip">
<string>Show motor settings</string>
<string>Motoreinstellungen anzeigen und bearbeiten</string>
</property>
</action>
<action name="_batteryAction">
@@ -355,7 +367,7 @@
<string>battery</string>
</property>
<property name="toolTip">
<string>Show battery settings</string>
<string>Batterieeinstellungen anzeigen und bearbeiten</string>
</property>
</action>
<action name="_consoleAction">
@@ -367,31 +379,19 @@
<string>console</string>
</property>
<property name="toolTip">
<string>Show console settings</string>
</property>
</action>
<action name="_exitAction">
<property name="icon">
<iconset>
<normaloff>:exit.png</normaloff>:exit.png</iconset>
</property>
<property name="text">
<string>Exit</string>
</property>
<property name="toolTip">
<string>Exit</string>
<string>Konseleneinstellungen anzeigen und bearbeiten</string>
</property>
</action>
<action name="_connectAction">
<property name="icon">
<iconset resource="bionxcontrol.qrc">
<iconset>
<normaloff>:/connected.png</normaloff>:/connected.png</iconset>
</property>
<property name="text">
<string>connect</string>
</property>
<property name="toolTip">
<string>connect to bike</string>
<string>TinyCAN native Treiber laden</string>
</property>
<property name="menuRole">
<enum>QAction::MenuRole::TextHeuristicRole</enum>
@@ -409,4 +409,7 @@
<include location="bionxcontrol.qrc"/>
</resources>
<connections/>
<buttongroups>
<buttongroup name="_buttonGroup"/>
</buttongroups>
</ui>

34
bcsliderstyle.cpp Normal file
View File

@@ -0,0 +1,34 @@
/***************************************************************************
BionxControl
© 2025 -2026 christoph holzheuer
christoph.holzheuer@gmail.com
Using:
mhs_can_drv.c
© 2011 - 2023 by MHS-Elektronik GmbH & Co. KG, Germany
Klaus Demlehner, klaus@mhs-elektronik.de
@see www.mhs-elektronik.de
Based on Bionx data type descriptions from:
BigXionFlasher USB V 0.2.4 rev. 97
© 2011-2013 by Thomas Koenig <info@bigxionflasher.org>
@see www.bigxionflasher.org
Bionx Bike Info
© 2018 Thorsten Schmidt (tschmidt@ts-soft.de)
@see www.ts-soft.de
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
@see https://github.com/bikemike/bionx-bikeinfo
***************************************************************************/
#include <bcsliderstyle.h>

269
bcsliderstyle.h Normal file
View File

@@ -0,0 +1,269 @@
/***************************************************************************
BionxControl
© 2025 -2026 christoph holzheuer
christoph.holzheuer@gmail.com
Using:
mhs_can_drv.c
© 2011 - 2023 by MHS-Elektronik GmbH & Co. KG, Germany
Klaus Demlehner, klaus@mhs-elektronik.de
@see www.mhs-elektronik.de
Based on Bionx data type descriptions from:
BigXionFlasher USB V 0.2.4 rev. 97
© 2011-2013 by Thomas Koenig <info@bigxionflasher.org>
@see www.bigxionflasher.org
Bionx Bike Info
© 2018 Thorsten Schmidt (tschmidt@ts-soft.de)
@see www.ts-soft.de
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
@see https://github.com/bikemike/bionx-bikeinfo
***************************************************************************/
#ifndef BCSLIDERSTYLE_H
#define BCSLIDERSTYLE_H
#include <QStyledItemDelegate>
#include <QApplication>
#include <QWidget>
#include <QTableView>
#include <QStandardItemModel>
#include <QVBoxLayout>
#include <QHeaderView>
#include <QSlider>
#include <QPainter>
#include <QApplication>
#include <QWidget>
#include <QPushButton>
#include <QLineEdit>
#include <QSlider>
#include <QCheckBox>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QLabel>
#include <QProxyStyle>
#include <QPainter>
#include <QStyleOptionSlider>
#include <QGraphicsDropShadowEffect>
// Fluent Design Slider Style
class FluentSliderStyle : public QProxyStyle
{
public:
FluentSliderStyle()
: QProxyStyle()
{}
// Wichtig: Genug Platz für Handle reservieren
int pixelMetric(PixelMetric metric, const QStyleOption* option = nullptr, const QWidget* widget = nullptr) const override
{
switch (metric)
{
case PM_SliderThickness:
return 32; // Höhe für horizontalen Slider
case PM_SliderLength:
return 20; // Handle-Größe
case PM_SliderControlThickness:
return 20;
case PM_SliderSpaceAvailable:
if (option)
{
if (const QStyleOptionSlider* sliderOpt = qstyleoption_cast<const QStyleOptionSlider*>(option)) {
if (sliderOpt->orientation == Qt::Horizontal) {
return sliderOpt->rect.width() - 20;
} else {
return sliderOpt->rect.height() - 20;
}
}
}
return QProxyStyle::pixelMetric(metric, option, widget);
default:
return QProxyStyle::pixelMetric(metric, option, widget);
}
}
QRect subControlRect(ComplexControl cc, const QStyleOptionComplex* opt,SubControl sc, const QWidget* widget) const override
{
if (cc == CC_Slider) {
if (const QStyleOptionSlider* slider = qstyleoption_cast<const QStyleOptionSlider*>(opt)) {
QRect rect = slider->rect;
int handleSize = 20;
if (sc == SC_SliderHandle) {
// Handle Position korrekt berechnen
if (slider->orientation == Qt::Horizontal) {
int range = slider->maximum - slider->minimum;
int pos = slider->sliderPosition - slider->minimum;
int pixelRange = rect.width() - handleSize;
int pixelPos = (range != 0) ? (pos * pixelRange) / range : 0;
return QRect(rect.x() + pixelPos,
rect.center().y() - handleSize / 2,
handleSize, handleSize);
} else {
int range = slider->maximum - slider->minimum;
int pos = slider->sliderPosition - slider->minimum;
int pixelRange = rect.height() - handleSize;
int pixelPos = (range != 0) ? (pos * pixelRange) / range : 0;
return QRect(rect.center().x() - handleSize / 2,
rect.bottom() - pixelPos - handleSize,
handleSize, handleSize);
}
}
}
}
return QProxyStyle::subControlRect(cc, opt, sc, widget);
}
void drawComplexControl(ComplexControl control, const QStyleOptionComplex* option, QPainter* painter, const QWidget* widget) const override
{
if (control == CC_Slider)
{
if (const QStyleOptionSlider* slider = qstyleoption_cast<const QStyleOptionSlider*>(option)) {
painter->setRenderHint(QPainter::Antialiasing);
// Fluent Colors
QColor accentColor(0, 120, 212); // #0078D4
QColor inactiveColor(138, 136, 134); // #8A8886
QColor bgColor(255, 255, 255); // White background
if (slider->orientation == Qt::Horizontal) {
drawHorizontalFluentSlider(painter, slider, accentColor, inactiveColor, bgColor);
} else {
drawVerticalFluentSlider(painter, slider, accentColor, inactiveColor, bgColor);
}
return;
}
}
QProxyStyle::drawComplexControl(control, option, painter, widget);
}
private:
void drawHorizontalFluentSlider(QPainter* painter, const QStyleOptionSlider* slider,
const QColor& activeColor, const QColor& inactiveColor,
const QColor& bgColor) const {
QRect groove = slider->rect;
QRect handle = subControlRect(CC_Slider, slider, SC_SliderHandle, nullptr);
int grooveHeight = 4;
// Track sollte im Widget-Zentrum sein, nicht im groove-Zentrum
int grooveY = slider->rect.center().y() - grooveHeight / 2;
// Full background track
QRect fullTrack(groove.left(), grooveY, groove.width(), grooveHeight);
painter->setPen(Qt::NoPen);
painter->setBrush(inactiveColor.lighter(150));
painter->drawRoundedRect(fullTrack, grooveHeight / 2, grooveHeight / 2);
// Active track (filled portion)
int activeWidth = handle.center().x() - groove.left();
QRect activeTrack(groove.left(), grooveY, activeWidth, grooveHeight);
painter->setBrush(activeColor);
painter->drawRoundedRect(activeTrack, grooveHeight / 2, grooveHeight / 2);
// Handle (Thumb) - Fluent style is more subtle
int handleSize = 20;
QRect thumbRect(handle.center().x() - handleSize / 2,
handle.center().y() - handleSize / 2,
handleSize, handleSize);
// Hover effect - subtle glow
if (slider->state & State_MouseOver) {
painter->setBrush(QColor(activeColor.red(), activeColor.green(),
activeColor.blue(), 30));
int glowSize = 32;
QRect glow(handle.center().x() - glowSize / 2,
handle.center().y() - glowSize / 2,
glowSize, glowSize);
painter->drawEllipse(glow);
}
// Thumb
painter->setBrush(bgColor);
painter->setPen(QPen(activeColor, 2));
painter->drawEllipse(thumbRect);
// Inner circle for pressed state
if (slider->state & State_Sunken) {
int innerSize = 8;
QRect inner(handle.center().x() - innerSize / 2,
handle.center().y() - innerSize / 2,
innerSize, innerSize);
painter->setPen(Qt::NoPen);
painter->setBrush(activeColor);
painter->drawEllipse(inner);
}
}
void drawVerticalFluentSlider(QPainter* painter, const QStyleOptionSlider* slider,
const QColor& activeColor, const QColor& inactiveColor,
const QColor& bgColor) const {
QRect groove = slider->rect;
QRect handle = subControlRect(CC_Slider, slider, SC_SliderHandle, nullptr);
int grooveWidth = 4;
// Track sollte im Widget-Zentrum sein
int grooveX = slider->rect.center().x() - grooveWidth / 2;
// Full background track
QRect fullTrack(grooveX, groove.top(), grooveWidth, groove.height());
painter->setPen(Qt::NoPen);
painter->setBrush(inactiveColor.lighter(150));
painter->drawRoundedRect(fullTrack, grooveWidth / 2, grooveWidth / 2);
// Active track
int activeHeight = groove.bottom() - handle.center().y();
QRect activeTrack(grooveX, handle.center().y(), grooveWidth, activeHeight);
painter->setBrush(activeColor);
painter->drawRoundedRect(activeTrack, grooveWidth / 2, grooveWidth / 2);
// Handle
int handleSize = 20;
QRect thumbRect(handle.center().x() - handleSize / 2,
handle.center().y() - handleSize / 2,
handleSize, handleSize);
if (slider->state & State_MouseOver) {
painter->setBrush(QColor(activeColor.red(), activeColor.green(),
activeColor.blue(), 30));
int glowSize = 32;
QRect glow(handle.center().x() - glowSize / 2,
handle.center().y() - glowSize / 2,
glowSize, glowSize);
painter->drawEllipse(glow);
}
painter->setBrush(bgColor);
painter->setPen(QPen(activeColor, 2));
painter->drawEllipse(thumbRect);
if (slider->state & State_Sunken) {
int innerSize = 8;
QRect inner(handle.center().x() - innerSize / 2,
handle.center().y() - innerSize / 2,
innerSize, innerSize);
painter->setPen(Qt::NoPen);
painter->setBrush(activeColor);
painter->drawEllipse(inner);
}
}
};
#endif // BCSLIDERSTYLE_H

View File

@@ -32,6 +32,7 @@
#include <QThread>
#include <QDebug>
#include <QCoreApplication>
#include <bctransmitter.h>
@@ -40,7 +41,7 @@
*/
BCTransmitter::BCTransmitter(QObject *parent)
: QObject(parent), _isBusy(false)
: QObject(parent)//, _isBusy(false)
{
//_canDriver = new BCDriverTinyCan{this};
_canDriver = &_dummyDriver;
@@ -56,18 +57,24 @@ BCTransmitter::BCTransmitter(QObject *parent)
void BCTransmitter::onToggleDriverConnection( bool connect )
{
qDebug() << " --- onToggleDriverConnection: " << connect;
emit driverStateChanged(BCDriver::DriverState::Initialized, "BUSY!");
bc::delay_millis(350);
// kill all pending stuff
QCoreApplication::removePostedEvents(this, QEvent::MetaCall);
// FIX! Ende der current op abwarten!
BCDriver::DriverState state = connect ? BCDriver::DriverState::DeviceReady : BCDriver::DriverState::NotPresent;
const QString& message = connect ? "Trying to connect" : " FAILED";
emit driverStateChanged(state, message);
return;
/*
// Hier sind wir noch in GUI Thread
QMutexLocker locker(&_mutex);
// weitere operation stoppen
_isBusy = true;
connect ? connectCanDriver() : disconnectCanDriver();
_isBusy = false;
*/
}
@@ -120,50 +127,18 @@ void BCTransmitter::disconnectCanDriver()
}
void BCTransmitter::onEnqueueValue( BCValuePtrConst value)
void BCTransmitter::onUpdateValue( BCValuePtrConst valuePtr)
{
// wir stellen hier auf die arte Tour sicher, das onEnqueueValue
// wir stellen hier auf die harte Tour sicher, das onUpdateValue
// nicht aus dem Parent-Thread direkt sondern über die EventQueue aufgerufen wurde.
Q_ASSERT(QThread::currentThread() == this->thread());
// Hier sind wir noch in GUI Thread
//QMutexLocker locker(&_mutex);
_valueQueue.enqueue( value );
// wir wollen nicht den ganzen Value verschicken, erstrecht
// wollen wir nicht den Value in verschiedenen Threads gleichzeitig
// in die Hand nehmen, also hantieren wir nur mit den Inidizes.
// Trigger processing im Event-Loop des Worker Threads
// invokeMethod mit QueuedConnection entkoppelt den Aufruf,
// damit onEnqueueValue sofort zurückkehrt (non-blocking für den Aufrufer).
//QMetaObject::invokeMethod(this, "onProcessValue", Qt::QueuedConnection);
onProcessValue();
}
void BCTransmitter::onProcessValue()
{
//if (_isBusy)
// return;
_isBusy = true;
while (true)
{
BCValuePtrConst valuePtr{};
{
//QMutexLocker locker(&_mutex);
if (_valueQueue.isEmpty())
{
_isBusy = false;
break; // Schleife verlassen, warten auf neue Events
}
valuePtr =_valueQueue.dequeue();
} // Mutex wird hier freigegeben! WICHTIG: Execute ohne Lock!
// Wir arbeiten hier ohne besondere Threadsynchronisation, mutexed o.ä: Die
// entkoppelung und serialisierung passiert bereits durch die Qt-Eventqueue.
// Das klappt aber nur in der hier gewählten Konstellation mit einer Parent-Thread
// und einem Worker.
// Kosmetik
const BCValue& value = *(valuePtr.get());
@@ -194,7 +169,8 @@ void BCTransmitter::onProcessValue()
TransmitResult result = value.isWord ? readWordValue( devID, regID ) : readByteValue( devID, regID );
if( result.has_value() )
{
newVisibleValue = value.formatValue( result.value() );
// quark! das gehört hier nicht hin!
newVisibleValue = value.formatValues( result.value() );
newState = BCValue::State::InSync;
}
else
@@ -207,11 +183,13 @@ void BCTransmitter::onProcessValue()
// __fix
//bc::processEventsFor(150);
bc::delay_millis(150);
bc::delay_millis(50);
}
}
void BCTransmitter::onProcessValue()
{}
TransmitResult BCTransmitter::readByteValue( uint32_t deviceID, uint8_t registerID )
{

View File

@@ -63,7 +63,7 @@ public:
public slots:
void onToggleDriverConnection( bool connect );
void onEnqueueValue(BCValuePtrConst value );
void onUpdateValue(BCValuePtrConst valuePtr );
void onProcessValue();
void onStartNativeDriver();
@@ -80,11 +80,11 @@ private:
TransmitResult readByteValue( uint32_t deviceID, uint8_t registerID );
TransmitResult readWordValue( uint32_t deviceID, uint8_t registerID );
using BCDataQueue = QQueue<BCValuePtrConst>;
//using BCDataQueue = QQueue<BCValuePtrConst>;
BCDataQueue _valueQueue;
QMutex _mutex;
std::atomic<bool> _isBusy{ false };
//BCDataQueue _valueQueue;
//QMutex _mutex;
//std::atomic<bool> _isBusy{ false };
// __fix!
BCDriver* _canDriver{};

View File

@@ -44,7 +44,7 @@ BCValue::BCValue( BCDevice::ID deviceID_, BC::ID registerID_)
visibleValue = "--";
}
QString BCValue::formatValue( uint32_t value ) const
QString BCValue::formatValues( uint32_t value ) const
{
if( factor == 1 )
return QString::number( value );

View File

@@ -93,7 +93,7 @@ public:
BCValue( BCDevice::ID deviceID_, BC::ID registerID_ );
QString formatValue( uint32_t value ) const;
QString formatValues( uint32_t value ) const;
void dumpValue() const;
mutable States state{BCValue::State::ReadOnly};
@@ -103,6 +103,7 @@ public:
int indexRow{-1};
QString label;
mutable QString visibleValue;
mutable double rawValue;
bool isWord{false};
QString unitLabel;
double factor{1};

View File

@@ -85,7 +85,7 @@ void BCValueModel::onValueUpdated( int row, BCValue::State state, const QString&
{
if( row > -1 && row < _valueList.size() )
{
BCValue& value = *(_valueList[row].get());
const BCValue& value = *(_valueList[row].get());
QModelIndex idx = index(row,1);
//qDebug();
@@ -132,7 +132,7 @@ int BCValueModel::columnCount(const QModelIndex& parent) const
{
if (parent.isValid())
return 0;
return 2;
return 3;
}
@@ -191,7 +191,8 @@ Das Model Gibt hier, unabhängig von der DataRole, immer das
Qt::ItemFlags BCValueModel::flags(const QModelIndex& index) const
{
if (!index.isValid())
// die label spalte lässt sich nicht editieren
if (!index.isValid() || index.column() == 0 )
return Qt::NoItemFlags;
return QAbstractTableModel::flags(index) | Qt::ItemIsEditable;

View File

@@ -59,11 +59,7 @@ void BCXmlLoader::loadXmlBikeData( const QString& fileName )
}
qDebug().noquote() << parts.join(" ");
};
/*
QString fileName = QFileDialog::getOpenFileName(this, "XML öffnen", "", "XML Files (*.xml)");
if (fileName.isEmpty())
return;
*/
QMetaEnum bcDeviceEnum{QMetaEnum::fromType<BCDevice::ID>()};
@@ -92,7 +88,7 @@ void BCXmlLoader::loadXmlBikeData( const QString& fileName )
if (token == QXmlStreamReader::StartElement)
{
QString deviceType = _xml.attributes().value("Type"_L1).toString();
printAttrs (_xml);
//printAttrs (_xml);
// Wir wollen die Device-ID aus dem XML Tag ermitteln
const char* deviceKey = _xml.attributes().value("Type"_L1).toLatin1().constData();
bool ok;
@@ -109,22 +105,13 @@ void BCXmlLoader::loadXmlBikeData( const QString& fileName )
}
}
}
/*
if (xml.hasError())
{
QMessageBox::critical(nullptr, "Parsing Fehler", xml.errorString());
}
else
{
m_model->setDevices(parsedValues);
}
/**
* @brief Lädt deie Daten des BionX eBikes
* @param deviceID
*/
// create & add new model to the model map
}
void BCXmlLoader::loadXmlBikeDeviceData(BCDevice::ID deviceID)
{
auto printAttrs = [](const QXmlStreamReader& xml)
@@ -138,7 +125,7 @@ void BCXmlLoader::loadXmlBikeDeviceData(BCDevice::ID deviceID)
printAttrs (_xml);
Q_ASSERT(_xml.isStartElement() && _xml.name() == "Device"_L1);
qDebug() << " XXX ---------------";
// temporäre Wertliste für neues Device
BCValueList currentValues;

View File

@@ -7,15 +7,12 @@
<file alias="bionx_akku.png">resources/bionx_akku.png</file>
<file alias="bionx_console.png">resources/bionx_console.png</file>
<file alias="bionx_motor.png">resources/bionx_motor.png</file>
<file alias="exit.png">resources/exit.png</file>
<file alias="important.png">resources/important.png</file>
<file alias="restart.png">resources/restart.png</file>
<file alias="splash.png">resources/splash.png</file>
<file alias="connect.png">resources/connect.png</file>
<file alias="connected.png">resources/connected.png</file>
<file alias="disconnected.png">resources/disconnected.png</file>
<file>resources/connect_white.svg</file>
<file>resources/sync_green.svg</file>
<file>resources/sync_yellow.svg</file>
<file alias="connect.svg">resources/connect.svg</file>
<file alias="exit.svg">resources/exit.svg</file>
<file alias="exit_red.svg">resources/exit_red.svg</file>
<file alias="sync_green.svg">resources/sync_green.svg</file>
<file alias="sync_yellow.svg">resources/sync_yellow.svg</file>
<file alias="connect_white.svg">resources/connect_white.svg</file>
<file alias="sync.svg">resources/sync.svg</file>
</qresource>
</RCC>

Binary file not shown.

View File

@@ -27,6 +27,43 @@ sudo apt purge modemmanager
// ---
sudo apt install \
qt6-declarative-dev \
qt6-tools-dev \
qt6-tools-dev-tools \
qt6-l10n-tools \
qt6-scxml-dev \
qt6-serialport-dev \
qt6-serialbus-dev \
qt6-connectivity-dev \
qt6-sensors-dev \
qt6-websockets-dev \
qt6-webchannel-dev \
qt6-svg-dev \
qt6-multimedia-dev \
qt6-charts-dev \
qt6-wayland \
qml6-module-qtquick-controls \
qml6-module-qtquick-layouts \
qml6-module-qtquick-templates \
qml6-module-qtquick-window \
qml6-module-qtqml-workerscript \
qml6-module-qtcharts \
qml6-module-qt-labs-platform \
libqt6sql6-mysql libqt6sql6-psql libqt6sql6-sqlite \
libqt6opengl6-dev
sudo apt update
sudo apt install \
qtcreator \
qtcreator-doc \
cmake \
ninja-build \
gdb \
clang \
clang-format
https://github.com/MHS-Elektronik/OBD-Display
sudo chgrp pi /opt
@@ -36,6 +73,7 @@ https://github.com/MHS-Elektronik/OBD-Display
tar -xzvf tiny_can_raspberry_790.tar.gz
rm tiny_can_raspberry_790.tar.gz
sudo apt install fonts-open-sans
RANT

Binary file not shown.

Binary file not shown.

View File

@@ -1,30 +0,0 @@
Sehr geehrte Frau Spaag,
Aktuell bin ich als Softwareentwickler im Bereich C++/Qt fest angestellt, suche jedoch
nach neuen Entwicklungsmöglichkeiten.
Ihre Stellenanzeige hat mich sofort angesprochen, denn das beschriebene Anforderungsprofil
harmonisiert sehr gut mit meiner derzeitigen Tätigkeit im Bereich Datenvisualisierung und
blub
Ich freu mich wie woschd und verbeibe mit freundlichen Grüßen
Christoph Holzheuer
// ----------
znode<T> ist eine header-only Klasse in modern c++ für generische Baumstrukturen
In der derzeitigen Ausführung dient sie mit Hilfe des pugixml Parser zur Verwaltung
von XML Daten innerhalb des xtree-Projekts.
Die Knoten sind std::c++, die Verwaltung der Attribs ist drangeebrt und somit variable.
Besonderheit ist die Übergabe das String types als template parameter: In Xtree instanzierung durch QString,
andere durch std::string bzw. std::w_string ohne Dependencies zur Qt-Lib eingehen zu müssen.
bridge-code
// ----------

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Binary file not shown.

View File

@@ -1,63 +0,0 @@
Challenges
-------------------------------------------------------------------------------------------------
BionxControl
Aufgabe:
Ansatz:
-------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------
xtree
Aufgabe:
Ansatz:
-------------------------------------------------------------------------------------------------
znode
Aufgabe:
Ansatz:
-------------------------------------------------------------------------------------------------
libPigPio
Aufgabe:
Ansatz:
-------------------------------------------------------------------------------------------------
supportware
Aufgabe:
Ansatz:
-------------------------------------------------------------------------------------------------
raDIYo
Aufgabe:
Ansatz:
-------------------------------------------------------------------------------------------------
miniCash.connect
Aufgabe:
Ansatz:
-------------------------------------------------------------------------------------------------

Binary file not shown.

View File

@@ -30,7 +30,7 @@
<Value ID='Cons_Assist_Mountain_Cap' Label='Mountain Cap' UnitLabel='%' Factor='1.5625' />
</Device>
<!--
<Device Type="Motor">
<Value ID='Motor_Rev_Hw' Label='Hardware Version' />
<Value ID='Motor_Rev_Sw' Label='Software Version' />
@@ -46,7 +46,7 @@
<Value ID='Battery_Rev_Hw' Label='Hardware Version' />
<Value ID='Battery_Rev_Sw' Label='Software Version' />
</Device>
-->
</Bike>
<!--

View File

@@ -3,118 +3,165 @@
/* Alle QWidgets bekommen diesen Font */
QWidget
{
font-size: 14px;
font-family: Segoe UI, sans-serif;
font-family: "Calibri", "Carlito", "Open Sans", sans-serif;
font-size: 10pt;
margin: 0px;
padding: 0px;
}
/*
QMainWindow
QLabel#_headerLabel
{
background-color: #272727;
font-size: 14pt;
font-weight: bold;
}
/*
QToolButton
{
background-color: white;
color: #201F1E;
border: 1px solid #8A8886;
border-radius: 4px;
min-width: 64px;
max-width: 64px;
min-height: 64px;
max-height: 64px;
}
QToolButton:hover
{
background-color: #F3F2F1;
border: 1px solid #323130;
}
QToolButton:pressed
{
background-color: #EDEBE9;
border: 1px solid #201F1E;
}
QToolButton:disabled
{
background-color: #F3F2F1;
color: #A19F9D;
border: 1px solid #EDEBE9;
}
*/
/* Spezifisches Styling für Buttons */
QPushButton
/* === QToolButton === */
QToolButton
{
background-color: #0078d7;
color: white;
background-color: transparent;
color: #000000;
border: none;
border-radius: 4px;
padding: 6px;
min-width: 64px;
max-width: 64px;
min-height: 64px;
max-height: 64px;
}
QPushButton:hover
QToolButton:hover
{
background-color: #1084e3;
background-color: #F9F9F9;
border: 1px solid #DDDDDD;
}
QPushButton:pressed
QToolButton:pressed
{
background-color: #005a9e;
background-color: #E0E0E0;
}
/* Normal */
QToolButton {
background-color: transparent;
border: 1px solid transparent;
QToolButton:checked
{
background-color: green;/*#0078D4;*/
color: #FFFFFF;
}
QToolButton:disabled
{
color: #A19F9D;
}
/* === QTableView & QTableWidget === */
QTableView, QTableWidget
{
background-color: #FFFFFF;
color: #000000;
gridline-color: #E1DFDD;
border: 1px solid #E1DFDD;
border-radius: 4px;
padding: 4px;
selection-background-color: #0078D4;
}
/* Hover */
QToolButton:hover {
background-color: #E3F2FD;
border: 1px solid #2196F3;
}
/* Pressed/Clicked */
QToolButton:pressed {
background-color: #BBDEFB;
}
/* Checked (bei checkable buttons) */
QToolButton:checked {
background-color: #2196F3;
color: white;
}
/* Checked + Hover */
QToolButton:checked:hover {
background-color: #1976D2;
}
/* Disabled */
QToolButton:disabled {
color: #BDBDBD;
background-color: transparent;
}
/*
QTableView
{
background-color: #404142;
border-radius: 8px;
outline: none;
show-decoration-selected: 0;
}
QTableView::item
{
border: 2px solid #2196F3;
border-radius: 8px;
padding: 8px;
margin: 4px;
background-color: white;
}
*/
QTableView::item:hover
{
border-color: #FF9800;
background-color: #fff8f0;
background-color: #e8f4f8;
}
QTableView::item:selected
{
border-color: #F44336; /* Roter Rahmen */
background-color: #ffebee;
QScrollBar::handle:horizontal {
background-color: #C8C6C4;
min-width: 40px;
border-radius: 6px;
margin: 2px;
}
QTableView::item:selected:hover
{
border-color: #FF9800;
background-color: #ffe0b2;
QScrollBar::handle:horizontal:hover {
background-color: #A19F9D;
}
*/
QTableView::item:focus
{
/*
// outline: none; Entfernt das Focus-Rectangle
// border-color: green;
// background-color: #ffe0b2;
*/
border: 2px solid gray;
border-style: inset;
background-color: white;
color: black;
QScrollBar::handle:horizontal:pressed {
background-color: #8A8886;
}
QScrollBar::add-line:horizontal,
QScrollBar::sub-line:horizontal {
width: 0px;
}
QScrollBar::add-page:horizontal,
QScrollBar::sub-page:horizontal {
background: none;
}
QScrollBar:vertical {
background-color: transparent;
width: 12px;
margin: 0;
}
QScrollBar::handle:vertical {
background-color: #C8C6C4;
min-height: 40px;
border-radius: 6px;
margin: 2px;
}
QScrollBar::handle:vertical:hover {
background-color: #A19F9D;
}
QScrollBar::handle:vertical:pressed {
background-color: #8A8886;
}
QScrollBar::add-line:vertical,
QScrollBar::sub-line:vertical {
height: 0px;
}
QScrollBar::add-page:vertical,
QScrollBar::sub-page:vertical {
background: none;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

1
resources/connect.svg Normal file
View File

@@ -0,0 +1 @@
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M8.25 4a.75.75 0 0 1 .743.648L9 4.75v2.773l1.874 2.815a.75.75 0 0 1 .117.306l.009.11v4.496a.75.75 0 0 1-.649.743L10.25 16H8.996v3.254a.75.75 0 0 1-1.492.101l-.007-.101L7.496 16 5.5 15.999v3.258a.75.75 0 0 1-1.493.101l-.006-.101L4 15.999 2.75 16a.75.75 0 0 1-.742-.648l-.007-.102v-4.496a.75.75 0 0 1 .071-.32l.055-.096 1.874-2.815V4.75a.75.75 0 0 1 1.493-.102l.007.102v3a.75.75 0 0 1-.072.32l-.054.096-1.874 2.815V14.5H9.5v-3.52L7.625 8.167a.75.75 0 0 1-.117-.306L7.5 7.75v-3A.75.75 0 0 1 8.25 4Zm7.004.001h4.496a.75.75 0 0 1 .743.649l.007.101L20.499 8h.75a.75.75 0 0 1 .744.648L22 8.75v4.496c0 .111-.025.22-.072.32l-.054.096L20 16.477v2.773a.75.75 0 0 1-1.494.102l-.006-.102v-3c0-.11.024-.22.071-.32l.054-.096L20.5 13.02V9.5h-5.998v3.52l1.874 2.814a.749.749 0 0 1 .118.306l.008.11v3a.75.75 0 0 1-1.493.102l-.007-.102v-2.773l-1.874-2.815a.75.75 0 0 1-.118-.306l-.008-.11V8.75a.75.75 0 0 1 .648-.743L13.752 8h.752V4.751a.75.75 0 0 1 .649-.743l.101-.007h4.496-4.496ZM19 5.501h-2.996V8h2.995V5.501Z" fill="#000000"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1 @@
<svg width="48" height="48" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M8.25 4a.75.75 0 0 1 .743.648L9 4.75v2.773l1.874 2.815a.75.75 0 0 1 .117.306l.009.11v4.496a.75.75 0 0 1-.649.743L10.25 16H8.996v3.254a.75.75 0 0 1-1.492.101l-.007-.101L7.496 16 5.5 15.999v3.258a.75.75 0 0 1-1.493.101l-.006-.101L4 15.999 2.75 16a.75.75 0 0 1-.742-.648l-.007-.102v-4.496a.75.75 0 0 1 .071-.32l.055-.096 1.874-2.815V4.75a.75.75 0 0 1 1.493-.102l.007.102v3a.75.75 0 0 1-.072.32l-.054.096-1.874 2.815V14.5H9.5v-3.52L7.625 8.167a.75.75 0 0 1-.117-.306L7.5 7.75v-3A.75.75 0 0 1 8.25 4Zm7.004.001h4.496a.75.75 0 0 1 .743.649l.007.101L20.499 8h.75a.75.75 0 0 1 .744.648L22 8.75v4.496c0 .111-.025.22-.072.32l-.054.096L20 16.477v2.773a.75.75 0 0 1-1.494.102l-.006-.102v-3c0-.11.024-.22.071-.32l.054-.096L20.5 13.02V9.5h-5.998v3.52l1.874 2.814a.749.749 0 0 1 .118.306l.008.11v3a.75.75 0 0 1-1.493.102l-.007-.102v-2.773l-1.874-2.815a.75.75 0 0 1-.118-.306l-.008-.11V8.75a.75.75 0 0 1 .648-.743L13.752 8h.752V4.751a.75.75 0 0 1 .649-.743l.101-.007h4.496-4.496ZM19 5.501h-2.996V8h2.995V5.501Z" fill="#DDE6E8"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

1
resources/exit.svg Normal file
View File

@@ -0,0 +1 @@
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12 2c5.523 0 10 4.477 10 10s-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2Zm0 1.5a8.5 8.5 0 1 0 0 17 8.5 8.5 0 0 0 0-17Zm3.446 4.897.084.073a.75.75 0 0 1 .073.976l-.073.084L13.061 12l2.47 2.47a.75.75 0 0 1 .072.976l-.073.084a.75.75 0 0 1-.976.073l-.084-.073L12 13.061l-2.47 2.47a.75.75 0 0 1-.976.072l-.084-.073a.75.75 0 0 1-.073-.976l.073-.084L10.939 12l-2.47-2.47a.75.75 0 0 1-.072-.976l.073-.084a.75.75 0 0 1 .976-.073l.084.073L12 10.939l2.47-2.47a.75.75 0 0 1 .976-.072Z" fill="#000000"/></svg>

After

Width:  |  Height:  |  Size: 599 B

1
resources/exit_red.svg Normal file
View File

@@ -0,0 +1 @@
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12 2c5.523 0 10 4.477 10 10s-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2Zm0 1.5a8.5 8.5 0 1 0 0 17 8.5 8.5 0 0 0 0-17Zm3.446 4.897.084.073a.75.75 0 0 1 .073.976l-.073.084L13.061 12l2.47 2.47a.75.75 0 0 1 .072.976l-.073.084a.75.75 0 0 1-.976.073l-.084-.073L12 13.061l-2.47 2.47a.75.75 0 0 1-.976.072l-.084-.073a.75.75 0 0 1-.073-.976l.073-.084L10.939 12l-2.47-2.47a.75.75 0 0 1-.072-.976l.073-.084a.75.75 0 0 1 .976-.073l.084.073L12 10.939l2.47-2.47a.75.75 0 0 1 .976-.072Z" fill="#FF0000"/></svg>

After

Width:  |  Height:  |  Size: 599 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 514 KiB

1
resources/sync.svg Normal file
View File

@@ -0,0 +1 @@
<svg width="48" height="48" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M16 8.25a.75.75 0 0 1 1.5 0v3.25a.75.75 0 0 1-.75.75H14a.75.75 0 0 1 0-1.5h1.27A3.502 3.502 0 0 0 12 8.5c-1.093 0-2.037.464-2.673 1.23a.75.75 0 1 1-1.154-.96C9.096 7.66 10.463 7 12 7c1.636 0 3.088.785 4 2v-.75ZM8 15v.75a.75.75 0 0 1-1.5 0v-3a.75.75 0 0 1 .75-.75H10a.75.75 0 0 1 0 1.5H8.837a3.513 3.513 0 0 0 5.842.765.75.75 0 1 1 1.142.972A5.013 5.013 0 0 1 8 15Zm4-13C6.477 2 2 6.477 2 12s4.477 10 10 10 10-4.477 10-10S17.523 2 12 2Zm8.5 10a8.5 8.5 0 1 1-17 0 8.5 8.5 0 0 1 17 0Z" fill="#000000"/></svg>

After

Width:  |  Height:  |  Size: 609 B

1
resources/sync_green.svg Normal file
View File

@@ -0,0 +1 @@
<svg width="48" height="48" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M16 8.25a.75.75 0 0 1 1.5 0v3.25a.75.75 0 0 1-.75.75H14a.75.75 0 0 1 0-1.5h1.27A3.502 3.502 0 0 0 12 8.5c-1.093 0-2.037.464-2.673 1.23a.75.75 0 1 1-1.154-.96C9.096 7.66 10.463 7 12 7c1.636 0 3.088.785 4 2v-.75ZM8 15v.75a.75.75 0 0 1-1.5 0v-3a.75.75 0 0 1 .75-.75H10a.75.75 0 0 1 0 1.5H8.837a3.513 3.513 0 0 0 5.842.765.75.75 0 1 1 1.142.972A5.013 5.013 0 0 1 8 15Zm4-13C6.477 2 2 6.477 2 12s4.477 10 10 10 10-4.477 10-10S17.523 2 12 2Zm8.5 10a8.5 8.5 0 1 1-17 0 8.5 8.5 0 0 1 17 0Z" fill="#00FF00"/></svg>

After

Width:  |  Height:  |  Size: 609 B

View File

@@ -0,0 +1 @@
<svg width="48" height="48" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M16 8.25a.75.75 0 0 1 1.5 0v3.25a.75.75 0 0 1-.75.75H14a.75.75 0 0 1 0-1.5h1.27A3.502 3.502 0 0 0 12 8.5c-1.093 0-2.037.464-2.673 1.23a.75.75 0 1 1-1.154-.96C9.096 7.66 10.463 7 12 7c1.636 0 3.088.785 4 2v-.75ZM8 15v.75a.75.75 0 0 1-1.5 0v-3a.75.75 0 0 1 .75-.75H10a.75.75 0 0 1 0 1.5H8.837a3.513 3.513 0 0 0 5.842.765.75.75 0 1 1 1.142.972A5.013 5.013 0 0 1 8 15Zm4-13C6.477 2 2 6.477 2 12s4.477 10 10 10 10-4.477 10-10S17.523 2 12 2Zm8.5 10a8.5 8.5 0 1 1-17 0 8.5 8.5 0 0 1 17 0Z" fill="#F2C511"/></svg>

After

Width:  |  Height:  |  Size: 609 B