diff --git a/README.md b/README.md
index 1d97d9709..d8034944d 100644
--- a/README.md
+++ b/README.md
@@ -40,7 +40,7 @@ handle other devices with Prismatik such as Adalight, Ardulight, or even Alienwa

-* Mood Lamps
+* Scriptable Mood Lamps ([see here how](/Software/res/moodlamps/))

@@ -104,6 +104,7 @@ handle other devices with Prismatik such as Adalight, Ardulight, or even Alienwa
You will need the following packages, usually all of them are in distro's repository:
* qt5-default
* libqt5serialport5-dev
+* qtdeclarative5-dev
* build-essential
* libgtk2.0-dev
* libusb-1.0-0-dev
diff --git a/Software/res/LightpackResources.qrc b/Software/res/LightpackResources.qrc
index 5978ad618..5fe13f260 100644
--- a/Software/res/LightpackResources.qrc
+++ b/Software/res/LightpackResources.qrc
@@ -39,5 +39,12 @@
icons/Prismatik-pixmap.png
text/cast.html
icons/persist.png
+
+ moodlamps/static.mjs
+ moodlamps/rgb_is_life.mjs
+ moodlamps/rgb_cycle.mjs
+ moodlamps/breathing.mjs
+ moodlamps/fire.mjs
+ moodlamps/random_noise.mjs
diff --git a/Software/res/moodlamps/README.md b/Software/res/moodlamps/README.md
new file mode 100644
index 000000000..b9cadb756
--- /dev/null
+++ b/Software/res/moodlamps/README.md
@@ -0,0 +1,144 @@
+Moodlamp scripting
+---------
+Prismatik uses [QJSEngine](https://doc.qt.io/qt-5/qjsengine.html) for parsing and running moodlamp scripts, so if you are familiar with JavaScript you'll be fine.
+
+The scripts live in your user's Prismatik folder:
+- Windows: `C:\Users\\Prismatik\moodlamps`
+- macOS & Linux: `~/.Prismatik/moodlamps`
+
+File name should obey these rules:
+- allowed characters `a-z`, `0-9` and `_` (underscore)
+- `.mjs` extension
+- unique name
+
+For ex: `my_cool_lamp.mjs`
+
+
+**The bare minimum template:**
+```js
+export const name = "your lamp name here"
+export const interval = 50 // frame time in ms
+
+/**
+ * @param {integer} baseColor integer RGB value of the base color that is selected in Prismatik
+ * @param {array} colors array of RGB integers representing your LEDs
+ * @return {array} array of new RGB integers representing your LEDs
+ */
+export function tick(baseColor, colors)
+{
+ for (let i = 0; i < colors.length; i++)
+ {
+ // your code here, for ex:
+ colors[i] = baseColor // will set all LEDs to the color you set in Prismatik
+ }
+
+ return colors
+}
+```
+
+
+**More examples:**
+
+```js
+export const name = "your lamp name here"
+export const interval = 50 // frame time in ms
+
+// if you need a global constant
+const myBlueConst = 0x0000FF // blue color
+
+// if you need a variable that persists between frames (a counter for ex), declare it here
+let someVariable = 0
+
+export function tick(baseColor, colors)
+{
+ for (let i = 0; i < colors.length; i++)
+ {
+ // to set all your LEDs to white
+ colors[i] = 0xFFFFFF
+ // or to blue
+ colors[i] = myBlueConst
+
+ // random colors for each LED
+ colors[i] = Math.round(Math.random() * 0xFFFFFF)
+ }
+
+ someVariable++ // counter from above
+
+ return colors
+}
+
+```
+
+**Recommended way:**
+
+For convenience, Prismatik partially exposes `QColor` class, see [here for documentation](https://doc.qt.io/qt-5/qcolor.html) and [here for exposed methods](/Software/src/QColorMetaWrapper.hpp).
+Prismatik uses `QColor` internally so for best compatibility it's better to use this class.
+Raw RGB values should work, but use them at your own risk.
+
+Example from `rgb_is_life.js`:
+```js
+export const name = "RGB is Life"
+export const interval = 33 // ms
+
+const speed = 1.5
+
+let m_frames = 0
+
+export function tick(baseColor, colors)
+{
+ // transform base RGB value into QColor
+ baseColor = new QColor(baseColor)
+
+ // RGB transition math
+ const degrees = 360.0 / colors.length
+ const step = speed * m_frames++
+ for (let i = 0; i < colors.length; i++)
+ {
+ // transform current color for a given LED into QColor
+ let color = new QColor(colors[i])
+
+ // increment baseColor's hue value for that smooth RGB rotation
+ const hue = baseColor.hslHue() + degrees * i + step
+
+ // set new hue while preserving baseColor's saturation and lightness
+ color.setHsl(hue, baseColor.hslSaturation(), baseColor.lightness())
+
+ // assign the new color
+ // colors[] has to contain .rgb() values
+ colors[i] = color.rgb()
+ }
+ if (step > 360)
+ m_frames = 0
+
+ return colors
+}
+```
+
+
+To see your changes use the reload button or relaunch the app.
+
+
+**Warnig:** scripts bundled with Prismatik are overwritten on launch, if you want to customize Prismatik's scripts, make sure to make a copy of them and modify those.
+
+
+**Logging and console**
+
+If you enable logs in Prismatik your JS errors will be logged to
+- Windows: `C:\Users\\Prismatik\Logs`
+- macOS & Linux: `~/.Prismatik/Logs`
+
+
+You can also enable the [Console API](https://doc.qt.io/qt-5/qtquick-debugging.html#console-api)
+
+```js
+export const enableConsole = true
+...
+export function tick()
+{
+ ...
+ console.log("some debug")
+ ...
+}
+```
+
+Don't forget to remove when you are done
diff --git a/Software/res/moodlamps/breathing.mjs b/Software/res/moodlamps/breathing.mjs
new file mode 100644
index 000000000..6cc54f8e4
--- /dev/null
+++ b/Software/res/moodlamps/breathing.mjs
@@ -0,0 +1,22 @@
+export const name = "Breathing"
+export const interval = 40 // ms
+
+let lightnessInc = -1
+let lightness = -1
+
+export function tick(baseColor, colors)
+{
+ baseColor = new QColor(baseColor)
+ if (lightness == -1)
+ lightness = baseColor.lightness()
+ let color = new QColor()
+ if (lightness <= baseColor.lightness() / 2 && lightnessInc == -1)
+ lightnessInc = 1
+ else if (lightness >= baseColor.lightness() - 1 && lightnessInc == 1)
+ lightnessInc = -1
+
+ lightness += lightnessInc
+
+ color.setHsl(baseColor.hslHue(), baseColor.hslSaturation(), lightness)
+ return colors.fill(color.rgb())
+}
diff --git a/Software/res/moodlamps/fire.mjs b/Software/res/moodlamps/fire.mjs
new file mode 100644
index 000000000..1fc02f998
--- /dev/null
+++ b/Software/res/moodlamps/fire.mjs
@@ -0,0 +1,74 @@
+export const name = "Fire"
+export const interval = 33 // ms
+
+let m_lightness = []
+let m_center = 0
+
+const Cooling = 8
+const SparkMax = 160
+const SparkMin = 100
+const DefaultLightness = 127
+
+function randomBounded(max) {
+ return Math.floor(Math.random() * Math.floor(max))
+}
+
+export function tick(baseColor, colors)
+{
+ if (colors.length < 2)
+ return colors
+
+ if (colors.length > m_lightness.length) {
+ for (let i = m_lightness.length; i < colors.length; ++i)
+ m_lightness.push(DefaultLightness)
+ }
+
+ // heavily inspired by FastLED Fire2012 demo
+ // https://github.com/FastLED/FastLED/blob/master/examples/Fire2012/Fire2012.ino
+
+ const centerMax = Math.round(colors.length / 4)
+ const middleLed = Math.floor(colors.length / 2)
+ const sparkCount = Math.round(colors.length / 12)
+
+ m_center += randomBounded(2) ? -1 : 1
+ m_center = Math.max(-centerMax, Math.min(centerMax, m_center))
+
+ for (let i = 0; i < middleLed + m_center; ++i) {
+ const minLightnessReduction = Cooling * Math.pow(i / (middleLed + m_center), 3)
+ const maxLightnessReduction = minLightnessReduction * 2 // useless?
+ const lightnessReduction = minLightnessReduction + randomBounded(Math.max(1, maxLightnessReduction - minLightnessReduction)) + Cooling / 3
+ m_lightness[i] = Math.round(Math.max(0, m_lightness[i] - lightnessReduction)) % 256
+ }
+
+ for (let i = colors.length - 1; i >= middleLed + m_center; --i) {
+ const minLightnessReduction = Cooling * Math.pow((colors.length - 1 - i) / (middleLed - m_center), 3)
+ const maxLightnessReduction = minLightnessReduction * 2 // useless?
+ const lightnessReduction = minLightnessReduction + randomBounded(Math.max(1, maxLightnessReduction - minLightnessReduction)) + Cooling / 3
+ m_lightness[i] = Math.round(Math.max(0, m_lightness[i] - lightnessReduction)) % 256
+ }
+
+ for (let k = middleLed + m_center; k > 1; --k)
+ m_lightness[k] = Math.round((m_lightness[k - 1] + m_lightness[k - 2] * 2) / 3) % 256
+
+ for (let k = middleLed + m_center; k < colors.length - 2; ++k)
+ m_lightness[k] = Math.round((m_lightness[k + 1] + m_lightness[k + 2] * 2) / 3) % 256
+
+
+ if (randomBounded(2) == 0) {
+ const y = randomBounded(Math.max(1, sparkCount))
+ m_lightness[y] = Math.max(SparkMax, m_lightness[y] + (SparkMin + randomBounded(SparkMax - SparkMin))) % 256
+ }
+ if (randomBounded(2) == 0) {
+ const z = colors.length - 1 - randomBounded(Math.max(1, sparkCount))
+ m_lightness[z] = Math.max(SparkMax, m_lightness[z] + (SparkMin + randomBounded(SparkMax - SparkMin))) % 256
+ }
+
+ let color = new QColor(baseColor)
+ for (let i = 0; i < colors.length; i++)
+ {
+ color.setHsl(color.hslHue(), color.hslSaturation(), m_lightness[i])
+ colors[i] = color.rgb()
+ }
+
+ return colors
+}
diff --git a/Software/res/moodlamps/random_noise.mjs b/Software/res/moodlamps/random_noise.mjs
new file mode 100644
index 000000000..362277b76
--- /dev/null
+++ b/Software/res/moodlamps/random_noise.mjs
@@ -0,0 +1,12 @@
+export const name = "Random noise"
+export const interval = 50 // ms
+
+export function tick(baseColor, colors)
+{
+ let color = new QColor(baseColor)
+ for (let i = 0; i < colors.length; i++) {
+ color.setRgb(Math.random() * 0xFFFFFF)
+ colors[i] = color.rgb()
+ }
+ return colors
+}
diff --git a/Software/res/moodlamps/rgb_cycle.mjs b/Software/res/moodlamps/rgb_cycle.mjs
new file mode 100644
index 000000000..159fbfd8d
--- /dev/null
+++ b/Software/res/moodlamps/rgb_cycle.mjs
@@ -0,0 +1,15 @@
+export const name = "RGB Cycle"
+export const interval = 33 // ms
+
+let degrees = 0
+
+export function tick(baseColor, colors)
+{
+ baseColor = new QColor(baseColor)
+ baseColor.setHsl(baseColor.hslHue() + degrees++, baseColor.hslSaturation(), baseColor.lightness())
+
+ if (degrees > 360)
+ degrees = 0
+
+ return colors.fill(baseColor.rgb())
+}
diff --git a/Software/res/moodlamps/rgb_is_life.mjs b/Software/res/moodlamps/rgb_is_life.mjs
new file mode 100644
index 000000000..9e8ae8797
--- /dev/null
+++ b/Software/res/moodlamps/rgb_is_life.mjs
@@ -0,0 +1,22 @@
+export const name = "RGB is Life"
+export const interval = 33 // ms
+const speed = 1.5
+
+let m_frames = 0
+
+export default function tick(baseColor, colors)
+{
+ baseColor = new QColor(baseColor)
+ const degrees = 360.0 / colors.length
+ const step = speed * m_frames++
+ for (let i = 0; i < colors.length; i++)
+ {
+ let color = new QColor(colors[i])
+ color.setHsl(baseColor.hslHue() + degrees * i + step, baseColor.hslSaturation(), baseColor.lightness())
+ colors[i] = color.rgb()
+ }
+ if (step > 360)
+ m_frames = 0
+
+ return colors
+}
diff --git a/Software/res/moodlamps/static.mjs b/Software/res/moodlamps/static.mjs
new file mode 100644
index 000000000..f1dd4c00e
--- /dev/null
+++ b/Software/res/moodlamps/static.mjs
@@ -0,0 +1,7 @@
+export const name = "Static (default)"
+export const interval = 50 // ms
+
+export function tick(baseColor, colors)
+{
+ return colors.fill(baseColor)
+}
diff --git a/Software/src/LightpackApplication.cpp b/Software/src/LightpackApplication.cpp
index 5523d2bb6..f778a6b32 100644
--- a/Software/src/LightpackApplication.cpp
+++ b/Software/src/LightpackApplication.cpp
@@ -699,8 +699,7 @@ void LightpackApplication::initGrabManager()
DEBUG_LOW_LEVEL << Q_FUNC_INFO;
m_grabManager = new GrabManager(NULL);
- m_moodlampManager = new MoodLampManager(NULL);
- m_moodlampManager->initFromSettings();
+ m_moodlampManager = new MoodLampManager(m_applicationDirPath);
#ifdef SOUNDVIZ_SUPPORT
m_soundManager = SoundManagerBase::create(m_settingsWindow->winId(), NULL);
@@ -727,7 +726,7 @@ void LightpackApplication::initGrabManager()
connect(settings(), SIGNAL(moodLampColorChanged(QColor)), m_moodlampManager, SLOT(setCurrentColor(QColor)));
connect(settings(), SIGNAL(moodLampSpeedChanged(int)), m_moodlampManager, SLOT(setLiquidModeSpeed(int)));
connect(settings(), SIGNAL(moodLampLiquidModeChanged(bool)), m_moodlampManager, SLOT(setLiquidMode(bool)));
- connect(settings(), SIGNAL(moodLampLampChanged(int)), m_moodlampManager, SLOT(setCurrentLamp(int)));
+ connect(settings(), SIGNAL(moodLampLampChanged(const QString&)), m_moodlampManager, SLOT(setCurrentLamp(const QString&)));
connect(settings(), SIGNAL(sendDataOnlyIfColorsChangesChanged(bool)), m_moodlampManager, SLOT(setSendDataOnlyIfColorsChanged(bool)));
#ifdef SOUNDVIZ_SUPPORT
@@ -774,8 +773,9 @@ void LightpackApplication::initGrabManager()
}
#endif
- connect(m_settingsWindow, SIGNAL(requestMoodLampLamps()), m_moodlampManager, SLOT(requestLampList()));
- connect(m_moodlampManager, SIGNAL(lampList(const QList &, int)), m_settingsWindow, SLOT(updateAvailableMoodLampLamps(const QList &, int)));
+ connect(m_settingsWindow, SIGNAL(requestMoodLampLamps(bool)), m_moodlampManager, SLOT(requestLampList(bool)));
+ connect(m_moodlampManager, SIGNAL(lampList(const QList &)), m_settingsWindow, SLOT(updateAvailableMoodLampLamps(const QList &)));
+ connect(m_settingsWindow, SIGNAL(openMoodLampScriptDir()), this, SLOT(openMoodLampScriptDir()));
}
connect(m_grabManager, SIGNAL(ambilightTimeOfUpdatingColors(double)), m_pluginInterface, SLOT(refreshAmbilightEvaluated(double)));
@@ -793,6 +793,11 @@ void LightpackApplication::initGrabManager()
#endif
}
+void LightpackApplication::openMoodLampScriptDir()
+{
+ QDesktopServices::openUrl(QUrl(m_moodlampManager->scriptDir()));
+}
+
void LightpackApplication::commitData(QSessionManager &sessionManager)
{
Q_UNUSED(sessionManager);
diff --git a/Software/src/LightpackApplication.hpp b/Software/src/LightpackApplication.hpp
index 423546cea..9a1172dbf 100644
--- a/Software/src/LightpackApplication.hpp
+++ b/Software/src/LightpackApplication.hpp
@@ -83,6 +83,7 @@ private slots:
void onFocusChanged(QWidget *, QWidget *);
void quitFromWizard(int result);
void onSessionChange(int change);
+ void openMoodLampScriptDir();
private:
void processCommandLineArguments();
diff --git a/Software/src/LiquidColorGenerator.cpp b/Software/src/LiquidColorGenerator.cpp
index 76cad8144..9eab20ad4 100644
--- a/Software/src/LiquidColorGenerator.cpp
+++ b/Software/src/LiquidColorGenerator.cpp
@@ -51,13 +51,16 @@ LiquidColorGenerator::LiquidColorGenerator(QObject *parent) : QObject(parent)
connect(&m_timer, SIGNAL(timeout()), this, SLOT(updateColor()));
}
-void LiquidColorGenerator::start()
+void LiquidColorGenerator::start(const QColor& color)
{
DEBUG_LOW_LEVEL << Q_FUNC_INFO;
m_isEnabled = true;
reset();
+ m_red = color.red();
+ m_green = color.green();
+ m_blue = color.blue();
updateColor();
}
diff --git a/Software/src/LiquidColorGenerator.hpp b/Software/src/LiquidColorGenerator.hpp
index 63cf19dc1..751819ad0 100644
--- a/Software/src/LiquidColorGenerator.hpp
+++ b/Software/src/LiquidColorGenerator.hpp
@@ -42,14 +42,14 @@ class LiquidColorGenerator : public QObject
void updateColor(QColor color);
public:
- void start();
+ void start(const QColor& color = Qt::black);
void stop();
QColor current();
void reset();
public slots:
void setSpeed(int value);
-
+
private:
int generateDelay();
QColor generateColor();
diff --git a/Software/src/MoodLamp.cpp b/Software/src/MoodLamp.cpp
deleted file mode 100644
index 29640c3ec..000000000
--- a/Software/src/MoodLamp.cpp
+++ /dev/null
@@ -1,227 +0,0 @@
-/*
- * MoodLamp.cpp
- *
- * Created on: 11.12.2011
- * Project: Lightpack
- *
- * Copyright (c) 2011 Mike Shatohin, mikeshatohin [at] gmail.com
- *
- * Lightpack a USB content-driving ambient lighting system
- *
- * Lightpack 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 2 of the License, or
- * (at your option) any later version.
- *
- * Lightpack is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- *
- */
-
-#include
-#include
-#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
-#include
-#endif
-#include "MoodLamp.hpp"
-#include "Settings.hpp"
-
-// Needed to get around the fact that the lamps are macros
-class QRandomGeneratorShim {
-
-#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
- QRandomGenerator m_rnd;
-public:
- void seed(quint32 seed) {
- m_rnd.seed(seed);
- }
-
- int bounded(int highest) {
- return m_rnd.bounded(highest);
- }
-
- int bounded(int lowest, int highest) {
- return m_rnd.bounded(lowest, highest);
- }
-#else
-public:
- void seed(quint32 seed) {
- qsrand(seed);
- }
-
- int bounded(int highest) {
- return qrand() % highest;
- }
-
- int bounded(int lowest, int highest) {
- return lowest + (qrand() % (highest - lowest + 1));
- }
-#endif
-
-};
- /*
- _OBJ_NAME_ : class name prefix
- _LABEL_ : name string to be displayed
- _BODY_ : class declaration body
- */
-#define DECLARE_LAMP(_OBJ_NAME_,_LABEL_,_BODY_) \
-class _OBJ_NAME_ ## MoodLamp : public MoodLampBase \
-{\
-public:\
-_OBJ_NAME_ ## MoodLamp() : MoodLampBase() {};\
-~_OBJ_NAME_ ## MoodLamp() = default;\
-static const char* const name() { return _LABEL_; };\
-static MoodLampBase* create() { return new _OBJ_NAME_ ## MoodLamp(); };\
-\
-_BODY_\
-};\
-struct _OBJ_NAME_ ## Register {\
-_OBJ_NAME_ ## Register(){\
- g_moodLampMap.insert(lampID, MoodLampLampInfo(_OBJ_NAME_ ## MoodLamp::name(), _OBJ_NAME_ ## MoodLamp::create, lampID));\
- ++lampID;\
-}\
-};\
-_OBJ_NAME_ ## Register _OBJ_NAME_ ## Reg;
-
-using namespace SettingsScope;
-
-namespace {
- static QMap g_moodLampMap;
- static int lampID = 0;
-}
-
-MoodLampBase* MoodLampBase::createWithID(const int id) {
- QMap::const_iterator i = g_moodLampMap.find(id);
- if (i != g_moodLampMap.end())
- return i.value().factory();
- qWarning() << Q_FUNC_INFO << "failed to find mood lamp ID: " << id;
- return nullptr;
-}
-
-void MoodLampBase::populateNameList(QList& list, int& recommended)
-{
- list = g_moodLampMap.values();
-
- if (list.size() > 0)
- recommended = list[0].id;
-}
-
-DECLARE_LAMP(Static, "Static (default)",
-public:
- int interval() const { return 50; };
-
- bool shine(const QColor& newColor, QList& colors)
- {
- bool changed = false;
- for (int i = 0; i < colors.size(); i++)
- {
- QRgb rgb = Settings::isLedEnabled(i) ? newColor.rgb() : 0;
- changed = changed || (colors[i] != rgb);
- colors[i] = rgb;
- }
- return changed;
- };
-);
-
-DECLARE_LAMP(Fire, "Fire",
-public:
- void init() {
- m_rnd.seed(QTime(0, 0, 0).secsTo(QTime::currentTime()));
- };
-
- bool shine(const QColor& newColor, QList& colors) {
- if (colors.size() < 2)
- return false;
-
- if (colors.size() > m_lightness.size()) {
- const size_t oldSize = m_lightness.size();
- m_lightness.reserve(colors.size());
- for (size_t i = oldSize; i < colors.size(); ++i)
- m_lightness << 0;
- }
-
- // heavily inspired by FastLED Fire2012 demo
- // https://github.com/FastLED/FastLED/blob/master/examples/Fire2012/Fire2012.ino
- const int centerMax = colors.size() / 4;
- const int middleLed = std::floor(colors.size() / 2);
- const int sparkCount = colors.size() / 12;
-
- m_center += m_rnd.bounded(2) ? -1 : 1;
- m_center = std::max(-centerMax, std::min(centerMax, m_center));
-
- for (int i = 0; i < middleLed + m_center; ++i) {
- const int minLightnessReduction = Cooling * std::pow((double)i / (middleLed + m_center), 3);
- const int maxLightnessReduction = minLightnessReduction * 2;
- const int lightnessReduction = minLightnessReduction + m_rnd.bounded(std::max(1, maxLightnessReduction - minLightnessReduction)) + Cooling / 3;
- m_lightness[i] = std::max(0, m_lightness[i] - lightnessReduction);
- }
-
- for (int i = colors.size() - 1; i >= middleLed + m_center; --i) {
- const int minLightnessReduction = Cooling * std::pow((double)(colors.size() - 1 - i) / (middleLed - m_center), 3);
- const int maxLightnessReduction = minLightnessReduction * 2;
- const int lightnessReduction = minLightnessReduction + m_rnd.bounded(std::max(1, maxLightnessReduction - minLightnessReduction)) + Cooling / 3;
- m_lightness[i] = std::max(0, m_lightness[i] - lightnessReduction);
- }
-
-
- for (int k = middleLed + m_center; k > 1; --k)
- m_lightness[k] = (m_lightness[k - 1] + m_lightness[k - 2] * 2) / 3;
-
- for (int k = middleLed + m_center; k < colors.size() - 2; ++k)
- m_lightness[k] = (m_lightness[k + 1] + m_lightness[k + 2] * 2) / 3;
-
-
- if (m_rnd.bounded(2) == 0) {
- int y = m_rnd.bounded(std::max(1, sparkCount));
- m_lightness[y] = std::max(SparkMax, (int)m_lightness[y] + (SparkMin + m_rnd.bounded(SparkMax - SparkMin)));
- }
- if (m_rnd.bounded(2) == 0) {
- int z = colors.size() - 1 - m_rnd.bounded(std::max(1, sparkCount));
- m_lightness[z] = std::max(SparkMax, (int)m_lightness[z] + (SparkMin + m_rnd.bounded(SparkMax - SparkMin)));
- }
-
- for (int i = 0; i < colors.size(); i++)
- {
- QColor color(newColor);
- color.setHsl(color.hue(), color.saturation(), m_lightness[i]);
- colors[i] = Settings::isLedEnabled(i) ? color.rgb() : 0;
- }
- return true;
- };
-private:
- QRandomGeneratorShim m_rnd;
- QList m_lightness;
- int m_center{ 0 };
-
- const size_t Cooling = 8;
- const int SparkMax = 160;
- const int SparkMin = 100;
-);
-
-DECLARE_LAMP(RGBLife, "RGB is Life",
-public:
- bool shine(const QColor& newColor, QList& colors)
- {
- bool changed = false;
- const int degrees = 360 / colors.size();
- const int step = Speed * m_frames++;
- for (int i = 0; i < colors.size(); i++)
- {
- QColor color(newColor);
- color.setHsl(color.hue() + degrees * i + step, color.saturation(), color.lightness());
- QRgb rgb = Settings::isLedEnabled(i) ? color.rgb() : 0;
- changed = changed || (colors[i] != rgb);
- colors[i] = rgb;
- }
- if (step > 360)
- m_frames = 0;
- return changed;
- };
-private:
- const double Speed = 1.5;
-);
diff --git a/Software/src/MoodLamp.hpp b/Software/src/MoodLamp.hpp
deleted file mode 100644
index 4d4947feb..000000000
--- a/Software/src/MoodLamp.hpp
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * MoodLamp.hpp
- *
- * Created on: 11.12.2011
- * Project: Lightpack
- *
- * Copyright (c) 2011 Mike Shatohin, mikeshatohin [at] gmail.com
- *
- * Lightpack a USB content-driving ambient lighting system
- *
- * Lightpack 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 2 of the License, or
- * (at your option) any later version.
- *
- * Lightpack is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- *
- */
-
-#pragma once
-
-#include
-#include
-
-class MoodLampBase;
-
-typedef MoodLampBase* (*LampFactory)();
-
-struct MoodLampLampInfo {
- MoodLampLampInfo() { this->name = ""; this->id = -1; this->factory = nullptr; }
- MoodLampLampInfo(QString name, LampFactory factory, int id) { this->name = name; this->id = id; this->factory = factory; }
- QString name;
- LampFactory factory;
- int id;
-};
-Q_DECLARE_METATYPE(MoodLampLampInfo);
-
-class MoodLampBase
-{
-public:
- MoodLampBase() { init(); };
- virtual ~MoodLampBase() = default;
-
- static const char* const name() { return "NO_NAME"; };
- static MoodLampBase* create() { Q_ASSERT_X(false, "MoodLampBase::create()", "not implemented"); return nullptr; };
- static MoodLampBase* createWithID(const int id);
- static void populateNameList(QList& list, int& recommended);
-
- virtual void init() {};
- virtual int interval() const { return DefaultInterval; };
- virtual bool shine(const QColor& newColor, QList& colors) = 0;
-protected:
- size_t m_frames{ 0 };
-private:
- const int DefaultInterval = 33;
-};
diff --git a/Software/src/MoodLampManager.cpp b/Software/src/MoodLampManager.cpp
index 1b0331d17..1e567ab32 100644
--- a/Software/src/MoodLampManager.cpp
+++ b/Software/src/MoodLampManager.cpp
@@ -27,24 +27,29 @@
#include "MoodLampManager.hpp"
#include "PrismatikMath.hpp"
#include "Settings.hpp"
+#include "QColorMetaWrapper.hpp"
#include
-#include "MoodLamp.hpp"
+#include
using namespace SettingsScope;
-MoodLampManager::MoodLampManager(QObject *parent) : QObject(parent)
+MoodLampManager::MoodLampManager(const QString& appDir, QObject *parent) : QObject(parent)
{
m_isMoodLampEnabled = false;
m_timer.setTimerType(Qt::PreciseTimer);
+
+ m_scriptDir = installScripts(appDir);
+ m_jsEngine = loadScripts();
+
connect(&m_timer, SIGNAL(timeout()), this, SLOT(updateColors()));
initFromSettings();
}
MoodLampManager::~MoodLampManager()
{
- if (m_lamp)
- delete m_lamp;
+ m_jsEngine->collectGarbage();
+ delete m_jsEngine;
}
void MoodLampManager::start(bool isEnabled)
@@ -52,7 +57,7 @@ void MoodLampManager::start(bool isEnabled)
DEBUG_LOW_LEVEL << Q_FUNC_INFO << isEnabled;
m_isMoodLampEnabled = isEnabled;
-
+
if (m_isMoodLampEnabled)
{
// This is usable if start is called after API unlock, and we should force set current color
@@ -60,12 +65,12 @@ void MoodLampManager::start(bool isEnabled)
}
if (m_isMoodLampEnabled && m_isLiquidMode)
- m_generator.start();
+ m_generator.start(m_currentColor);
else
m_generator.stop();
- if (m_isMoodLampEnabled && m_lamp)
- m_timer.start(m_lamp->interval());
+ if (m_isMoodLampEnabled && !m_jsLamp.isUndefined())
+ m_timer.start(m_jsLamp.property("interval").toUInt());
else
m_timer.stop();
}
@@ -85,7 +90,7 @@ void MoodLampManager::setLiquidMode(bool state)
m_isLiquidMode = state;
emit moodlampFrametime(1000); // reset FPS to 1
if (m_isLiquidMode && m_isMoodLampEnabled)
- m_generator.start();
+ m_generator.start(m_currentColor);
else {
m_generator.stop();
if (m_isMoodLampEnabled)
@@ -136,39 +141,91 @@ void MoodLampManager::initFromSettings()
setCurrentLamp(Settings::getMoodLampLamp());
}
-void MoodLampManager::setCurrentLamp(const int id)
+void MoodLampManager::setCurrentLamp(const QString& moduleName)
{
m_timer.stop();
- if (m_lamp) {
- delete m_lamp;
- m_lamp = nullptr;
+ if (!m_jsLamp.isUndefined()) {
+ m_jsLamp = QJSValue();
+ m_jsEngine->collectGarbage();
+ }
+
+ if (!m_lamps.contains(moduleName)) {
+ qWarning() << Q_FUNC_INFO << moduleName << "unknown lamp";
+ return;
}
- m_lamp = MoodLampBase::createWithID(id);
+ const MoodLampLampInfo& lampInfo = m_lamps[moduleName];
+
+ const QJSValue& newLamp = m_jsEngine->importModule(lampInfo.modulePath);
+ if (newLamp.isError()) {
+ qWarning() << Q_FUNC_INFO << QString("JS Error in %1:%2 %3")
+ .arg(newLamp.property("fileName").toString())
+ .arg(newLamp.property("lineNumber").toInt())
+ .arg(newLamp.toString());
+ return;
+ }
+ m_lampModuleName = moduleName;
+ m_jsLamp = newLamp;
emit moodlampFrametime(1000); // reset FPS to 1
- if (m_isMoodLampEnabled && m_lamp)
- m_timer.start(m_lamp->interval());
+ if (m_isMoodLampEnabled && !m_jsLamp.isUndefined()) {
+ updateColors(true);
+ m_timer.start(m_jsLamp.property("interval").toUInt());
+ }
}
void MoodLampManager::updateColors(const bool forceUpdate)
{
DEBUG_HIGH_LEVEL << Q_FUNC_INFO << m_isLiquidMode;
- QColor newColor;
+ QColor baseColor;
if (m_isLiquidMode)
- {
- newColor = m_generator.current();
- }
+ baseColor = m_generator.current();
else
- {
- newColor = m_currentColor;
+ baseColor = m_currentColor;
+
+ DEBUG_MID_LEVEL << Q_FUNC_INFO << baseColor.rgb();
+
+ bool changed = false;
+ if (!m_jsLamp.isUndefined()) {
+ QJSValueList args;
+ args << baseColor.rgb();
+
+ QVariantList outColors;
+ outColors.reserve(m_colors.size());
+ for (const QRgb color : m_colors)
+ outColors << color;
+ args << m_jsEngine->toScriptValue(outColors);
+
+ // Qt 5.15+ can auto convert m_colors
+ //args << m_jsEngine.toScriptValue(m_colors);
+
+ const QJSValue& result = m_jsLamp.property("tick").call(args);
+ if (result.isError()) {
+ qWarning() << Q_FUNC_INFO << QString("JS Error in %1:%2 %3")
+ .arg(result.property("fileName").toString())
+ .arg(result.property("lineNumber").toInt())
+ .arg(result.toString());
+ }
+ else if (result.isArray()) {
+ const QVariantList& colors = result.toVariant().toList();
+ for (int i = 0; i < colors.size(); ++i) {
+ const QRgb newColor = Settings::isLedEnabled(i) ? colors[i].toInt() : 0;
+ changed = changed || (m_colors[i] != newColor);
+ m_colors[i] = newColor;
+ }
+ }
+ else
+ qWarning() << Q_FUNC_INFO << m_jsLamp.property("name").toString() << "tick() does not return [rgb1, rgb2, ...]";
+ }
+ else { // fallback to static
+ for (QRgb& color : m_colors) {
+ changed = changed || (color != baseColor.rgb());
+ color = baseColor.rgb();
+ }
}
- DEBUG_MID_LEVEL << Q_FUNC_INFO << newColor.rgb();
-
- bool changed = (m_lamp ? m_lamp->shine(newColor, m_colors) : false);
if (changed || !m_isSendDataOnlyIfColorsChanged || forceUpdate) {
emit updateLedsColors(m_colors);
if (forceUpdate) {
@@ -193,12 +250,84 @@ void MoodLampManager::initColors(int numberOfLeds)
m_colors << 0;
}
-void MoodLampManager::requestLampList()
+void MoodLampManager::requestLampList(const bool reloadScripts = false)
{
- QList list;
- int recommended = 0;
+ DEBUG_LOW_LEVEL << Q_FUNC_INFO << reloadScripts;
+
+ if (reloadScripts) {
+ m_lamps.clear();
+ m_scriptDir.refresh();
+ QJSEngine* oldEngine = m_jsEngine;
+ m_jsEngine = loadScripts();
+ setCurrentLamp(m_lampModuleName);
+ delete oldEngine;
+ }
+ emit lampList(m_lamps.values());
+}
- MoodLampBase::populateNameList(list, recommended);
+QDir MoodLampManager::installScripts(const QString& appDir)
+{
+ const QString moodlampsDirName("moodlamps");
+ QDir installDir(appDir);
+ if (!installDir.exists(moodlampsDirName))
+ installDir.mkdir(moodlampsDirName);
+ installDir.cd(moodlampsDirName);
+
+ const QDir resLampDir(":/" + moodlampsDirName, "*.mjs", QDir::IgnoreCase | QDir::Name, QDir::Files);
+ const QStringList& reslampScriptList = resLampDir.entryList();
+ for (const QString& lampScript : reslampScriptList) {
+ if (QFile::exists(installDir.absoluteFilePath(lampScript)) && !QFile::remove(installDir.absoluteFilePath(lampScript))) {
+ qWarning() << Q_FUNC_INFO << "Could not remove" << lampScript;
+ continue;
+ }
+ if (!QFile::copy(":/" + moodlampsDirName + "/" + lampScript, installDir.absoluteFilePath(lampScript)))
+ qWarning() << Q_FUNC_INFO << "Cound not install" << lampScript;
+ }
+ return installDir;
+}
- emit lampList(list, recommended);
+QJSEngine* MoodLampManager::loadScripts()
+{
+ QJSEngine* jsEngine = new QJSEngine(this);
+ jsEngine->globalObject().setProperty("QColor", jsEngine->newQMetaObject());
+ jsEngine->globalObject().setProperty("QT_VERSION", QT_VERSION);
+
+ const QRegularExpression cleanNameFilter("^[a-z0-9_]+\\.mjs$");
+ const QStringList& lampScriptList = m_scriptDir.entryList().filter(cleanNameFilter);
+ bool enableConsole = false;
+ for (const QString& lampScript : lampScriptList) {
+ const QJSValue& jsModule = jsEngine->importModule(m_scriptDir.filePath(lampScript));
+ if (jsModule.isError()) {
+ qWarning() << Q_FUNC_INFO << lampScript << QString("JS Error in %1:%2 %3")
+ .arg(jsModule.property("fileName").toString())
+ .arg(jsModule.property("lineNumber").toInt())
+ .arg(jsModule.toString());
+ }
+ else if (!jsModule.hasOwnProperty("name") || !jsModule.property("name").isString())
+ qWarning() << Q_FUNC_INFO << lampScript << "does not have \"export const name = string\"";
+ else if (!jsModule.hasOwnProperty("tick") || !jsModule.property("tick").isCallable())
+ qWarning() << Q_FUNC_INFO << lampScript << "does not have \"export function tick(baseColor, colors){...}\"";
+ else if (!jsModule.hasOwnProperty("interval") || !jsModule.property("interval").isNumber() || jsModule.property("interval").toUInt() < 1)
+ qWarning() << Q_FUNC_INFO << lampScript << "does not have \"export const interval = number\" with a valid (> 0) value" ;
+ else {
+ m_lamps.insert(lampScript,
+ MoodLampLampInfo(
+ jsModule.property("name").toString(),
+ lampScript,
+ m_scriptDir.filePath(lampScript)
+ )
+ );
+ enableConsole |= jsModule.hasOwnProperty("enableConsole") && jsModule.property("enableConsole").toBool();
+ }
+ }
+ if (m_lamps.isEmpty())
+ qWarning() << Q_FUNC_INFO << "No moodlamps loaded from " << m_scriptDir.absolutePath() << "; file names have to match" << cleanNameFilter.pattern();
+ if (enableConsole)
+ jsEngine->installExtensions(QJSEngine::ConsoleExtension);
+ return jsEngine;
+}
+
+QString MoodLampManager::scriptDir() const
+{
+ return m_scriptDir.absolutePath();
}
diff --git a/Software/src/MoodLampManager.hpp b/Software/src/MoodLampManager.hpp
index becd39e84..2bb748652 100644
--- a/Software/src/MoodLampManager.hpp
+++ b/Software/src/MoodLampManager.hpp
@@ -29,24 +29,36 @@
#include
#include
#include
+#include
+#include
#include "LiquidColorGenerator.hpp"
-#include "MoodLamp.hpp"
+
+struct MoodLampLampInfo {
+ MoodLampLampInfo() {}
+ MoodLampLampInfo(const QString& name, const QString& moduleName, const QString& modulePath) :
+ name(name), moduleName(moduleName), modulePath(modulePath)
+ {}
+ QString name;
+ QString moduleName;
+ QString modulePath;
+};
+Q_DECLARE_METATYPE(MoodLampLampInfo);
class MoodLampManager : public QObject
{
Q_OBJECT
public:
- explicit MoodLampManager(QObject *parent = 0);
+ explicit MoodLampManager(const QString& appDir, QObject *parent = 0);
~MoodLampManager();
signals:
void updateLedsColors(const QList & colors);
- void lampList(const QList &, int);
+ void lampList(const QList &);
void moodlampFrametime(const double frameMs);
public:
void start(bool isMoodLampEnabled);
-
+ QString scriptDir() const;
// Common options
void reset();
@@ -57,8 +69,8 @@ public slots:
void settingsProfileChanged(const QString &profileName);
void setNumberOfLeds(int value);
void setCurrentColor(QColor color);
- void setCurrentLamp(const int id);
- void requestLampList();
+ void setCurrentLamp(const QString& moduleName);
+ void requestLampList(const bool reloadScripts);
void setSendDataOnlyIfColorsChanged(bool state);
private slots:
@@ -66,10 +78,10 @@ private slots:
private:
void initColors(int numberOfLeds);
+ QDir installScripts(const QString& appDir);
+ QJSEngine* loadScripts();
private:
- MoodLampBase* m_lamp{ nullptr };
-
LiquidColorGenerator m_generator;
QList m_colors;
@@ -81,4 +93,11 @@ private slots:
QTimer m_timer;
QElapsedTimer m_elapsedTimer;
size_t m_frames{ 1 };
+
+ QJSEngine* m_jsEngine;
+ QJSValue m_jsLamp;
+ QDir m_scriptDir;
+ QString m_lampModuleName;
+
+ QMap m_lamps;
};
diff --git a/Software/src/QColorMetaWrapper.hpp b/Software/src/QColorMetaWrapper.hpp
new file mode 100644
index 000000000..781fb87f9
--- /dev/null
+++ b/Software/src/QColorMetaWrapper.hpp
@@ -0,0 +1,94 @@
+
+#include
+#include
+
+
+class QColorMetaWrapper : public QObject
+{
+ Q_OBJECT
+
+public:
+ // QColorMetaWrapper(const QColor &color) : m_color(color) {}
+ Q_INVOKABLE QColorMetaWrapper(const QString &name) : m_color(QColor(name)) {}
+ Q_INVOKABLE QColorMetaWrapper(int color) : m_color(QColor(color)) {}
+ Q_INVOKABLE QColorMetaWrapper(int r, int g, int b, int a = 255) : m_color(QColor(r, g, b, a)) {}
+ Q_INVOKABLE QColorMetaWrapper () {}
+ Q_INVOKABLE int alpha() const { return m_color.alpha(); }
+ Q_INVOKABLE float alphaF() const { return m_color.alphaF(); }
+ Q_INVOKABLE int black() const { return m_color.black(); }
+ Q_INVOKABLE float blackF() const { return m_color.blackF(); }
+ Q_INVOKABLE int blue() const { return m_color.blue(); }
+ Q_INVOKABLE float blueF() const { return m_color.blueF(); }
+ Q_INVOKABLE int cyan() const { return m_color.cyan(); }
+ Q_INVOKABLE float cyanF() const { return m_color.cyanF(); }
+ // Q_INVOKABLE QColorMetaWrapper darker(int factor = 200) const { return QColorMetaWrapper(m_color.darker(factor)); }
+ Q_INVOKABLE int green() const { return m_color.green(); }
+ Q_INVOKABLE float greenF() const { return m_color.greenF(); }
+ Q_INVOKABLE int hslHue() const { return m_color.hslHue(); }
+ Q_INVOKABLE float hslHueF() const { return m_color.hslHueF(); }
+ Q_INVOKABLE int hslSaturation() const { return m_color.hslSaturation(); }
+ Q_INVOKABLE float hslSaturationF() const { return m_color.hslSaturationF(); }
+ Q_INVOKABLE int hsvHue() const { return m_color.hsvHue(); }
+ Q_INVOKABLE float hsvHueF() const { return m_color.hsvHueF(); }
+ Q_INVOKABLE int hsvSaturation() const { return m_color.hsvSaturation(); }
+ Q_INVOKABLE float hsvSaturationF() const { return m_color.hsvSaturationF(); }
+ Q_INVOKABLE int hue() const { return m_color.hue(); }
+ Q_INVOKABLE float hueF() const { return m_color.hueF(); }
+ Q_INVOKABLE bool isValid() const { return m_color.isValid(); }
+ // Q_INVOKABLE QColorMetaWrapper lighter(int factor = 150) const { return QColorMetaWrapper(m_color.lighter(factor)); }
+ Q_INVOKABLE int lightness() const { return m_color.lightness(); }
+ Q_INVOKABLE float lightnessF() const { return m_color.lightnessF(); }
+ Q_INVOKABLE int magenta() const { return m_color.magenta(); }
+ Q_INVOKABLE float magentaF() const { return m_color.magentaF(); }
+ Q_INVOKABLE QString name() const { return m_color.name(); }
+ Q_INVOKABLE int red() const { return m_color.red(); }
+ Q_INVOKABLE float redF() const { return m_color.redF(); }
+ Q_INVOKABLE int rgb() const { return m_color.rgb(); }
+ Q_INVOKABLE int rgba() const { return m_color.rgba(); }
+ Q_INVOKABLE int saturation() const { return m_color.saturation(); }
+ Q_INVOKABLE float saturationF() const { return m_color.saturationF(); }
+ Q_INVOKABLE void setAlpha(int alpha) { return m_color.setAlpha(alpha); }
+ Q_INVOKABLE void setAlphaF(float alpha) { m_color.setAlphaF(alpha); }
+ Q_INVOKABLE void setBlue(int blue) { m_color.setBlue(blue); }
+ Q_INVOKABLE void setBlueF(float blue) { m_color.setBlueF(blue); }
+ Q_INVOKABLE void setCmyk(int c, int m, int y, int k, int a = 255) { m_color.setCmyk(c, m, y, k, a); }
+ Q_INVOKABLE void setCmykF(float c, float m, float y, float k, float a = 1.0) { m_color.setCmykF(c, m, y, k, a); }
+ Q_INVOKABLE void setGreen(int green) { m_color.setGreen(green); }
+ Q_INVOKABLE void setGreenF(float green) { m_color.setGreenF(green); }
+ Q_INVOKABLE void setHsl(int h, int s, int l, int a = 255) { m_color.setHsl(h, s, l, a); }
+ Q_INVOKABLE void setHslF(float h, float s, float l, float a = 1.0) { m_color.setHslF(h, s, l, a); }
+ Q_INVOKABLE void setHsv(int h, int s, int v, int a = 255) { m_color.setHsv(h, s, v, a); }
+ Q_INVOKABLE void setHsvF(float h, float s, float v, float a = 1.0) { m_color.setHsvF(h, s, v, a); }
+ Q_INVOKABLE void setNamedColor(const QString &name) { m_color.setNamedColor(name); }
+ Q_INVOKABLE void setRed(int red) { m_color.setRed(red); }
+ Q_INVOKABLE void setRedF(float red) { m_color.setRedF(red); }
+ Q_INVOKABLE void setRgb(int r, int g, int b, int a = 255) { m_color.setRgb(r, g, b, a); }
+ Q_INVOKABLE void setRgb(int rgb) { m_color.setRgb(rgb); }
+ Q_INVOKABLE void setRgbF(float r, float g, float b, float a = 1.0) { m_color.setRgbF(r, g, b, a); }
+ Q_INVOKABLE void setRgba(int rgba) { m_color.setRgba(rgba); }
+ // Q_INVOKABLE QColor toCmyk() const
+ // Q_INVOKABLE QColor toExtendedRgb() const
+ // Q_INVOKABLE QColor toHsl() const
+ // Q_INVOKABLE QColor toHsv() const
+ // Q_INVOKABLE QColor toRgb() const
+ Q_INVOKABLE int value() const { return m_color.value(); }
+ Q_INVOKABLE float valueF() const { return m_color.valueF(); }
+ Q_INVOKABLE int yellow() const { return m_color.yellow(); }
+ Q_INVOKABLE float yellowF() const { return m_color.yellowF(); }
+
+
+ // Q_INVOKABLE static QStringList colorNames() { return QColor::colorNames(); }
+ // Q_INVOKABLE static QColorMetaWrapper fromCmyk(int c, int m, int y, int k, int a = 255) { return QColorMetaWrapper(QColor::fromCmyk(c, m, y, k, a)); }
+ // Q_INVOKABLE static QColorMetaWrapper fromCmykF(qreal c, qreal m, qreal y, qreal k, qreal a = 1.0) { return QColorMetaWrapper(QColor::fromCmykF(c, m ,y, k, a)); }
+ // Q_INVOKABLE static QColorMetaWrapper fromHsl(int h, int s, int l, int a = 255) { return QColorMetaWrapper(QColor::fromHsl(h, s, l, a)); }
+ // Q_INVOKABLE static QColorMetaWrapper fromHslF(qreal h, qreal s, qreal l, qreal a = 1.0) { return QColorMetaWrapper(QColor::fromHslF(h, s, l, a)); }
+ // Q_INVOKABLE static QColorMetaWrapper fromHsv(int h, int s, int v, int a = 255) { return QColorMetaWrapper(QColor::fromHsv(h, s, v, a)); }
+ // Q_INVOKABLE static QColorMetaWrapper fromHsvF(qreal h, qreal s, qreal v, qreal a = 1.0) { return QColorMetaWrapper(QColor::fromHsvF(h, s, v, a)); }
+ // Q_INVOKABLE static QColorMetaWrapper fromRgb(int rgb) { return QColorMetaWrapper(QColor::fromRgb(rgb)); }
+ // Q_INVOKABLE static QColorMetaWrapper fromRgb(int r, int g, int b, int a = 255) { return QColorMetaWrapper(QColor::fromRgb(r, g, b, a)); }
+ // Q_INVOKABLE static QColorMetaWrapper fromRgbF(qreal r, qreal g, qreal b, qreal a = 1.0) { return QColorMetaWrapper(QColor::fromRgbF(r, g, b, a)); }
+ // Q_INVOKABLE static QColorMetaWrapper fromRgba(int rgba) { return QColorMetaWrapper(QColor::fromRgba(rgba)); }
+ // Q_INVOKABLE static bool isValidColor(const QString &name) { return QColor::isValidColor(name); }
+private:
+ QColor m_color;
+};
diff --git a/Software/src/Settings.cpp b/Software/src/Settings.cpp
index cd14534d9..41ee09bb0 100644
--- a/Software/src/Settings.cpp
+++ b/Software/src/Settings.cpp
@@ -1530,7 +1530,7 @@ QColor Settings::getMoodLampColor()
void Settings::setMoodLampColor(QColor value)
{
DEBUG_LOW_LEVEL << Q_FUNC_INFO << value.name();
- setValue(Profile::Key::MoodLamp::Color, value.name() );
+ setValue(Profile::Key::MoodLamp::Color, value.name());
m_this->moodLampColorChanged(value);
}
@@ -1547,13 +1547,14 @@ void Settings::setMoodLampSpeed(int value)
m_this->moodLampSpeedChanged(value);
}
-int Settings::getMoodLampLamp()
+QString Settings::getMoodLampLamp()
{
DEBUG_LOW_LEVEL << Q_FUNC_INFO;
- return value(Profile::Key::MoodLamp::Lamp).toInt();
+ const QString& val = value(Profile::Key::MoodLamp::Lamp).toString();
+ return val.isEmpty() ? Profile::MoodLamp::LampDefault : val;
}
-void Settings::setMoodLampLamp(int value)
+void Settings::setMoodLampLamp(const QString& value)
{
DEBUG_LOW_LEVEL << Q_FUNC_INFO;
setValue(Profile::Key::MoodLamp::Lamp, value);
@@ -2143,5 +2144,13 @@ void Settings::migrateSettings()
setValueMain(Main::Key::MainConfigVersion, "4.0");
}
+
+ const QString& val = value(Profile::Key::MoodLamp::Lamp).toString();
+ if (val == "0")
+ setValue(Profile::Key::MoodLamp::Lamp, "static.mjs");
+ else if (val == "1")
+ setValue(Profile::Key::MoodLamp::Lamp, "fire.mjs");
+ else if (val == "2")
+ setValue(Profile::Key::MoodLamp::Lamp, "rgb_is_life.mjs");
}
} /*SettingsScope*/
diff --git a/Software/src/Settings.hpp b/Software/src/Settings.hpp
index d326712a5..9effd45d3 100644
--- a/Software/src/Settings.hpp
+++ b/Software/src/Settings.hpp
@@ -214,8 +214,8 @@ class Settings : public QObject
static void setMoodLampColor(QColor color);
static int getMoodLampSpeed();
static void setMoodLampSpeed(int value);
- static int getMoodLampLamp();
- static void setMoodLampLamp(int value);
+ static QString getMoodLampLamp();
+ static void setMoodLampLamp(const QString& value);
#ifdef SOUNDVIZ_SUPPORT
static int getSoundVisualizerDevice();
@@ -353,7 +353,7 @@ class Settings : public QObject
void moodLampLiquidModeChanged(bool isLiquidMode);
void moodLampColorChanged(const QColor color);
void moodLampSpeedChanged(int value);
- void moodLampLampChanged(int value);
+ void moodLampLampChanged(const QString& value);
#ifdef SOUNDVIZ_SUPPORT
void soundVisualizerDeviceChanged(int value);
void soundVisualizerVisualizerChanged(int value);
diff --git a/Software/src/SettingsDefaults.hpp b/Software/src/SettingsDefaults.hpp
index 3955b7e75..8ab9b8def 100644
--- a/Software/src/SettingsDefaults.hpp
+++ b/Software/src/SettingsDefaults.hpp
@@ -190,7 +190,7 @@ static const double GammaMax = 10.0;
// [MoodLamp]
namespace MoodLamp
{
-static const int LampDefault = 0;
+static const QString LampDefault = "static.mjs";
static const int SpeedMin = 1;
static const int SpeedDefault = 50;
static const int SpeedMax = 100;
diff --git a/Software/src/SettingsWindow.cpp b/Software/src/SettingsWindow.cpp
index 3cab18442..73a0a2655 100644
--- a/Software/src/SettingsWindow.cpp
+++ b/Software/src/SettingsWindow.cpp
@@ -222,6 +222,9 @@ void SettingsWindow::connectSignalsSlots()
connect(ui->radioButton_LiquidColorMoodLampMode, SIGNAL(toggled(bool)), this, SLOT(onMoodLampLiquidMode_Toggled(bool)));
connect(ui->horizontalSlider_MoodLampSpeed, SIGNAL(valueChanged(int)), this, SLOT(onMoodLampSpeed_valueChanged(int)));
connect(ui->comboBox_MoodLampLamp, SIGNAL(currentIndexChanged(int)), this, SLOT(onMoodLampLamp_currentIndexChanged(int)));
+ connect(ui->toolButton_MoodLampReload, SIGNAL(clicked()), this, SLOT(onMoodLampReloadScripts_clicked()));
+ connect(ui->toolButton_MoodLampOpenDir, SIGNAL(clicked()), this, SLOT(onMoodLampOpenScripts_clicked()));
+ connect(ui->pushButton_MoodLampOpenReadme, SIGNAL(clicked()), this, SLOT(onMoodLampOpenReadme_clicked()));
// Main options
connect(ui->comboBox_LightpackModes, SIGNAL(currentIndexChanged(int)), this, SLOT(onLightpackModes_currentIndexChanged(int)));
@@ -267,7 +270,7 @@ void SettingsWindow::connectSignalsSlots()
#else
ui->pushButton_SoundVizDeviceHelp->hide();
#endif // Q_OS_MACOS
-
+
#endif// SOUNDVIZ_SUPPORT
connect(ui->checkBox_ExpertModeEnabled, SIGNAL(toggled(bool)), this, SLOT(onExpertModeEnabled_Toggled(bool)));
connect(ui->checkBox_KeepLightsOnAfterExit, SIGNAL(toggled(bool)), this, SLOT(onKeepLightsAfterExit_Toggled(bool)));
@@ -496,7 +499,7 @@ int SettingsWindow::getLigtpackFirmwareVersionMajor()
void SettingsWindow::onPostInit() {
updateUiFromSettings();
this->requestFirmwareVersion();
- this->requestMoodLampLamps();
+ this->requestMoodLampLamps(false);
#ifdef SOUNDVIZ_SUPPORT
this->requestSoundVizDevices();
this->requestSoundVizVisualizers();
@@ -986,18 +989,17 @@ void SettingsWindow::updateAvailableSoundVizVisualizers(const QList & lamps, int recommended)
+void SettingsWindow::updateAvailableMoodLampLamps(const QList & lamps)
{
ui->comboBox_MoodLampLamp->blockSignals(true);
ui->comboBox_MoodLampLamp->clear();
- int selectedLamp = Settings::getMoodLampLamp();
- if (selectedLamp == -1) selectedLamp = recommended;
+ QString selectedLamp = Settings::getMoodLampLamp();
int selectIndex = -1;
for (int i = 0; i < lamps.size(); i++) {
- ui->comboBox_MoodLampLamp->addItem(lamps[i].name, lamps[i].id);
- if (lamps[i].id == selectedLamp) {
+ ui->comboBox_MoodLampLamp->addItem(lamps[i].name, lamps[i].moduleName);
+
+ if (lamps[i].moduleName == selectedLamp)
selectIndex = i;
- }
}
ui->comboBox_MoodLampLamp->setCurrentIndex(selectIndex);
ui->comboBox_MoodLampLamp->blockSignals(false);
@@ -1076,7 +1078,7 @@ void SettingsWindow::ledDeviceOpenSuccess(bool isSuccess)
}
void SettingsWindow::ledDeviceCallSuccess(bool isSuccess)
-{
+{
DEBUG_HIGH_LEVEL << Q_FUNC_INFO << isSuccess << m_backlightStatus << sender();
// If Backlight::StatusOff then nothings changed
@@ -1135,7 +1137,7 @@ void SettingsWindow::ledDeviceFirmwareVersionUnofficialResult(const int version)
}
void SettingsWindow::refreshAmbilightEvaluated(double updateResultMs)
-{
+{
DEBUG_HIGH_LEVEL << Q_FUNC_INFO << updateResultMs;
double hz = 0;
@@ -1161,7 +1163,7 @@ void SettingsWindow::refreshAmbilightEvaluated(double updateResultMs)
DEBUG_HIGH_LEVEL << Q_FUNC_INFO << "Therotical Max Hz for led count and baud rate:" << theoreticalMaxHz << ledCount << baudRate;
if (theoreticalMaxHz <= hz)
qWarning() << Q_FUNC_INFO << hz << "FPS went over theoretical max of" << theoreticalMaxHz;
-
+
const QPalette& defaultPalette = ui->label_GrabFrequency_txt_fps->palette();
QPalette palette = ui->label_GrabFrequency_value->palette();
@@ -1440,8 +1442,8 @@ void SettingsWindow::onMoodLampSpeed_valueChanged(int value)
void SettingsWindow::onMoodLampLamp_currentIndexChanged(int index)
{
if (!updatingFromSettings) {
- DEBUG_MID_LEVEL << Q_FUNC_INFO << index << ui->comboBox_MoodLampLamp->currentData().toInt();
- Settings::setMoodLampLamp(ui->comboBox_MoodLampLamp->currentData().toInt());
+ DEBUG_MID_LEVEL << Q_FUNC_INFO << index << ui->comboBox_MoodLampLamp->currentData().toString();
+ Settings::setMoodLampLamp(ui->comboBox_MoodLampLamp->currentData().toString());
}
}
@@ -1460,6 +1462,27 @@ void SettingsWindow::onMoodLampLiquidMode_Toggled(bool checked)
}
}
+void SettingsWindow::onMoodLampReloadScripts_clicked()
+{
+ DEBUG_LOW_LEVEL << Q_FUNC_INFO;
+
+ this->requestMoodLampLamps(true);
+}
+
+void SettingsWindow::onMoodLampOpenScripts_clicked()
+{
+ DEBUG_LOW_LEVEL << Q_FUNC_INFO;
+
+ this->openMoodLampScriptDir();
+}
+
+void SettingsWindow::onMoodLampOpenReadme_clicked()
+{
+ DEBUG_LOW_LEVEL << Q_FUNC_INFO;
+
+ QDesktopServices::openUrl(QUrl("https://github.com/psieg/Lightpack/tree/master/Software/res/moodlamps"));
+}
+
#ifdef SOUNDVIZ_SUPPORT
void SettingsWindow::onSoundVizDevice_currentIndexChanged(int index)
{
@@ -1850,7 +1873,7 @@ void SettingsWindow::updateUiFromSettings()
onLightpackModeChanged(mode);
ui->checkBox_ExpertModeEnabled->setChecked (Settings::isExpertModeEnabled());
-
+
ui->checkBox_checkForUpdates->setChecked (Settings::isCheckForUpdatesEnabled());
ui->checkBox_installUpdates->setChecked (Settings::isInstallUpdatesEnabled());
@@ -1877,12 +1900,12 @@ void SettingsWindow::updateUiFromSettings()
ui->radioButton_LuminosityDeadZone->setChecked (!Settings::isMinimumLuminosityEnabled());
// Check the selected moodlamp mode (setChecked(false) not working to select another)
- ui->radioButton_ConstantColorMoodLampMode->setChecked (!Settings::isMoodLampLiquidMode());
+ ui->radioButton_BaseColorMoodLamp->setChecked (!Settings::isMoodLampLiquidMode());
ui->radioButton_LiquidColorMoodLampMode->setChecked (Settings::isMoodLampLiquidMode());
ui->pushButton_SelectColorMoodLamp->setColor (Settings::getMoodLampColor());
ui->horizontalSlider_MoodLampSpeed->setValue (Settings::getMoodLampSpeed());
for (int i = 0; i < ui->comboBox_MoodLampLamp->count(); i++) {
- if (ui->comboBox_MoodLampLamp->itemData(i).toInt() == Settings::getMoodLampLamp()) {
+ if (ui->comboBox_MoodLampLamp->itemData(i).toString() == Settings::getMoodLampLamp()) {
ui->comboBox_MoodLampLamp->setCurrentIndex(i);
break;
}
diff --git a/Software/src/SettingsWindow.hpp b/Software/src/SettingsWindow.hpp
index 405f1440a..20485a2c1 100644
--- a/Software/src/SettingsWindow.hpp
+++ b/Software/src/SettingsWindow.hpp
@@ -78,7 +78,7 @@ class SettingsWindow : public QMainWindow {
void requestSoundVizDevices();
void requestSoundVizVisualizers();
#endif
- void requestMoodLampLamps();
+ void requestMoodLampLamps(const bool reloadScripts);
void recreateLedDevice();
void resultBacklightStatus(Backlight::Status);
void backlightStatusChanged(Backlight::Status);
@@ -88,6 +88,7 @@ class SettingsWindow : public QMainWindow {
void updateApiKey(QString key);
void updateApiDeviceNumberOfLeds(int value);
void reloadPlugins();
+ void openMoodLampScriptDir();
public slots:
void ledDeviceOpenSuccess(bool isSuccess);
@@ -112,7 +113,7 @@ public slots:
void onPingDeviceEverySecond_Toggled(bool state);
void processMessage(const QString &message);
- void updateAvailableMoodLampLamps(const QList & lamps, int recommended);
+ void updateAvailableMoodLampLamps(const QList & lamps);
#ifdef SOUNDVIZ_SUPPORT
void updateAvailableSoundVizDevices(const QList & devices, int recommended);
void updateAvailableSoundVizVisualizers(const QList & visualizers, int recommended);
@@ -138,6 +139,9 @@ private slots:
void onMoodLampSpeed_valueChanged(int value);
void onMoodLampLamp_currentIndexChanged(int index);
void onMoodLampLiquidMode_Toggled(bool isLiquidMode);
+ void onMoodLampReloadScripts_clicked();
+ void onMoodLampOpenScripts_clicked();
+ void onMoodLampOpenReadme_clicked();
#ifdef SOUNDVIZ_SUPPORT
void onSoundVizDevice_currentIndexChanged(int index);
void onSoundVizVisualizer_currentIndexChanged(int index);
diff --git a/Software/src/SettingsWindow.ui b/Software/src/SettingsWindow.ui
index 278f6b2f1..8c564c437 100644
--- a/Software/src/SettingsWindow.ui
+++ b/Software/src/SettingsWindow.ui
@@ -720,14 +720,69 @@ Internally emulates the effects of f.lux, redshift, Night Light, Night Shift...<
0
-
-
+
+
-
+
+
+ -
+
+
+ Reload lamp list
+
+
+ Reload
+
+
+
+ :/icons/refresh.png:/icons/refresh.png
+
+
+
+ -
+
+
+ Open lamp script directory
+
+
+ Add more
+
+
+
+ :/icons/profile_new.png:/icons/profile_new.png
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+
+
+
+ :/icons/help.png:/icons/help.png
+
+
+ true
+
+
+
+
-
-
-
+
+
+ <html><head/><body><p>A constant base color that will be passed to moodlamps as an optional seed/starting color.</p><p>Some lamps might not use it, some might use it partially (a combination of R,G,B,H,S,L,V components)</p></body></html>
+
- Constant color:
+ Base color:
true
@@ -739,6 +794,9 @@ Internally emulates the effects of f.lux, redshift, Night Light, Night Shift...<
-
+
+ <html><head/><body><p>A constant base color that will be passed to moodlamps as an optional seed/starting color.</p><p>Some lamps might not use it, some might use it partially (a combination of R,G,B,H,S,L,V components)</p></body></html>
+
@@ -769,8 +827,11 @@ Internally emulates the effects of f.lux, redshift, Night Light, Night Shift...<
-
+
+ Makes the base color smoothly cycle through a dozen of colors at a certain rate.
+
- Change color with rate:
+ Change base color with rate:
@@ -781,6 +842,9 @@ Internally emulates the effects of f.lux, redshift, Night Light, Night Shift...<
8
+
+ Makes the base color smoothly cycle through a dozen of colors at a certain rate.
+
margin-bottom:0.4em; margin-left:0.5em
@@ -797,6 +861,9 @@ Internally emulates the effects of f.lux, redshift, Night Light, Night Shift...<
false
+
+ Makes the base color smoothly cycle through a dozen of colors at a certain rate.
+
1
@@ -815,6 +882,9 @@ Internally emulates the effects of f.lux, redshift, Night Light, Night Shift...<
8
+
+ Makes the base color smoothly cycle through a dozen of colors at a certain rate.
+
margin-bottom:0.4em;
@@ -3087,7 +3157,11 @@ Internally emulates the effects of f.lux, redshift, Night Light, Night Shift...<
radioButton_GrabWidgetsDontShow
radioButton_Colored
radioButton_White
- radioButton_ConstantColorMoodLampMode
+ comboBox_MoodLampLamp
+ toolButton_MoodLampReload
+ toolButton_MoodLampOpenDir
+ pushButton_MoodLampOpenReadme
+ radioButton_BaseColorMoodLamp
pushButton_SelectColorMoodLamp
radioButton_LiquidColorMoodLampMode
horizontalSlider_MoodLampSpeed
diff --git a/Software/src/src.pro b/Software/src/src.pro
index 66546f492..3ea473463 100644
--- a/Software/src/src.pro
+++ b/Software/src/src.pro
@@ -14,7 +14,7 @@ CONFIG(msvc) {
}
DESTDIR = ../bin
TEMPLATE = app
-QT += network widgets
+QT += network widgets qml
win32 {
QT += serialport
}
@@ -279,7 +279,6 @@ SOURCES += \
ApiServer.cpp \
ApiServerSetColorTask.cpp \
MoodLampManager.cpp \
- MoodLamp.cpp \
LiquidColorGenerator.cpp \
LedDeviceManager.cpp \
SelectWidget.cpp \
@@ -335,7 +334,7 @@ HEADERS += \
../../CommonHeaders/COMMANDS.h \
../../CommonHeaders/USB_ID.h \
MoodLampManager.hpp \
- MoodLamp.hpp \
+ QColorMetaWrapper.hpp \
LiquidColorGenerator.hpp \
LedDeviceManager.hpp \
SelectWidget.hpp \