From 7eab1b5ba1dfcc6884bf288fbdd8c38037c855d3 Mon Sep 17 00:00:00 2001 From: wheaney <42350981+wheaney@users.noreply.github.com> Date: Thu, 15 Jan 2026 13:45:41 -0800 Subject: [PATCH] UI WIP --- kwin/src/breezydesktopconfig.kcfg | 7 +- kwin/src/kcm/breezydesktopeffectkcm.cpp | 96 +++++++++++++++++++++++++ kwin/src/kcm/breezydesktopeffectkcm.h | 8 ++- kwin/src/kcm/breezydesktopeffectkcm.ui | 16 ++++- kwin/src/kcm/labeledslider.h | 30 ++++++++ 5 files changed, 153 insertions(+), 4 deletions(-) diff --git a/kwin/src/breezydesktopconfig.kcfg b/kwin/src/breezydesktopconfig.kcfg index d6f1bca..856f890 100644 --- a/kwin/src/breezydesktopconfig.kcfg +++ b/kwin/src/breezydesktopconfig.kcfg @@ -17,6 +17,11 @@ 230 + + cm + + Units for displaying distances in the KCM UI. Valid values: "cm" or "in". + false @@ -30,7 +35,7 @@ How far apart the displays are visually (not logically) - 100 + 97 10 250 diff --git a/kwin/src/kcm/breezydesktopeffectkcm.cpp b/kwin/src/kcm/breezydesktopeffectkcm.cpp index 527cc9e..8ee41ae 100644 --- a/kwin/src/kcm/breezydesktopeffectkcm.cpp +++ b/kwin/src/kcm/breezydesktopeffectkcm.cpp @@ -48,6 +48,8 @@ #include #include #include +#include +#include #include #include @@ -201,6 +203,27 @@ BreezyDesktopEffectConfig::BreezyDesktopEffectConfig(QObject *parent, const KPlu ui.setupUi(widget()); addConfig(BreezyDesktopConfig::self(), widget()); + // Advanced tab: measurement units selector (stored as "cm" or "in") + if (ui.comboMeasurementUnits) { + ui.comboMeasurementUnits->clear(); + ui.comboMeasurementUnits->addItem(i18n("Centimeters (cm)"), QStringLiteral("cm")); + ui.comboMeasurementUnits->addItem(i18n("Inches (in)"), QStringLiteral("in")); + + { + QSignalBlocker b(ui.comboMeasurementUnits); + const QString saved = KConfigGroup(BreezyDesktopConfig::self()->sharedConfig(), QLatin1String(EFFECT_GROUP)) + .readEntry(QStringLiteral("measurement_units"), QStringLiteral("cm")); + const int idx = ui.comboMeasurementUnits->findData(saved); + ui.comboMeasurementUnits->setCurrentIndex(idx >= 0 ? idx : 0); + } + + connect(ui.comboMeasurementUnits, qOverload(&QComboBox::currentIndexChanged), this, [this](int) { + if (m_updatingFromConfig) return; + applyDistanceLabelFormatters(); + save(); + }); + } + // One-time check if the KWin effect backend is actually loaded. If not, disable UI early. checkEffectLoaded(); @@ -394,6 +417,8 @@ BreezyDesktopEffectConfig::BreezyDesktopEffectConfig(QObject *parent, const KPlu lookAheadOverrideSlider->setValueText(-1, i18n("Default")); } + applyDistanceLabelFormatters(); + renderVirtualDisplays(dbusListVirtualDisplays()); m_virtualDisplayPollTimer.setInterval(15000); @@ -431,6 +456,14 @@ void BreezyDesktopEffectConfig::save() m_updatingFromConfig = true; updateConfigFromUi(); BreezyDesktopConfig::self()->save(); + + // Store measurement_units explicitly (snake_case key) without depending on KConfigXT accessor naming. + { + KConfigGroup grp(BreezyDesktopConfig::self()->sharedConfig(), QLatin1String(EFFECT_GROUP)); + grp.writeEntry(QStringLiteral("measurement_units"), measurementUnitsFromUi()); + grp.sync(); + } + KCModule::save(); ui.kcfg_FocusedDisplayDistance->setEnabled( ui.kcfg_ZoomOnFocusEnabled->isChecked() || ui.SmoothFollowEnabled->isChecked()); @@ -472,11 +505,29 @@ void BreezyDesktopEffectConfig::updateUiFromConfig() ui.kcfg_FocusedDisplayDistance->setEnabled( ui.kcfg_ZoomOnFocusEnabled->isChecked() || ui.SmoothFollowEnabled->isChecked()); ui.kcfg_SmoothFollowThreshold->setValue(BreezyDesktopConfig::self()->smoothFollowThreshold()); + + if (ui.comboMeasurementUnits) { + QSignalBlocker b(ui.comboMeasurementUnits); + const QString saved = KConfigGroup(BreezyDesktopConfig::self()->sharedConfig(), QLatin1String(EFFECT_GROUP)) + .readEntry(QStringLiteral("measurement_units"), QStringLiteral("cm")); + const int idx = ui.comboMeasurementUnits->findData(saved); + ui.comboMeasurementUnits->setCurrentIndex(idx >= 0 ? idx : 0); + } + + applyDistanceLabelFormatters(); } void BreezyDesktopEffectConfig::updateUiFromDefaultConfig() { ui.shortcutsEditor->allDefault(); + + if (ui.comboMeasurementUnits) { + QSignalBlocker b(ui.comboMeasurementUnits); + const int idx = ui.comboMeasurementUnits->findData(QStringLiteral("cm")); + ui.comboMeasurementUnits->setCurrentIndex(idx >= 0 ? idx : 0); + } + + applyDistanceLabelFormatters(); } void BreezyDesktopEffectConfig::updateUnmanagedState() @@ -648,6 +699,11 @@ void BreezyDesktopEffectConfig::pollDriverState() auto stateJson = stateJsonOpt.value(); m_connectedDeviceBrand = stateJson.value(QStringLiteral("connected_device_brand")).toString(); m_connectedDeviceModel = stateJson.value(QStringLiteral("connected_device_model")).toString(); + m_connectedDeviceFullDistanceCm = stateJson.value(QStringLiteral("connected_device_full_distance_cm")).toDouble(0.0); + m_connectedDeviceFullSizeCm = stateJson.value(QStringLiteral("connected_device_full_size_cm")).toDouble(0.0); + m_connectedDevicePoseHasPosition = stateJson.value(QStringLiteral("connected_device_pose_has_position")).toBool(false); + + applyDistanceLabelFormatters(); const bool smoothFollow = smoothFollowEnabled(stateJsonOpt); if (ui.SmoothFollowEnabled->isChecked() != smoothFollow) { @@ -711,6 +767,46 @@ void BreezyDesktopEffectConfig::pollDriverState() m_driverStateInitialized = true; } +QString BreezyDesktopEffectConfig::measurementUnitsFromUi() const +{ + if (!ui.comboMeasurementUnits) return QStringLiteral("cm"); + const QString v = ui.comboMeasurementUnits->currentData().toString(); + if (v == QLatin1String("in")) return QStringLiteral("in"); + return QStringLiteral("cm"); +} + +void BreezyDesktopEffectConfig::applyDistanceLabelFormatters() +{ + auto *focused = ui.kcfg_FocusedDisplayDistance; + auto *all = ui.kcfg_AllDisplaysDistance; + if (!focused || !all) return; + + // Only apply the unit conversion labels when the driver reports positional tracking. + if (!m_connectedDevicePoseHasPosition) { + focused->clearValueToDisplayStringFn(); + all->clearValueToDisplayStringFn(); + return; + } + + const double fullCm = static_cast(m_connectedDeviceFullDistanceCm); + const QString units = measurementUnitsFromUi(); + const QLocale loc; + + LabeledSlider::ValueToDisplayStringFn fn = [fullCm, units, loc](int raw) -> QString { + if (fullCm <= 0.0) return QString(); + const double ratio = static_cast(raw) / 100.0; // slider uses a 2-decimal fixed-point scale + const double cm = ratio * fullCm; + if (units == QLatin1String("in")) { + const double inches = cm / 2.54; + return loc.toString(inches, 'f', 1) + QStringLiteral(" in"); + } + return loc.toString(cm, 'f', 0) + QStringLiteral(" cm"); + }; + + focused->setValueToDisplayStringFn(fn); + all->setValueToDisplayStringFn(fn); +} + double BreezyDesktopEffectConfig::neckSaverHorizontalMultiplier(std::optional configJsonOpt) { if (!configJsonOpt) return 1.0; diff --git a/kwin/src/kcm/breezydesktopeffectkcm.h b/kwin/src/kcm/breezydesktopeffectkcm.h index 27ec9cd..9e1cb90 100644 --- a/kwin/src/kcm/breezydesktopeffectkcm.h +++ b/kwin/src/kcm/breezydesktopeffectkcm.h @@ -28,6 +28,9 @@ public Q_SLOTS: void defaults() override; private: + QString measurementUnitsFromUi() const; + void applyDistanceLabelFormatters(); + void updateDriverEnabled(); void updateMultitapEnabled(); void updateSmoothFollowEnabled(); @@ -73,8 +76,9 @@ private: int m_smoothFollowThreshold = 1; QString m_connectedDeviceBrand; QString m_connectedDeviceModel; - int m_connectedDeviceFullDistanceCm = 0; - int m_connectedDeviceFullSizeCm = 0; + float m_connectedDeviceFullDistanceCm = 0.0; + float m_connectedDeviceFullSizeCm = 0.0; + bool m_connectedDevicePoseHasPosition = false; QTimer m_statePollTimer; // periodic driver state polling QTimer m_virtualDisplayPollTimer; // periodic virtual display list polling bool m_licenseLoading = false; diff --git a/kwin/src/kcm/breezydesktopeffectkcm.ui b/kwin/src/kcm/breezydesktopeffectkcm.ui index 5df95ad..815d884 100644 --- a/kwin/src/kcm/breezydesktopeffectkcm.ui +++ b/kwin/src/kcm/breezydesktopeffectkcm.ui @@ -159,11 +159,14 @@ + + 2 + QSlider::NoTicks - 10 + 5 25 @@ -631,6 +634,17 @@ + + + + Measurement units: + + + + + + + diff --git a/kwin/src/kcm/labeledslider.h b/kwin/src/kcm/labeledslider.h index d08190b..1a539bb 100644 --- a/kwin/src/kcm/labeledslider.h +++ b/kwin/src/kcm/labeledslider.h @@ -5,6 +5,7 @@ #include #include #include // for std::max +#include #include #include #include @@ -33,6 +34,8 @@ class LabeledSlider : public QSlider { // Example: minimum=0, tickInterval=20, tickStartOffset=10 -> labels at 10,30,50,... Q_PROPERTY(int tickStartOffset READ tickStartOffset WRITE setTickStartOffset) public: + using ValueToDisplayStringFn = std::function; + explicit LabeledSlider(QWidget *parent = nullptr) : QSlider(Qt::Horizontal, parent) { @@ -74,6 +77,24 @@ public: QMap valueTexts() const { return m_valueTexts; } + // Optional custom formatter for displayed values. + // If set, it is consulted for values without an explicit setValueText() override. + // Returning a null QString (QString()) falls back to the built-in formatting. + void setValueToDisplayStringFn(ValueToDisplayStringFn fn) { + m_valueToDisplayStringFn = std::move(fn); + updateGeometry(); + update(); + } + + void clearValueToDisplayStringFn() { + if (!m_valueToDisplayStringFn) return; + m_valueToDisplayStringFn = nullptr; + updateGeometry(); + update(); + } + + bool hasValueToDisplayStringFn() const { return static_cast(m_valueToDisplayStringFn); } + int decimalShift() const { return m_decimalShift; } void setDecimalShift(int shift) { // clamp to sensible range @@ -193,6 +214,14 @@ private: if (it != m_valueTexts.constEnd()) { return *it; } + + if (m_valueToDisplayStringFn) { + QString formatted = m_valueToDisplayStringFn(raw); + if (!formatted.isNull()) { + return formatted; + } + } + if (m_decimalShift == 0) { return QString::number(raw); } @@ -210,6 +239,7 @@ private: int m_decimalShift = 0; // display-only decimal shift int m_tickStartOffset = 0; // label positions start offset relative to minimum QMap m_valueTexts; // optional text overrides for specific values + ValueToDisplayStringFn m_valueToDisplayStringFn; // optional custom formatter private: int labelInterval() const { int ti = tickInterval();