diff --git a/CMakeLists.txt b/CMakeLists.txt index 0d81e2ff824d..fdd661c1b6d5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3034,6 +3034,7 @@ if(BUILD_TESTING) src/test/controller_mapping_file_handler_test.cpp src/test/controllerrenderingengine_test.cpp src/test/qmlcontrolproxytest.cpp + src/test/qmleffectsproxytest.cpp src/test/qmlkeyutilstest.cpp src/test/qmlskincontrolcreatortest.cpp src/test/themeqml_test.cpp @@ -3994,6 +3995,7 @@ if(QML) src/qml/qmldlgpreferencesproxy.cpp src/qml/qmleffectmanifestparametersmodel.cpp src/qml/qmleffectslotproxy.cpp + src/qml/qmleffectunitproxy.cpp src/qml/qmleffectsmanagerproxy.cpp src/qml/qmlkeyutils.cpp src/qml/qmllegacylibraryitem.cpp diff --git a/res/qml/EffectAssignmentButtons.qml b/res/qml/EffectAssignmentButtons.qml new file mode 100644 index 000000000000..b3486237a02d --- /dev/null +++ b/res/qml/EffectAssignmentButtons.qml @@ -0,0 +1,37 @@ +pragma ComponentBehavior: Bound + +import "." as Skin +import Mixxx 1.0 as Mixxx +import QtQuick +import "Theme" + +Row { + id: root + + property color activeColor: Theme.effectUnitColor + required property string channelGroup + property color inactiveColor: Theme.knobBackgroundColor + property bool showFourUnits: showFourUnitsControl.value > 0 + + Repeater { + model: root.showFourUnits ? 4 : 2 + + Skin.ControlButton { + required property int index + + activeColor: root.activeColor + group: "[EffectRack1_EffectUnit" + (index + 1) + "]" + height: 20 + key: "group_" + root.channelGroup + "_enable" + text: root.showFourUnits ? (index === 0 ? "FX1" : index + 1) : "FX" + (index + 1) + toggleable: true + width: root.showFourUnits ? (index === 0 ? 26 : 20) : 26 + } + } + Mixxx.ControlProxy { + id: showFourUnitsControl + + group: "[Skin]" + key: "show_4effectunits" + } +} diff --git a/res/qml/EffectChainPresetPopup.qml b/res/qml/EffectChainPresetPopup.qml new file mode 100644 index 000000000000..b13460e3bdab --- /dev/null +++ b/res/qml/EffectChainPresetPopup.qml @@ -0,0 +1,175 @@ +pragma ComponentBehavior: Bound + +import "." as Skin +import Mixxx 1.0 as Mixxx +import QtQuick +import QtQuick.Controls + +Menu { + id: root + + property color backgroundColor: "#151517" + property color borderColor: "#333333" + property url checkedSource + property color disabledTextColor: "#494949" + property string fontFamily: "Open Sans" + property int fontPixelSize: 14 + property color hoverColor: "#2c454f" + property color hoverTextColor: "#ffffff" + property color separatorBottomColor: "#333333" + property color separatorTopColor: "#000000" + required property Mixxx.EffectSlotProxy slot1 + required property Mixxx.EffectSlotProxy slot2 + required property Mixxx.EffectSlotProxy slot3 + property url submenuArrowSource + property color textColor: "#c2b3a5" + required property Mixxx.EffectUnitProxy unit + + bottomPadding: 5 + leftPadding: 5 + rightPadding: 5 + topPadding: 5 + width: 160 + + background: Rectangle { + border.color: root.borderColor + border.width: 1 + color: root.backgroundColor + radius: 1 + } + delegate: Skin.EffectPresetMenuItem { + checkedSource: root.checkedSource + disabledTextColor: root.disabledTextColor + fontFamily: root.fontFamily + fontPixelSize: root.fontPixelSize + hoverColor: root.hoverColor + hoverTextColor: root.hoverTextColor + submenuArrowSource: root.submenuArrowSource + textColor: root.textColor + } + + Instantiator { + model: Mixxx.EffectsManager.standardChainPresetModel + + delegate: Skin.EffectPresetMenuItem { + required property int index + required property string name + required property string presetDisplay + required property string tooltip + + ToolTip.delay: 500 + ToolTip.text: tooltip + ToolTip.visible: hovered && tooltip.length > 0 + checkedSource: root.checkedSource + disabledTextColor: root.disabledTextColor + fontFamily: root.fontFamily + fontPixelSize: root.fontPixelSize + hoverColor: root.hoverColor + hoverTextColor: root.hoverTextColor + submenuArrowSource: root.submenuArrowSource + text: (root.unit.presetName === name ? "✓ " : "") + presetDisplay + textColor: root.textColor + + onTriggered: root.unit.loadPreset(index) + } + + onObjectAdded: (index, object) => root.insertItem(index, object) + onObjectRemoved: (index, object) => root.removeItem(object) + } + Skin.EffectPresetMenuSeparator { + bottomColor: root.separatorBottomColor + topColor: root.separatorTopColor + } + Skin.EffectPresetMenuItem { + checkedSource: root.checkedSource + disabledTextColor: root.disabledTextColor + enabled: root.unit.canUpdatePreset + fontFamily: root.fontFamily + fontPixelSize: root.fontPixelSize + hoverColor: root.hoverColor + hoverTextColor: root.hoverTextColor + submenuArrowSource: root.submenuArrowSource + text: qsTr("Update Preset") + textColor: root.textColor + visible: enabled + + onTriggered: root.unit.updatePreset() + } + Skin.EffectPresetMenuItem { + checkedSource: root.checkedSource + disabledTextColor: root.disabledTextColor + enabled: root.unit.canRenamePreset + fontFamily: root.fontFamily + fontPixelSize: root.fontPixelSize + hoverColor: root.hoverColor + hoverTextColor: root.hoverTextColor + submenuArrowSource: root.submenuArrowSource + text: qsTr("Rename Preset") + textColor: root.textColor + visible: enabled + + onTriggered: root.unit.renamePreset() + } + Skin.EffectPresetMenuItem { + checkedSource: root.checkedSource + disabledTextColor: root.disabledTextColor + fontFamily: root.fontFamily + fontPixelSize: root.fontPixelSize + hoverColor: root.hoverColor + hoverTextColor: root.hoverTextColor + submenuArrowSource: root.submenuArrowSource + text: qsTr("Save As New Preset...") + textColor: root.textColor + + onTriggered: root.unit.savePresetAs() + } + Skin.EffectPresetMenuSeparator { + bottomColor: root.separatorBottomColor + topColor: root.separatorTopColor + } + Skin.EffectSlotPresetMenu { + backgroundColor: root.backgroundColor + borderColor: root.borderColor + checkedSource: root.checkedSource + disabledTextColor: root.disabledTextColor + fontFamily: root.fontFamily + fontPixelSize: root.fontPixelSize + hoverColor: root.hoverColor + hoverTextColor: root.hoverTextColor + separatorBottomColor: root.separatorBottomColor + separatorTopColor: root.separatorTopColor + slot: root.slot1 + submenuArrowSource: root.submenuArrowSource + textColor: root.textColor + } + Skin.EffectSlotPresetMenu { + backgroundColor: root.backgroundColor + borderColor: root.borderColor + checkedSource: root.checkedSource + disabledTextColor: root.disabledTextColor + fontFamily: root.fontFamily + fontPixelSize: root.fontPixelSize + hoverColor: root.hoverColor + hoverTextColor: root.hoverTextColor + separatorBottomColor: root.separatorBottomColor + separatorTopColor: root.separatorTopColor + slot: root.slot2 + submenuArrowSource: root.submenuArrowSource + textColor: root.textColor + } + Skin.EffectSlotPresetMenu { + backgroundColor: root.backgroundColor + borderColor: root.borderColor + checkedSource: root.checkedSource + disabledTextColor: root.disabledTextColor + fontFamily: root.fontFamily + fontPixelSize: root.fontPixelSize + hoverColor: root.hoverColor + hoverTextColor: root.hoverTextColor + separatorBottomColor: root.separatorBottomColor + separatorTopColor: root.separatorTopColor + slot: root.slot3 + submenuArrowSource: root.submenuArrowSource + textColor: root.textColor + } +} diff --git a/res/qml/EffectFocusButton.qml b/res/qml/EffectFocusButton.qml new file mode 100644 index 000000000000..60a3be7b76e4 --- /dev/null +++ b/res/qml/EffectFocusButton.qml @@ -0,0 +1,40 @@ +import "." as Skin +import Mixxx 1.0 as Mixxx +import QtQuick +import "Theme" + +Skin.Button { + id: root + + required property int effectNumber + required property string effectUnitGroup + readonly property bool focused: Math.round(focusedEffectControl.value) === effectNumber + + activeColor: Theme.effectColor + highlight: focused + text: "F" + visible: showFocusControl.value > 0 + + TapHandler { + acceptedButtons: Qt.LeftButton + + onTapped: focusedEffectControl.value = root.focused ? 0 : root.effectNumber + } + TapHandler { + acceptedButtons: Qt.RightButton + + onTapped: focusedEffectControl.value = 0 + } + Mixxx.ControlProxy { + id: focusedEffectControl + + group: root.effectUnitGroup + key: "focused_effect" + } + Mixxx.ControlProxy { + id: showFocusControl + + group: root.effectUnitGroup + key: "show_focus" + } +} diff --git a/res/qml/EffectPresetMenuItem.qml b/res/qml/EffectPresetMenuItem.qml new file mode 100644 index 000000000000..ad51f29e7156 --- /dev/null +++ b/res/qml/EffectPresetMenuItem.qml @@ -0,0 +1,54 @@ +import QtQuick +import QtQuick.Controls + +MenuItem { + id: root + + property url checkedSource + property color disabledTextColor: "#494949" + property string fontFamily: "Open Sans" + property int fontPixelSize: 14 + property color hoverColor: "#2c454f" + property color hoverTextColor: "#ffffff" + property url submenuArrowSource + property color textColor: "#c2b3a5" + + implicitHeight: visible ? 22 : 0 + implicitWidth: Math.max(160, contentItem.implicitWidth + leftPadding + rightPadding) + leftPadding: checkable ? 22 : 6 + rightPadding: subMenu ? 22 : 6 + + arrow: Image { + anchors.right: parent.right + anchors.rightMargin: 6 + anchors.verticalCenter: parent.verticalCenter + fillMode: Image.PreserveAspectFit + height: 10 + source: root.submenuArrowSource + visible: root.subMenu && root.enabled + width: 6 + } + background: Rectangle { + color: root.highlighted && root.enabled ? root.hoverColor : "transparent" + radius: 1 + } + contentItem: Text { + color: !root.enabled ? root.disabledTextColor : root.highlighted ? root.hoverTextColor : root.textColor + elide: Text.ElideRight + font.family: root.fontFamily + font.pixelSize: root.fontPixelSize + horizontalAlignment: Text.AlignLeft + text: root.text + verticalAlignment: Text.AlignVCenter + } + indicator: Image { + anchors.left: parent.left + anchors.leftMargin: 4 + anchors.verticalCenter: parent.verticalCenter + fillMode: Image.PreserveAspectFit + height: 14 + source: root.checked ? root.checkedSource : "" + visible: root.checkable && root.checked + width: 14 + } +} diff --git a/res/qml/EffectPresetMenuSeparator.qml b/res/qml/EffectPresetMenuSeparator.qml new file mode 100644 index 000000000000..35edeac88947 --- /dev/null +++ b/res/qml/EffectPresetMenuSeparator.qml @@ -0,0 +1,28 @@ +import QtQuick +import QtQuick.Controls + +MenuSeparator { + id: root + + property color bottomColor: "#333333" + property color topColor: "#000000" + + implicitHeight: 6 + + contentItem: Item { + Rectangle { + color: root.topColor + height: 1 + width: parent.width - 8 + x: 4 + y: 2 + } + Rectangle { + color: root.bottomColor + height: 1 + width: parent.width - 8 + x: 4 + y: 3 + } + } +} diff --git a/res/qml/EffectRow.qml b/res/qml/EffectRow.qml index fe1c540f416f..5f9cc756827f 100644 --- a/res/qml/EffectRow.qml +++ b/res/qml/EffectRow.qml @@ -1,41 +1,62 @@ import "." as Skin -import QtQuick 2.12 +import Mixxx 1.0 as Mixxx +import QtQuick Item { id: root - width: positioner.width - height: positioner.height + implicitHeight: columns.height Row { - id: positioner - - Skin.EffectUnit { - id: effectUnit1 - - width: root.width / 2 - unitNumber: 1 + id: columns + + width: root.width + + Column { + width: parent.width / 2 + + Skin.EffectUnit { + unitNumber: 1 + width: parent.width + } + Skin.EffectUnit { + unitNumber: 3 + visible: showFourUnitsControl.value > 0 + width: parent.width + } } - - Skin.EffectUnit { - id: effectUnit2 - - width: root.width / 2 - unitNumber: 2 + Column { + width: parent.width / 2 + + Skin.EffectUnit { + unitNumber: 2 + width: parent.width + } + Skin.EffectUnit { + unitNumber: 4 + visible: showFourUnitsControl.value > 0 + width: parent.width + } } } - Skin.SectionBackground { - anchors.top: parent.top - anchors.left: parent.left anchors.bottom: parent.bottom + anchors.left: parent.left anchors.right: parent.horizontalCenter + anchors.top: parent.top + z: -1 } - Skin.SectionBackground { - anchors.top: parent.top anchors.bottom: parent.bottom anchors.left: parent.horizontalCenter anchors.right: parent.right + anchors.top: parent.top + z: -1 + } + Mixxx.ControlProxy { + id: showFourUnitsControl + + group: "[Skin]" + key: "show_4effectunits" } } diff --git a/res/qml/EffectSelector.qml b/res/qml/EffectSelector.qml new file mode 100644 index 000000000000..a46be3a0e3d7 --- /dev/null +++ b/res/qml/EffectSelector.qml @@ -0,0 +1,39 @@ +import Mixxx 1.0 as Mixxx +import QtQuick +import QtQuick.Controls + +ComboBox { + id: root + + required property Mixxx.EffectSlotProxy slot + + function syncCurrentEffect() { + for (let index = 0; index < count; ++index) { + if (model.get(index).effectId === slot.effectId) { + currentIndex = index; + return; + } + } + currentIndex = 0; + } + + model: Mixxx.EffectsManager.visibleEffectsModel + textRole: "display" + + Component.onCompleted: syncCurrentEffect() + onActivated: index => { + const effectId = model.get(index).effectId || ""; + if (slot.effectId !== effectId) { + slot.effectId = effectId; + } + } + onCountChanged: syncCurrentEffect() + + Connections { + function onEffectIdChanged() { + root.syncCurrentEffect(); + } + + target: root.slot + } +} diff --git a/res/qml/EffectSlot.qml b/res/qml/EffectSlot.qml index acfd5b89312d..d5665c028a74 100644 --- a/res/qml/EffectSlot.qml +++ b/res/qml/EffectSlot.qml @@ -1,194 +1,188 @@ +pragma ComponentBehavior: Bound + import "." as Skin import Mixxx 1.0 as Mixxx -import QtQuick 2.12 +import QtQuick import "Theme" Item { id: root - property Mixxx.EffectSlotProxy slot: Mixxx.EffectsManager.getEffectSlot(unitNumber, effectNumber) - required property int unitNumber required property int effectNumber + readonly property string effectUnitGroup: slot.chainSlotGroup property bool expanded: false readonly property string group: slot.group property real maxSelectorWidth: 300 + property Mixxx.EffectSlotProxy slot: Mixxx.EffectsManager.getEffectSlot(unitNumber, effectNumber) + required property int unitNumber height: 50 Item { id: selector + anchors.bottom: parent.bottom anchors.left: parent.left anchors.top: parent.top - anchors.bottom: parent.bottom width: Math.min(root.width, root.maxSelectorWidth) + Skin.EffectFocusButton { + id: focusButton + + anchors.left: parent.left + anchors.margins: 5 + anchors.verticalCenter: parent.verticalCenter + effectNumber: root.effectNumber + effectUnitGroup: root.effectUnitGroup + height: 16 + width: visible ? 16 : 0 + } Skin.ControlButton { id: effectEnableButton - anchors.left: parent.left - anchors.top: parent.top + activeColor: Theme.effectColor anchors.bottom: parent.bottom + anchors.left: focusButton.right anchors.margins: 5 - width: 40 + anchors.top: parent.top group: root.group key: "enabled" - toggleable: true text: "ON" - activeColor: Theme.effectColor + toggleable: true + width: 40 } - - Skin.ComboBox { + Skin.EffectSelector { id: effectSelector - anchors.top: parent.top anchors.bottom: parent.bottom anchors.left: effectEnableButton.right - anchors.right: effectMetaKnob.left anchors.margins: 5 - textRole: "display" - model: Mixxx.EffectsManager.visibleEffectsModel - onActivated: (index) => { - const effectId = model.get(index).effectId; - if (root.slot.effectId != effectId) - root.slot.effectId = effectId; - } - Component.onCompleted: root.slot.onEffectIdChanged() - - Connections { - function onEffectIdChanged() { - const rowCount = effectSelector.model.rowCount(); - // TODO: Consider using an additional QHash in the - // model and provide a more efficient lookup method - for (let i = 0; i < rowCount; i++) { - if (effectSelector.model.get(i).effectId === root.slot.effectId) { - effectSelector.currentIndex = i; - break; - } - } - } - - target: root.slot - } + anchors.right: effectMetaKnob.left + anchors.top: parent.top + slot: root.slot } - Skin.ControlMiniKnob { id: effectMetaKnob - anchors.right: parent.right - anchors.top: parent.top anchors.bottom: parent.bottom anchors.margins: 5 + anchors.right: parent.right + anchors.top: parent.top arcStart: Knob.ArcStart.Minimum - width: 40 + color: Theme.effectColor group: root.group key: "meta" - color: Theme.effectColor + width: 40 } } - ListView { id: parametersView - visible: root.expanded - anchors.leftMargin: 10 - anchors.top: parent.top + anchors.bottom: parent.bottom anchors.left: selector.right + anchors.leftMargin: 10 anchors.right: parent.right - anchors.bottom: parent.bottom + anchors.top: parent.top clip: true - spacing: 5 model: root.slot.parametersModel orientation: ListView.Horizontal + spacing: 5 + visible: root.expanded delegate: Item { id: parameter + required property string controlKey required property int index - required property string shortName + readonly property bool isButton: type === 1 + readonly property bool isKnob: type === 0 + readonly property string label: shortName || name + required property bool loaded required property string name - required property string controlKey + required property string shortName + readonly property bool shown: loaded && slotNumber > 0 && slotNumber <= 8 + readonly property int slotNumber: { + const match = controlKey.match(/(\d+)$/); + return match ? Number(match[1]) : 0; + } required property int type - property int number: index + 1 - // TODO: Use null coalescing when we switch to Qt >= 5.15 - property string label: shortName ?? name - property bool isKnob: type == 0 - property bool isButton: type == 1 - width: 50 height: 50 + visible: shown + width: shown ? 55 : 0 EmbeddedText { anchors.fill: parent - verticalAlignment: Text.AlignBottom - text: parameter.label font.bold: false + text: parameter.label + verticalAlignment: Text.AlignBottom } - - Skin.ControlMiniKnob { - id: parameterKnob - - width: 30 + Loader { + active: parameter.shown && parameter.isKnob + anchors.horizontalCenter: parent.horizontalCenter height: 30 - anchors.centerIn: parent - arcStart: 0 - group: root.group - key: parameter.controlKey - color: Theme.effectColor - visible: parameter.isKnob - - Mixxx.ControlProxy { - id: parameterLoadedControl - - property bool loaded: value != 0 + width: 30 + sourceComponent: Skin.ControlMiniKnob { + arcStart: 0 + color: Theme.effectColor group: root.group - key: parameter.controlKey + "_loaded" + key: parameter.controlKey } } - - Skin.ControlButton { - id: buttonParameterButton - + Loader { + active: parameter.shown && parameter.isButton + anchors.horizontalCenter: parent.horizontalCenter height: 22 width: parent.width - anchors.centerIn: parent - group: root.group - key: parameter.controlKey - activeColor: Theme.effectColor - visible: parameter.isButton - toggleable: true - text: "ON" - - Mixxx.ControlProxy { - id: buttonParameterLoadedControl - - property bool loaded: value != 0 + sourceComponent: Skin.ControlButton { + activeColor: Theme.effectColor group: root.group - key: parameter.controlKey + "_loaded" + key: parameter.controlKey + text: "ON" + toggleable: true } } - } + Row { + anchors.bottom: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + height: 8 + visible: parameter.shown && parameter.isKnob + + Skin.ControlButton { + activeColor: Theme.effectColor + group: root.group + height: 8 + key: parameter.controlKey + "_link_inverse" + text: "I" + toggleable: true + width: 12 + } + Item { + height: 8 + width: 34 - populate: Transition { - NumberAnimation { - property: "opacity" - from: 0 - to: 1 - duration: 200 - } + Rectangle { + anchors.fill: parent + color: linkTypeControl.value > 0 ? Theme.effectColor : Theme.knobBackgroundColor + } + Text { + anchors.centerIn: parent + font.pixelSize: 7 + text: "LINK " + Math.round(linkTypeControl.value) + } + TapHandler { + onTapped: linkTypeControl.value = (Math.round(linkTypeControl.value) + 1) % 5 + } + Mixxx.ControlProxy { + id: linkTypeControl - NumberAnimation { - property: "scale" - from: 0 - to: 1 - duration: 200 + group: root.group + key: parameter.controlKey + "_link_type" + } + } } } - - Skin.FadeBehavior on opacity { - fadeTarget: parametersView - } } } diff --git a/res/qml/EffectSlotPresetMenu.qml b/res/qml/EffectSlotPresetMenu.qml new file mode 100644 index 000000000000..5b6620c2766f --- /dev/null +++ b/res/qml/EffectSlotPresetMenu.qml @@ -0,0 +1,92 @@ +pragma ComponentBehavior: Bound + +import Mixxx 1.0 as Mixxx +import QtQuick +import QtQuick.Controls + +Menu { + id: root + + property color backgroundColor: "#151517" + property color borderColor: "#333333" + property url checkedSource + property color disabledTextColor: "#494949" + property string fontFamily: "Open Sans" + property int fontPixelSize: 14 + property color hoverColor: "#2c454f" + property color hoverTextColor: "#ffffff" + property color separatorBottomColor: "#333333" + property color separatorTopColor: "#000000" + required property Mixxx.EffectSlotProxy slot + property url submenuArrowSource + property color textColor: "#c2b3a5" + + bottomPadding: 5 + enabled: slot.loaded + leftPadding: 5 + rightPadding: 5 + title: slot.number + ": " + slot.effectName + topPadding: 5 + width: 160 + + background: Rectangle { + border.color: root.borderColor + border.width: 1 + color: root.backgroundColor + radius: 1 + } + delegate: EffectPresetMenuItem { + checkedSource: root.checkedSource + disabledTextColor: root.disabledTextColor + fontFamily: root.fontFamily + fontPixelSize: root.fontPixelSize + hoverColor: root.hoverColor + hoverTextColor: root.hoverTextColor + submenuArrowSource: root.submenuArrowSource + textColor: root.textColor + } + + Instantiator { + model: root.slot.parametersModel + + delegate: EffectPresetMenuItem { + required property bool loaded + required property string name + required property string parameterId + + checkable: true + checked: loaded + checkedSource: root.checkedSource + disabledTextColor: root.disabledTextColor + fontFamily: root.fontFamily + fontPixelSize: root.fontPixelSize + hoverColor: root.hoverColor + hoverTextColor: root.hoverTextColor + submenuArrowSource: root.submenuArrowSource + text: name + textColor: root.textColor + + onTriggered: root.slot.setParameterVisible(parameterId, !loaded) + } + + onObjectAdded: (index, object) => root.insertItem(index, object) + onObjectRemoved: (index, object) => root.removeItem(object) + } + EffectPresetMenuSeparator { + bottomColor: root.separatorBottomColor + topColor: root.separatorTopColor + } + EffectPresetMenuItem { + checkedSource: root.checkedSource + disabledTextColor: root.disabledTextColor + fontFamily: root.fontFamily + fontPixelSize: root.fontPixelSize + hoverColor: root.hoverColor + hoverTextColor: root.hoverTextColor + submenuArrowSource: root.submenuArrowSource + text: qsTr("Save snapshot") + textColor: root.textColor + + onTriggered: root.slot.saveDefaultSnapshot() + } +} diff --git a/res/qml/EffectUnit.qml b/res/qml/EffectUnit.qml index 5e293a084861..fd5a7acc3762 100644 --- a/res/qml/EffectUnit.qml +++ b/res/qml/EffectUnit.qml @@ -1,10 +1,14 @@ import "." as Skin -import QtQuick 2.12 +import Mixxx 1.0 as Mixxx +import QtQuick import "Theme" Item { id: root + readonly property bool expanded: expandedControl.value > 0 + readonly property string group: unit.group + readonly property Mixxx.EffectUnitProxy unit: Mixxx.EffectsManager.getEffectUnit(unitNumber) required property int unitNumber implicitHeight: effectContainer.height @@ -12,188 +16,142 @@ Item { Item { id: effectContainer - anchors.margins: 5 anchors.left: parent.left - anchors.top: parent.top + anchors.margins: 5 anchors.right: effectUnitControlsFrame.left - height: 60 + anchors.top: parent.top + height: root.expanded ? 160 : 60 Skin.EffectSlot { id: effect1 - anchors.top: parent.top anchors.left: parent.left - width: parent.width / 3 - unitNumber: root.unitNumber + anchors.top: parent.top effectNumber: 1 - expanded: false + expanded: root.expanded + height: 50 + unitNumber: root.unitNumber + width: root.expanded ? parent.width : parent.width / 3 } - Skin.EffectSlot { id: effect2 - anchors.top: parent.top - anchors.left: effect1.right - width: parent.width / 3 - unitNumber: root.unitNumber + anchors.left: root.expanded ? parent.left : effect1.right + anchors.top: root.expanded ? effect1.bottom : parent.top effectNumber: 2 - expanded: false + expanded: root.expanded + height: 50 + unitNumber: root.unitNumber + width: root.expanded ? parent.width : parent.width / 3 } - Skin.EffectSlot { id: effect3 - anchors.top: parent.top - anchors.left: effect2.right - width: parent.width / 3 - unitNumber: root.unitNumber + anchors.left: root.expanded ? parent.left : effect2.right + anchors.top: root.expanded ? effect2.bottom : parent.top effectNumber: 3 - expanded: false - } - - states: State { - when: expandButton.checked - name: "expanded" - - AnchorChanges { - target: effect1 - anchors.left: effectContainer.left - } - - AnchorChanges { - target: effect2 - anchors.left: effectContainer.left - anchors.top: effect1.bottom - } - - AnchorChanges { - target: effect3 - anchors.left: effectContainer.left - anchors.top: effect2.bottom - } - - PropertyChanges { - target: effect1 - width: parent.width - expanded: true - } - - PropertyChanges { - target: effect2 - width: parent.width - expanded: true - } - - PropertyChanges { - target: effect3 - width: parent.width - expanded: true - } - - PropertyChanges { - target: effectContainer - height: 160 - } - - PropertyChanges { - target: superKnob - visible: true - } - - PropertyChanges { - target: dryWetKnob - visible: true - } - } - - transitions: Transition { - AnchorAnimation { - targets: [effect1, effect2, effect3] - duration: 150 - } + expanded: root.expanded + height: 50 + unitNumber: root.unitNumber + width: root.expanded ? parent.width : parent.width / 3 } } - Rectangle { id: effectUnitControlsFrame + anchors.bottom: parent.bottom anchors.margins: 5 anchors.right: parent.right anchors.top: parent.top - anchors.bottom: parent.bottom - width: effectUnitControls.width color: Theme.knobBackgroundColor radius: 5 + width: 150 Column { - id: effectUnitControls - - anchors.top: parent.top - anchors.right: parent.right - padding: 5 - spacing: 10 - - Item { - width: 40 - height: width + anchors.fill: parent + anchors.margins: 5 + spacing: 5 - Skin.Button { - id: expandButton + Row { + spacing: 5 - anchors.fill: parent + Skin.ControlButton { activeColor: Theme.effectUnitColor - text: "▼" - checkable: true + group: root.group + height: 26 + key: "show_parameters" + text: root.expanded ? "▲" : "▼" + toggleable: true + width: 36 } - } - - Skin.ControlKnob { - id: superKnob - - height: 40 - width: height - arcStart: Knob.ArcStart.Minimum - group: "[EffectRack1_EffectUnit" + root.unitNumber + "]" - key: "super1" - color: Theme.effectUnitColor - visible: false + Skin.Button { + activeColor: Theme.effectUnitColor + height: 26 + text: "FX " + root.unitNumber + width: 56 - Skin.FadeBehavior on visible { - fadeTarget: superKnob + onClicked: presetPopup.open() } - } - - Skin.ControlKnob { - id: dryWetKnob - - height: 40 - width: height - arcStart: Knob.ArcStart.Minimum - group: "[EffectRack1_EffectUnit" + root.unitNumber + "]" - key: "mix" - color: Theme.effectUnitColor - visible: false - - Skin.FadeBehavior on visible { - fadeTarget: dryWetKnob + Skin.ControlButton { + activeColor: Theme.effectUnitColor + group: root.group + height: 26 + key: "group_[Headphone]_enable" + text: "PFL" + toggleable: true + width: 42 } } + Row { + spacing: 5 - add: Transition { - NumberAnimation { - property: "opacity" - from: 0 - to: 1 - duration: 150 + Skin.ControlButton { + activeColor: Theme.effectUnitColor + group: root.group + height: 30 + key: "mix_mode" + text: "D/W" + toggleable: true + width: 36 } - - NumberAnimation { - property: "scale" - from: 0 - to: 1 - duration: 150 + Skin.ControlKnob { + arcStart: Knob.ArcStart.Minimum + color: Theme.effectUnitColor + group: root.group + height: 40 + key: "mix" + width: 40 + } + Skin.ControlKnob { + arcStart: Knob.ArcStart.Minimum + color: Theme.effectUnitColor + group: root.group + height: 40 + key: "super1" + visible: showSuperKnobsControl.value > 0 + width: visible ? 40 : 0 } } } } + Skin.EffectChainPresetPopup { + id: presetPopup + + slot1: effect1.slot + slot2: effect2.slot + slot3: effect3.slot + unit: root.unit + } + Mixxx.ControlProxy { + id: expandedControl + + group: root.group + key: "show_parameters" + } + Mixxx.ControlProxy { + id: showSuperKnobsControl + + group: "[Skin]" + key: "show_superknobs" + } } diff --git a/res/skins/LateNightQML/Controls/Fader.qml b/res/skins/LateNightQML/Controls/Fader.qml new file mode 100644 index 000000000000..f2237dfa2b3d --- /dev/null +++ b/res/skins/LateNightQML/Controls/Fader.qml @@ -0,0 +1,37 @@ +import QtQuick +import "../../../qml" as Skin +import "../LateNightTheme" + +Skin.ControlFader { + id: root + + property int backgroundMargin: 0 + property alias backgroundSource: backgroundImage.source + property alias handleSource: handleImage.source + property color valueLineColor: LateNightTheme.mixerSliderBarColor + + bar.color: valueLineColor + bar.enabled: true + bar.margin: 8 + bar.width: 2 + implicitHeight: backgroundImage.implicitHeight + (backgroundMargin * 2) + implicitWidth: backgroundImage.implicitWidth + (backgroundMargin * 2) + showDefaultHandle: false + + background: Image { + id: backgroundImage + + anchors.fill: parent + anchors.margins: root.backgroundMargin + fillMode: Image.PreserveAspectFit + } + handle: Image { + id: handleImage + + fillMode: Image.PreserveAspectFit + height: implicitHeight + width: implicitWidth + x: root.horizontal ? root.visualPosition * (root.width - width) : (root.width - width) / 2 + y: root.vertical ? root.visualPosition * (root.height - height) : (root.height - height) / 2 + } +} diff --git a/res/skins/LateNightQML/Controls/ImageVuMeter.qml b/res/skins/LateNightQML/Controls/ImageVuMeter.qml new file mode 100644 index 000000000000..3d892afd237b --- /dev/null +++ b/res/skins/LateNightQML/Controls/ImageVuMeter.qml @@ -0,0 +1,88 @@ +import Mixxx 1.0 as Mixxx +import QtQuick +import "../LateNightTheme" + +Item { + id: root + + property int backgroundVariant: 0 + property real clipThreshold: 0.98 + readonly property bool clipping: peakIndicatorControl.value > 0 || levelControl.value >= clipThreshold + property bool drawGroove: true + required property string group + readonly property real level: Math.max(0, Math.min(1, levelControl.value)) + property string levelKey: "vu_meter" + property string peakKey: "peak_indicator" + readonly property string variantName: backgroundVariant === 0 ? "dark" : "light" + + clip: true + implicitHeight: 96 + implicitWidth: 8 + + Rectangle { + anchors.horizontalCenter: parent.horizontalCenter + color: "#040404" + height: parent.height + visible: root.drawGroove + width: 8 + } + Image { + anchors.horizontalCenter: parent.horizontalCenter + fillMode: Image.PreserveAspectFit + height: 11 + smooth: false + source: LateNightTheme.mixerVuClipBackground(root.variantName) + width: 6 + y: 1 + } + Image { + anchors.bottom: parent.bottom + anchors.bottomMargin: 1 + anchors.horizontalCenter: parent.horizontalCenter + fillMode: Image.PreserveAspectFit + height: 81 + smooth: false + source: LateNightTheme.mixerVuLevelBackground(root.variantName) + width: 6 + } + Image { + anchors.horizontalCenter: parent.horizontalCenter + fillMode: Image.PreserveAspectFit + height: 11 + opacity: root.clipping ? 1 : 0 + smooth: false + source: LateNightTheme.lateNightAsset("style", "vu_deck_clipping_active.png") + width: 6 + y: 1 + } + Item { + anchors.bottom: parent.bottom + anchors.bottomMargin: 1 + anchors.horizontalCenter: parent.horizontalCenter + clip: true + height: 81 * root.level + width: 6 + + Image { + anchors.bottom: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + fillMode: Image.PreserveAspectFit + height: 81 + smooth: false + source: LateNightTheme.lateNightAsset("style", "vu_deck_level_active.png") + width: 6 + } + } + Mixxx.ControlProxy { + id: levelControl + + group: root.group + key: root.levelKey + } + Mixxx.ControlProxy { + id: peakIndicatorControl + + group: root.group + key: root.peakKey + } +} diff --git a/res/skins/LateNightQML/Controls/Knob.qml b/res/skins/LateNightQML/Controls/Knob.qml new file mode 100644 index 000000000000..a3e55efe3452 --- /dev/null +++ b/res/skins/LateNightQML/Controls/Knob.qml @@ -0,0 +1,96 @@ +import QtQuick +import "../../../qml" as Skin +import "../LateNightTheme" + +Skin.ControlKnob { + id: root + + readonly property int arcRenderScale: 8 + property url backgroundSource: LateNightTheme.assetRegularKnobBackground + property bool displayArc: false + property color displayArcColor: "transparent" + property real displayArcOffsetY: 1.998 + property real displayArcRadius: 12.5 + property int displayArcStart: 1 // Knob.ArcStart.Center + property real displayArcWidth: 2 + property string indicatorColor: "orange" + property string indicatorKind: "regular" + + angle: LateNightTheme.isClassic ? 135 : 130 + arc: false + arcStart: displayArcStart + color: displayArcColor + implicitHeight: backgroundImage.implicitHeight + implicitWidth: backgroundImage.implicitWidth + knobCenterOffsetY: displayArcOffsetY + showDefaultBackground: false + showDefaultForeground: false + + background: Image { + id: backgroundImage + + anchors.fill: parent + fillMode: Image.PreserveAspectFit + source: root.backgroundSource + } + foreground: Item { + anchors.fill: parent + + Image { + anchors.fill: parent + fillMode: Image.PreserveAspectFit + source: LateNightTheme.mixerKnobIndicator(root.indicatorKind, root.indicatorColor) + } + } + + Canvas { + id: arcCanvas + + readonly property color renderedColor: root.displayArcColor + readonly property real renderedOffsetY: root.displayArcOffsetY + readonly property real renderedRadius: root.displayArcRadius + readonly property int renderedStart: root.displayArcStart + readonly property real renderedValue: root.value + readonly property real renderedWidth: root.displayArcWidth + + antialiasing: true + height: root.height * root.arcRenderScale + layer.enabled: true + layer.mipmap: true + renderStrategy: Canvas.Immediate + scale: 1 / root.arcRenderScale + transformOrigin: Item.TopLeft + visible: root.displayArc + width: root.width * root.arcRenderScale + z: 1 + + Component.onCompleted: requestPaint() + onPaint: { + const ctx = getContext("2d"); + ctx.clearRect(0, 0, width, height); + if (!root.displayArc) { + return; + } + + const renderScale = root.arcRenderScale; + const startAngle = root.angleFrom(root.arcStartValue - root.valueCenter) - 90; + const sweepAngle = root.angleFrom(root.value - root.arcStartValue); + const startRadians = startAngle * Math.PI / 180; + const endRadians = (startAngle + sweepAngle) * Math.PI / 180; + + ctx.beginPath(); + ctx.strokeStyle = root.displayArcColor; + ctx.lineWidth = root.displayArcWidth * renderScale; + ctx.lineCap = "round"; + ctx.arc((root.width / 2) * renderScale, (root.height / 2 + root.displayArcOffsetY) * renderScale, root.displayArcRadius * renderScale, startRadians, endRadians, sweepAngle < 0); + ctx.stroke(); + } + onRenderedColorChanged: requestPaint() + onRenderedOffsetYChanged: requestPaint() + onRenderedRadiusChanged: requestPaint() + onRenderedStartChanged: requestPaint() + onRenderedValueChanged: requestPaint() + onRenderedWidthChanged: requestPaint() + onVisibleChanged: requestPaint() + } +} diff --git a/res/skins/LateNightQML/Deck/FullDeck.qml b/res/skins/LateNightQML/Deck/FullDeck.qml index 712bce9066ed..12e39df58ad8 100644 --- a/res/skins/LateNightQML/Deck/FullDeck.qml +++ b/res/skins/LateNightQML/Deck/FullDeck.qml @@ -7,16 +7,13 @@ import "../LateNightTheme" Controls.Panel { id: root - implicitHeight: root.minimized ? 80 : 206 - implicitWidth: 620 - + property bool editMode: false required property string group property bool minimized: false - property bool editMode: false + readonly property bool show8Hotcues: show8HotcuesProxy.value > 0 readonly property bool showBeatjumpControls: showBeatjumpControlsProxy.value > 0 readonly property bool showBigSpinnyOrCover: selectBigSpinnyProxy.value > 0 readonly property bool showHotcues: showHotcuesProxy.value > 0 - readonly property bool show8Hotcues: show8HotcuesProxy.value > 0 readonly property bool showIntroOutroCues: showIntroOutroCuesProxy.value > 0 readonly property bool showKeyControls: showKeyControlsProxy.value > 0 readonly property bool showLoopControls: showLoopControlsProxy.value > 0 @@ -28,99 +25,103 @@ Controls.Panel { signal toggleFocus color: LateNightTheme.deckPanelColor + implicitHeight: root.minimized ? 80 : 206 + implicitWidth: 620 Mixxx.ControlProxy { id: selectBigSpinnyProxy + group: "[Skin]" key: "select_big_spinny_or_cover" } - Mixxx.ControlProxy { id: showKeyControlsProxy + group: "[Skin]" key: "show_key_controls" } - Mixxx.ControlProxy { id: showVinylControlsProxy + group: "[Skin]" key: "show_vinylcontrol" } - Mixxx.ControlProxy { id: show4EffectUnitsProxy + group: "[Skin]" key: "show_4effectunits" } - Mixxx.ControlProxy { id: showHotcuesProxy + group: "[Skin]" key: "show_hotcues" } - Mixxx.ControlProxy { id: show8HotcuesProxy + group: "[Skin]" key: "show_8_hotcues" } - Mixxx.ControlProxy { id: showIntroOutroCuesProxy + group: "[Skin]" key: "show_intro_outro_cues" } - Mixxx.ControlProxy { id: showLoopControlsProxy + group: "[Skin]" key: "show_loop_controls" } - Mixxx.ControlProxy { id: showBeatjumpControlsProxy + group: "[Skin]" key: "show_beatjump_controls" } - Mixxx.ControlProxy { id: showRateControlsProxy + group: "[Skin]" key: "show_rate_controls" } - Mixxx.ControlProxy { id: showRateControlButtonsProxy + group: "[Skin]" key: "show_rate_control_buttons" } - RowLayout { + anchors.bottomMargin: 2 anchors.fill: parent anchors.leftMargin: 1 - anchors.topMargin: 2 anchors.rightMargin: 1 - anchors.bottomMargin: 2 + anchors.topMargin: 2 spacing: 2 // Central main deck column ColumnLayout { id: mainDeckColumn - Layout.fillWidth: true - Layout.fillHeight: false + Layout.alignment: Qt.AlignTop + Layout.fillHeight: false + Layout.fillWidth: true spacing: 1 // Top row: FX assignment and key controls. RowLayout { id: topPlaceholderRow + + Layout.fillHeight: false Layout.fillWidth: true - Layout.preferredHeight: 20 - Layout.minimumHeight: 20 Layout.maximumHeight: 20 - Layout.fillHeight: false - visible: !root.minimized + Layout.minimumHeight: 20 + Layout.preferredHeight: 20 spacing: 0 + visible: !root.minimized // FX assignment buttons: toggle effect unit assignment for this deck Row { @@ -132,53 +133,47 @@ Controls.Panel { delegate: Item { id: fxAssignButton + readonly property bool active: fxAssignProxy.value > 0 + readonly property color activeColor: index < 2 ? (LateNightTheme.isClassic ? LateNightTheme.effectsUnitColor12 : LateNightTheme.effectsUnitDimColor12) : (LateNightTheme.isClassic ? LateNightTheme.effectsUnitColor34 : LateNightTheme.effectsUnitDimColor34) + readonly property color fillColor: active ? activeColor : inactiveColor + readonly property color inactiveColor: LateNightTheme.deckEmbeddedButtonInactiveColor required property int index - width: show4EffectUnitsProxy.value > 0 && index > 0 ? 20 : 26 height: 20 - readonly property bool active: fxAssignProxy.value > 0 - readonly property color activeColor: index < 2 ? "#236b00" : "#146674" - readonly property color inactiveColor: LateNightTheme.deckEmbeddedButtonInactiveColor - readonly property color fillColor: active ? activeColor : inactiveColor + width: show4EffectUnitsProxy.value > 0 && index > 0 ? 20 : 26 Mixxx.ControlProxy { id: fxAssignProxy + group: `[EffectRack1_EffectUnit${index + 1}]` key: `group_${root.group}_enable` } - Rectangle { anchors.fill: parent color: fxAssignButton.fillColor } - Image { anchors.fill: parent + fillMode: Image.Stretch source: { if (index === 0) { - return fxAssignButton.active - ? LateNightTheme.lateNightButton("btn_embedded_library_active.svg") - : LateNightTheme.lateNightButton("btn_embedded_library.svg"); + return fxAssignButton.active ? LateNightTheme.lateNightButton("btn_embedded_library_active.svg") : LateNightTheme.lateNightButton("btn_embedded_library.svg"); } else { - return fxAssignButton.active - ? LateNightTheme.lateNightButton("btn_embedded_grid_active.svg") - : LateNightTheme.lateNightButton("btn_embedded_grid.svg"); + return fxAssignButton.active ? LateNightTheme.lateNightButton("btn_embedded_grid_active.svg") : LateNightTheme.lateNightButton("btn_embedded_grid.svg"); } } - fillMode: Image.Stretch } - Text { anchors.centerIn: parent - text: show4EffectUnitsProxy.value > 0 && index > 0 ? (index + 1).toString() : "FX" + (show4EffectUnitsProxy.value > 0 && index === 0 ? "1" : (index + 1).toString()) + color: fxAssignButton.active ? (LateNightTheme.isClassic ? "#000000" : LateNightTheme.mixerControlTextColor) : (LateNightTheme.isClassic ? "#d2d2d1" : "#666666") + font.bold: true font.family: "Open Sans" font.pixelSize: 10 - font.bold: true - color: fxAssignButton.active ? "#000000" : "#666666" + text: show4EffectUnitsProxy.value > 0 && index > 0 ? (index + 1).toString() : "FX" + (show4EffectUnitsProxy.value > 0 && index === 0 ? "1" : (index + 1).toString()) } - MouseArea { anchors.fill: parent + onClicked: { fxAssignProxy.value = !fxAssignProxy.value; } @@ -186,39 +181,35 @@ Controls.Panel { } } } - Item { - Layout.fillWidth: true Layout.fillHeight: true + Layout.fillWidth: true Rectangle { + anchors.bottom: parent.bottom anchors.left: parent.left anchors.right: parent.right - anchors.bottom: parent.bottom - height: 1 color: LateNightTheme.deckPanelBorderDark + height: 1 } } - VinylControlsPlaceholder { - Layout.preferredWidth: 158 - Layout.preferredHeight: 20 Layout.maximumHeight: 20 + Layout.preferredHeight: 20 + Layout.preferredWidth: 158 group: root.group visible: root.showVinylControls } - Item { - Layout.preferredWidth: root.showVinylControls ? 2 : 0 Layout.fillHeight: true + Layout.preferredWidth: root.showVinylControls ? 2 : 0 visible: root.showVinylControls } - KeyControlsPlaceholder { - Layout.preferredWidth: 111 + Layout.maximumHeight: 20 Layout.maximumWidth: 111 Layout.preferredHeight: 20 - Layout.maximumHeight: 20 + Layout.preferredWidth: 111 group: root.group visible: root.showKeyControls } @@ -227,16 +218,18 @@ Controls.Panel { // Middle Row: Big Spinny on the left, Title/Overview on the right RowLayout { id: middleDeckRow - Layout.fillWidth: true + Layout.fillHeight: false + Layout.fillWidth: true + Layout.maximumHeight: root.minimized ? 68 : 122 Layout.minimumHeight: root.minimized ? 68 : 122 Layout.preferredHeight: root.minimized ? 68 : 122 - Layout.maximumHeight: root.minimized ? 68 : 122 spacing: 8 // Big Spinny/Cover Slot (Large mode) SpinnyCoverSlot { id: leftSpinnyBig + Layout.preferredHeight: 114 Layout.preferredWidth: 114 group: root.group @@ -246,18 +239,20 @@ Controls.Panel { // Column containing Title rows and Overview row ColumnLayout { id: titleOverviewColumn - Layout.fillWidth: true + Layout.fillHeight: false + Layout.fillWidth: true Layout.preferredHeight: root.minimized ? 68 : 122 spacing: 2 // Title, Time, Artist, Duration Rows TitleTimeRows { id: titleTimeRows + Layout.fillWidth: true + Layout.maximumHeight: root.minimized ? 48 : 55 Layout.minimumHeight: root.minimized ? 48 : 55 Layout.preferredHeight: root.minimized ? 48 : 55 - Layout.maximumHeight: root.minimized ? 48 : 55 group: root.group TapHandler { @@ -268,16 +263,18 @@ Controls.Panel { // Row containing Small Spinny (on the left of overview) and the Waveform Overview RowLayout { id: overviewAndSpinnyRow - Layout.fillWidth: true + Layout.fillHeight: false + Layout.fillWidth: true + Layout.maximumHeight: root.minimized ? 20 : 63 Layout.minimumHeight: root.minimized ? 20 : 63 Layout.preferredHeight: root.minimized ? 20 : 63 - Layout.maximumHeight: root.minimized ? 20 : 63 spacing: 1 // Small Spinny/Cover Slot (Small mode) SpinnyCoverSlot { id: leftSpinnySmall + Layout.preferredHeight: 63 Layout.preferredWidth: 63 group: root.group @@ -287,8 +284,9 @@ Controls.Panel { // Waveform Overview Row OverviewRow { id: overviewRow - Layout.fillWidth: true + Layout.fillHeight: true + Layout.fillWidth: true group: root.group } } @@ -298,17 +296,18 @@ Controls.Panel { // Lower Transport, Loop, Beatjump Placeholders TransportLoopBeatjumpPlaceholders { id: transportRow - Layout.fillWidth: true + Layout.fillHeight: false + Layout.fillWidth: true + Layout.maximumHeight: 55 Layout.minimumHeight: 55 Layout.preferredHeight: 55 - Layout.maximumHeight: 55 group: root.group - showHotcues: root.showHotcues show8Hotcues: root.show8Hotcues + showBeatjumpControls: root.showBeatjumpControls + showHotcues: root.showHotcues showIntroOutroCues: root.showIntroOutroCues showLoopControls: root.showLoopControls - showBeatjumpControls: root.showBeatjumpControls visible: !root.minimized } } @@ -316,21 +315,20 @@ Controls.Panel { // Right Rate controls placeholder RatePlaceholder { id: rateControls - Layout.preferredWidth: 90 + + Layout.alignment: Qt.AlignTop Layout.fillHeight: false + Layout.maximumHeight: 202 Layout.minimumHeight: 202 Layout.preferredHeight: 202 - Layout.maximumHeight: 202 - Layout.alignment: Qt.AlignTop + Layout.preferredWidth: 90 group: root.group showRateControlButtons: root.showRateControlButtons visible: !root.minimized && root.showRateControls } } - Mixxx.PlayerDropArea { anchors.fill: parent group: root.group } - } diff --git a/res/skins/LateNightQML/Effects/EffectControlButton.qml b/res/skins/LateNightQML/Effects/EffectControlButton.qml new file mode 100644 index 000000000000..d182c3db13f5 --- /dev/null +++ b/res/skins/LateNightQML/Effects/EffectControlButton.qml @@ -0,0 +1,51 @@ +import Mixxx 1.0 as Mixxx +import QtQuick + +Item { + id: root + + readonly property bool active: control.value > 0 + property url activeBackgroundSource: backgroundSource + property color activeColor: "#888888" + property url activeSource: normalSource + property int backgroundInset: 2 + property url backgroundSource + required property string group + required property string key + property color normalColor: "#262626" + property url normalSource + property bool toggleable: true + + Rectangle { + anchors.fill: parent + anchors.margins: root.backgroundInset + color: root.active ? root.activeColor : root.normalColor + } + Image { + anchors.fill: parent + fillMode: Image.Stretch + source: root.active ? root.activeBackgroundSource : root.backgroundSource + } + Image { + anchors.fill: parent + fillMode: Image.Stretch + source: root.active ? root.activeSource : root.normalSource + } + MouseArea { + anchors.fill: parent + + onClicked: { + if (root.toggleable) { + control.value = root.active ? 0 : 1; + } else { + control.value = 1; + } + } + } + Mixxx.ControlProxy { + id: control + + group: root.group + key: root.key + } +} diff --git a/res/skins/LateNightQML/Effects/EffectFocusButton.qml b/res/skins/LateNightQML/Effects/EffectFocusButton.qml new file mode 100644 index 000000000000..702fdea50061 --- /dev/null +++ b/res/skins/LateNightQML/Effects/EffectFocusButton.qml @@ -0,0 +1,34 @@ +import Mixxx 1.0 as Mixxx +import QtQuick +import "../LateNightTheme" + +Item { + id: root + + required property int effectNumber + readonly property bool focused: Math.round(focusedEffect.value) === effectNumber + required property string unitGroup + + Image { + anchors.fill: parent + source: root.focused ? LateNightTheme.assetFxFocusActiveButton : LateNightTheme.assetFxFocusButton + } + MouseArea { + acceptedButtons: Qt.LeftButton | Qt.RightButton + anchors.fill: parent + + onClicked: mouse => { + if (mouse.button === Qt.RightButton) { + focusedEffect.value = 0; + } else { + focusedEffect.value = root.focused ? 0 : root.effectNumber; + } + } + } + Mixxx.ControlProxy { + id: focusedEffect + + group: root.unitGroup + key: "focused_effect" + } +} diff --git a/res/skins/LateNightQML/Effects/EffectParameter.qml b/res/skins/LateNightQML/Effects/EffectParameter.qml new file mode 100644 index 000000000000..840b5774b639 --- /dev/null +++ b/res/skins/LateNightQML/Effects/EffectParameter.qml @@ -0,0 +1,170 @@ +import Mixxx 1.0 as Mixxx +import QtQuick +import "../Controls" as LateNightControls +import "../LateNightTheme" + +Item { + id: root + + required property bool buttonParameter + required property string controlKey + required property string group + required property string label + required property color linkColor + required property color unitColor + + implicitHeight: 45 + implicitWidth: buttonParameter ? 55 : 40 + + EffectControlButton { + activeColor: LateNightTheme.effectsParameterActiveColor + activeSource: LateNightTheme.assetFxParameterActiveButton + anchors.horizontalCenter: parent.horizontalCenter + group: root.group + height: 20 + key: root.controlKey + normalColor: LateNightTheme.effectsParameterInactiveColor + normalSource: LateNightTheme.assetFxParameterButton + visible: root.buttonParameter + width: 35 + y: 5 + } + LateNightControls.Knob { + anchors.horizontalCenter: parent.horizontalCenter + backgroundSource: LateNightTheme.assetFxKnobBackground + displayArc: true + displayArcColor: LateNightTheme.effectsParameterArcColor + displayArcRadius: 12 + displayArcStart: LateNightControls.Knob.ArcStart.Minimum + group: root.group + height: 26 + indicatorColor: LateNightTheme.isClassic ? "white" : "grey" + indicatorKind: "fx" + key: root.controlKey + visible: !root.buttonParameter + width: 26 + } + Text { + anchors.horizontalCenter: parent.horizontalCenter + color: "#666666" + elide: Text.ElideRight + font.pixelSize: 10 + height: 10 + horizontalAlignment: Text.AlignHCenter + text: root.label + verticalAlignment: Text.AlignVCenter + width: root.width + y: 28 + } + Row { + anchors.horizontalCenter: parent.horizontalCenter + height: 7 + spacing: 1 + visible: !root.buttonParameter + y: 38 + + Rectangle { + color: inverseControl.item && inverseControl.item.value > 0 ? "#9c0900" : (LateNightTheme.isClassic ? "#4b4b4b" : "#333333") + height: 7 + radius: 3 + width: 8 + + MouseArea { + anchors.fill: parent + + onClicked: { + if (inverseControl.item) { + inverseControl.item.value = inverseControl.item.value > 0 ? 0 : 1; + } + } + } + } + Rectangle { + id: linkBar + + readonly property color backgroundColor: LateNightTheme.isClassic ? "#4b4b4b" : "#333333" + readonly property color leftColor: state === 1 || state === 2 || state === 4 ? root.linkColor : backgroundColor + readonly property color middleColor: state === 1 ? root.linkColor : backgroundColor + readonly property color rightColor: state === 1 || state === 3 || state === 4 ? root.linkColor : backgroundColor + readonly property int state: linkControl.item ? Math.round(linkControl.item.value) : 0 + + height: 7 + radius: 3 + width: 34 + + gradient: Gradient { + orientation: Gradient.Horizontal + + GradientStop { + color: linkBar.leftColor + position: 0 + } + GradientStop { + color: linkBar.leftColor + position: 0.33 + } + GradientStop { + color: linkBar.middleColor + position: 0.34 + } + GradientStop { + color: linkBar.middleColor + position: 0.66 + } + GradientStop { + color: linkBar.rightColor + position: 0.67 + } + GradientStop { + color: linkBar.rightColor + position: 1 + } + } + + MouseArea { + anchors.fill: parent + + onClicked: { + if (linkControl.item) { + linkControl.item.value = (Math.round(linkControl.item.value) + 1) % 5; + } + } + } + } + } + Loader { + id: inverseControl + + active: !root.buttonParameter + + sourceComponent: ParameterControlProxy { + group: root.group + key: root.controlKey + "_link_inverse" + } + } + Loader { + id: linkControl + + active: !root.buttonParameter + + sourceComponent: ParameterControlProxy { + group: root.group + key: root.controlKey + "_link_type" + } + } + + component ParameterControlProxy: Item { + id: proxyRoot + + required property string group + required property string key + property alias value: control.value + + Mixxx.ControlProxy { + id: control + + group: proxyRoot.group + key: proxyRoot.key + } + } +} diff --git a/res/skins/LateNightQML/Effects/EffectSelector.qml b/res/skins/LateNightQML/Effects/EffectSelector.qml new file mode 100644 index 000000000000..23803ce48ed7 --- /dev/null +++ b/res/skins/LateNightQML/Effects/EffectSelector.qml @@ -0,0 +1,132 @@ +import Mixxx 1.0 as Mixxx +import QtQuick +import QtQuick.Controls +import "../LateNightTheme" + +ComboBox { + id: root + + property int popupMaxItem: 44 + property int popupWidth: 160 + required property Mixxx.EffectSlotProxy slot + + function syncCurrentEffect() { + for (let index = 0; index < count; ++index) { + if (model.get(index).effectId === slot.effectId) { + currentIndex = index; + return; + } + } + currentIndex = 0; + } + + font.family: "Open Sans" + font.pixelSize: LateNightTheme.isClassic ? 12 : 14 + font.weight: LateNightTheme.isClassic ? Font.Bold : Font.Medium + implicitHeight: 24 + model: Mixxx.EffectsManager.visibleEffectsModel + textRole: "display" + + background: BorderImage { + border.bottom: 2 + border.left: 2 + border.right: 2 + border.top: 2 + horizontalTileMode: BorderImage.Stretch + source: root.popup.visible ? LateNightTheme.assetFxSelectorActiveBorder : LateNightTheme.assetFxSelectorBorder + verticalTileMode: BorderImage.Stretch + } + contentItem: Text { + color: LateNightTheme.mixerQuickEffectSelectorTextColor + elide: Text.ElideRight + font.pixelSize: 11 + leftPadding: 5 + rightPadding: 18 + text: root.displayText + verticalAlignment: Text.AlignVCenter + } + delegate: ItemDelegate { + id: effectDelegate + + required property int index + + checkable: false + checked: root.currentIndex === index + height: 20 + highlighted: root.highlightedIndex === index + padding: 0 + width: ListView.view ? ListView.view.width : root.popupWidth + + background: Rectangle { + color: effectDelegate.highlighted ? (LateNightTheme.isClassic ? "#5e4507" : "#2c454f") : "transparent" + radius: effectDelegate.highlighted ? 1 : 0 + } + contentItem: Text { + color: effectDelegate.checked || effectDelegate.highlighted ? "#ffffff" : LateNightTheme.mixerQuickEffectSelectorTextColor + elide: Text.ElideRight + font: root.font + leftPadding: 20 + rightPadding: 4 + text: root.textAt(effectDelegate.index) + verticalAlignment: Text.AlignVCenter + } + } + indicator: Image { + anchors.right: parent.right + anchors.rightMargin: 3 + anchors.verticalCenter: parent.verticalCenter + height: 8 + source: LateNightTheme.assetFxSelectorDownButton + width: 12 + } + popup: Popup { + height: Math.min(contentItem.contentHeight, root.popupMaxItem * 20) + 2 + padding: 1 + width: root.popupWidth + x: 0 + y: root.height + + background: Rectangle { + border.color: LateNightTheme.isClassic ? "#888888" : "#333333" + border.width: 1 + color: LateNightTheme.isClassic ? "#0f0f0f" : "#151517" + radius: LateNightTheme.isClassic ? 2 : 1 + } + contentItem: ListView { + id: effectList + + clip: true + currentIndex: root.highlightedIndex + implicitHeight: contentHeight + model: root.popup.visible ? root.delegateModel : null + + ScrollBar.vertical: ScrollBar { + policy: ScrollBar.AlwaysOff + } + } + + onOpened: effectList.contentY = 0 + } + + Component.onCompleted: syncCurrentEffect() + onActivated: index => { + const effectId = model.get(index).effectId || ""; + if (slot.effectId !== effectId) { + slot.effectId = effectId; + } + } + onCountChanged: syncCurrentEffect() + + MouseArea { + anchors.fill: parent + + onClicked: root.popup.open() + } + Connections { + function onEffectIdChanged() { + root.syncCurrentEffect(); + } + + target: root.slot + } +} diff --git a/res/skins/LateNightQML/Effects/EffectSlot.qml b/res/skins/LateNightQML/Effects/EffectSlot.qml new file mode 100644 index 000000000000..b64ba3de5c0c --- /dev/null +++ b/res/skins/LateNightQML/Effects/EffectSlot.qml @@ -0,0 +1,270 @@ +pragma ComponentBehavior: Bound + +import Mixxx 1.0 as Mixxx +import QtQuick +import "../Controls" as LateNightControls +import "../LateNightTheme" + +Item { + id: root + + property int activeButtonParameterCount: 0 + property int activeKnobParameterCount: 0 + readonly property real buttonParameterWidth: 55 + parameterInitialGrowth + required property int effectNumber + property bool expanded: false + readonly property real knobParameterWidth: 40 + parameterInitialGrowth + (activeKnobParameterCount > 0 ? Math.max(0, parameterExtraWidth - parameterInitialGrowth * (activeButtonParameterCount + activeKnobParameterCount)) / activeKnobParameterCount : 0) + readonly property real parameterExtraWidth: Math.max(0, Math.min(parametersFlickable.width - parameterMinimumWidth, activeButtonParameterCount * 5 + activeKnobParameterCount * 20)) + readonly property real parameterInitialGrowth: activeButtonParameterCount + activeKnobParameterCount > 0 ? Math.min(5, parameterExtraWidth / (activeButtonParameterCount + activeKnobParameterCount)) : 0 + readonly property real parameterMinimumWidth: activeButtonParameterCount * 55 + activeKnobParameterCount * 40 + readonly property Mixxx.EffectSlotProxy slot: Mixxx.EffectsManager.getEffectSlot(unitNumber, effectNumber) + required property color unitColor + readonly property color unitDimColor: unitNumber < 3 ? LateNightTheme.effectsUnitDimColor12 : LateNightTheme.effectsUnitDimColor34 + required property string unitGroup + required property int unitNumber + + function recountActiveParameters() { + let buttonCount = 0; + for (let index = 0; index < buttonRepeater.count; ++index) { + const loader = buttonRepeater.itemAt(index); + if (loader && loader.active) { + ++buttonCount; + } + } + let knobCount = 0; + for (let index = 0; index < knobRepeater.count; ++index) { + const loader = knobRepeater.itemAt(index); + if (loader && loader.active) { + ++knobCount; + } + } + activeButtonParameterCount = buttonCount; + activeKnobParameterCount = knobCount; + } + + implicitHeight: expanded ? 51 : 34 + + Rectangle { + anchors.fill: parent + color: "transparent" + } + Rectangle { + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: slotControls.left + anchors.top: parent.top + border.color: showFocus.value > 0 && Math.round(focusedEffect.value) === root.effectNumber ? LateNightTheme.effectsFocusBorderColor : (LateNightTheme.isClassic ? LateNightTheme.deckPanelBorderDark : "transparent") + border.width: 1 + color: LateNightTheme.effectsParameterPanelColor + visible: root.expanded + + Image { + anchors.fill: parent + anchors.margins: 1 + fillMode: Image.Tile + source: LateNightTheme.optionalDeckControlsBackgroundTile + visible: LateNightTheme.isClassic + } + Rectangle { + anchors.fill: parent + anchors.margins: 1 + color: "#32000000" + visible: showFocus.value > 0 && Math.round(focusedEffect.value) === root.effectNumber + } + } + Item { + id: slotControls + + anchors.right: parent.right + height: 30 + width: root.expanded ? 182 : parent.width + y: Math.round((parent.height - height) / 2) + + EffectFocusButton { + anchors.left: parent.left + anchors.leftMargin: 2 + anchors.verticalCenter: parent.verticalCenter + effectNumber: root.effectNumber + height: 16 + unitGroup: root.unitGroup + visible: showFocus.value > 0 + width: 16 + } + EffectControlButton { + id: enableButton + + activeBackgroundSource: LateNightTheme.assetFxSlotButtonActiveBackground + activeColor: root.unitColor + activeSource: LateNightTheme.assetFxToggleActiveButton + anchors.left: parent.left + anchors.leftMargin: showFocus.value > 0 ? 20 : 2 + anchors.verticalCenter: parent.verticalCenter + backgroundSource: LateNightTheme.assetFxSlotButtonBackground + group: root.slot.group + height: 26 + key: "enabled" + normalColor: LateNightTheme.effectsSlotToggleInactiveColor + normalSource: LateNightTheme.assetFxToggleButton + width: 26 + } + LateNightControls.Knob { + id: metaKnob + + anchors.left: enableButton.right + anchors.leftMargin: 2 + anchors.verticalCenter: parent.verticalCenter + backgroundSource: LateNightTheme.assetSmallKnobBackground + displayArc: true + displayArcColor: root.unitColor + displayArcRadius: 11.5 + displayArcStart: LateNightControls.Knob.ArcStart.Minimum + group: root.slot.group + height: 30 + indicatorColor: root.unitNumber < 3 ? "green" : "blue" + indicatorKind: "small" + key: "meta" + width: 35 + } + EffectSelector { + id: effectSelector + + anchors.left: metaKnob.right + anchors.leftMargin: 2 + anchors.right: parent.right + anchors.rightMargin: 2 + anchors.verticalCenter: parent.verticalCenter + height: 24 + slot: root.slot + } + } + Flickable { + id: parametersFlickable + + anchors.bottom: parent.bottom + anchors.bottomMargin: 1 + anchors.left: parent.left + anchors.right: slotControls.left + anchors.top: parent.top + anchors.topMargin: 3 + clip: true + contentHeight: height + contentWidth: Math.max(width, parameterRow.implicitWidth) + flickableDirection: Flickable.HorizontalFlick + visible: root.expanded + + Row { + id: parameterRow + + height: parent.height + width: implicitWidth + x: Math.max(0, parametersFlickable.width - width) + + Repeater { + id: buttonRepeater + + model: root.slot.parametersModel + + delegate: Loader { + id: buttonLoader + + property bool completed: false + required property string controlKey + required property bool loaded + required property string name + required property string shortName + required property int type + + function updateActive() { + if (completed) { + active = loaded && controlKey.length > 0 && type === 1; + Qt.callLater(root.recountActiveParameters); + } + } + + active: false + height: active && item ? item.implicitHeight : 0 + visible: active + width: active && item ? root.buttonParameterWidth : 0 + + sourceComponent: EffectParameter { + buttonParameter: true + controlKey: buttonLoader.controlKey + group: root.slot.group + label: buttonLoader.shortName || buttonLoader.name + linkColor: root.unitDimColor + unitColor: root.unitColor + } + + Component.onCompleted: { + completed = true; + updateActive(); + } + onControlKeyChanged: updateActive() + onLoadedChanged: updateActive() + onTypeChanged: updateActive() + } + + onCountChanged: Qt.callLater(root.recountActiveParameters) + } + Repeater { + id: knobRepeater + + model: root.slot.parametersModel + + delegate: Loader { + id: knobLoader + + property bool completed: false + required property string controlKey + required property bool loaded + required property string name + required property string shortName + required property int type + + function updateActive() { + if (completed) { + active = loaded && controlKey.length > 0 && type === 0; + Qt.callLater(root.recountActiveParameters); + } + } + + active: false + height: active && item ? item.implicitHeight : 0 + visible: active + width: active && item ? root.knobParameterWidth : 0 + + sourceComponent: EffectParameter { + buttonParameter: false + controlKey: knobLoader.controlKey + group: root.slot.group + label: knobLoader.shortName || knobLoader.name + linkColor: root.unitDimColor + unitColor: root.unitColor + } + + Component.onCompleted: { + completed = true; + updateActive(); + } + onControlKeyChanged: updateActive() + onLoadedChanged: updateActive() + onTypeChanged: updateActive() + } + + onCountChanged: Qt.callLater(root.recountActiveParameters) + } + } + } + Mixxx.ControlProxy { + id: focusedEffect + + group: root.unitGroup + key: "focused_effect" + } + Mixxx.ControlProxy { + id: showFocus + + group: root.unitGroup + key: "show_focus" + } +} diff --git a/res/skins/LateNightQML/Effects/EffectUnit.qml b/res/skins/LateNightQML/Effects/EffectUnit.qml new file mode 100644 index 000000000000..1957e323f508 --- /dev/null +++ b/res/skins/LateNightQML/Effects/EffectUnit.qml @@ -0,0 +1,352 @@ +import Mixxx 1.0 as Mixxx +import QtQuick +import "../../../qml" as Skin +import "../Controls" as LateNightControls +import "../LateNightTheme" +import "../Mixer" as LateNightMixer + +Item { + id: root + + readonly property real collapsedChainWidth: collapsedSlotWidth * 3 + flowWidth * 2 + readonly property real collapsedSlotWidth: Math.min(182, (slots.width - flowWidth * 2) / 3) + readonly property color controllerColor: unitNumber < 3 ? LateNightTheme.effectsControllerColor12 : LateNightTheme.effectsControllerColor34 + readonly property bool expanded: expandedControl.value > 0 + readonly property int flowWidth: LateNightTheme.isPaleMoon ? 12 : 3 + readonly property string group: unit.group + readonly property int headerWidth: expanded ? 60 : 48 + readonly property int masterWidth: expanded ? 60 : (showSuper.value > 0 ? 160 : 123) + readonly property Mixxx.EffectUnitProxy unit: Mixxx.EffectsManager.getEffectUnit(unitNumber) + readonly property color unitColor: unitNumber < 3 ? LateNightTheme.effectsUnitColor12 : LateNightTheme.effectsUnitColor34 + required property int unitNumber + + implicitHeight: expanded ? 154 : 38 + + Rectangle { + anchors.fill: parent + border.color: LateNightTheme.deckPanelBorderDark + border.width: 1 + color: LateNightTheme.effectsPanelColor + radius: 1 + } + Item { + id: slots + + height: parent.height + width: parent.width - (root.expanded ? root.masterWidth : root.masterWidth + root.headerWidth) + x: 0 + y: 0 + + Image { + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.top: parent.top + fillMode: Image.Stretch + source: LateNightTheme.assetFxFlowVertical + visible: root.expanded && LateNightTheme.isPaleMoon + width: 90 + } + EffectSlot { + id: slot1 + + effectNumber: 1 + expanded: root.expanded + height: root.expanded ? 51 : 34 + unitColor: root.unitColor + unitGroup: root.group + unitNumber: root.unitNumber + width: root.expanded ? parent.width : root.collapsedSlotWidth + x: root.expanded ? 0 : parent.width - root.collapsedChainWidth + y: root.expanded ? 0 : 2 + } + EffectSlot { + id: slot2 + + effectNumber: 2 + expanded: root.expanded + height: root.expanded ? 51 : 34 + unitColor: root.unitColor + unitGroup: root.group + unitNumber: root.unitNumber + width: slot1.width + x: root.expanded ? 0 : slot1.x + slot1.width + root.flowWidth + y: root.expanded ? 52 : 2 + } + EffectSlot { + id: slot3 + + effectNumber: 3 + expanded: root.expanded + height: root.expanded ? 50 : 34 + unitColor: root.unitColor + unitGroup: root.group + unitNumber: root.unitNumber + width: slot1.width + x: root.expanded ? 0 : slot2.x + slot2.width + root.flowWidth + y: root.expanded ? 104 : 2 + } + Image { + fillMode: Image.PreserveAspectFit + height: 34 + source: LateNightTheme.assetFxFlowHorizontal + visible: !root.expanded + width: root.flowWidth + x: slot1.x + slot1.width + y: 2 + } + Image { + fillMode: Image.PreserveAspectFit + height: 34 + source: LateNightTheme.assetFxFlowHorizontal + visible: !root.expanded + width: root.flowWidth + x: slot2.x + slot2.width + y: 2 + } + Item { + height: 2 + visible: root.expanded + width: Math.max(0, parent.width - 185) + y: 51 + + Rectangle { + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + color: LateNightTheme.deckPanelBorderDark + height: 1 + } + Rectangle { + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + color: LateNightTheme.deckPanelBorderLight + height: 1 + } + } + Item { + height: 2 + visible: root.expanded + width: Math.max(0, parent.width - 185) + y: 103 + + Rectangle { + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + color: LateNightTheme.deckPanelBorderDark + height: 1 + } + Rectangle { + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + color: LateNightTheme.deckPanelBorderLight + height: 1 + } + } + } + Item { + id: masterControls + + height: root.expanded ? root.height - 35 : root.height + width: root.masterWidth + x: root.expanded ? root.width - width : root.width - root.headerWidth - width + y: root.expanded ? 35 : 0 + + Rectangle { + anchors.fill: parent + border.color: LateNightTheme.deckPanelBorderDark + border.width: 1 + color: LateNightTheme.effectsPanelColor + } + EffectControlButton { + id: mixMode + + activeColor: LateNightTheme.effectsMasterButtonInactiveColor + activeSource: LateNightTheme.assetFxMixModeButton + group: root.group + height: 26 + key: "mix_mode" + normalColor: LateNightTheme.effectsMasterButtonInactiveColor + normalSource: LateNightTheme.assetFxMixModeButton + width: 32 + x: root.expanded ? 1 : presetButton.x + presetButton.width + 2 + y: root.expanded ? 2 : 6 + } + Image { + fillMode: Image.PreserveAspectFit + height: mixMode.height + source: mixMode.active ? LateNightTheme.assetFxMixModeDryWetSumButton : LateNightTheme.assetFxMixModeDryWetButton + width: mixMode.width + x: mixMode.x + y: mixMode.y + } + LateNightMixer.PflButton { + group: root.group + height: 26 + key: "group_[Headphone]_enable" + width: 26 + x: mixMode.x + mixMode.width + y: mixMode.y + } + Item { + id: presetButton + + height: 22 + width: 22 + x: root.expanded ? 19 : mixKnob.x + mixKnob.width + 2 + y: root.expanded ? (showSuper.value > 0 ? 30 : 43) : 8 + + Image { + anchors.fill: parent + source: LateNightTheme.assetFxSettingsButton + } + MouseArea { + anchors.fill: parent + + onClicked: presetPopup.open() + } + } + LateNightControls.Knob { + id: mixKnob + + backgroundSource: LateNightTheme.assetSmallKnobBackground + displayArc: true + displayArcColor: LateNightTheme.mixerAccentRed + displayArcRadius: 11.5 + displayArcStart: LateNightControls.Knob.ArcStart.Minimum + group: root.group + height: 30 + indicatorColor: "red" + indicatorKind: "small" + key: "mix" + width: 35 + x: root.expanded ? 12 : (showSuper.value > 0 ? 39 : 2) + y: root.expanded ? (showSuper.value > 0 ? 54 : 76) : 4 + } + LateNightControls.Knob { + backgroundSource: LateNightTheme.assetSmallKnobBackground + displayArc: true + displayArcColor: root.unitColor + displayArcRadius: 11.5 + displayArcStart: LateNightControls.Knob.ArcStart.Minimum + group: root.group + height: 30 + indicatorColor: root.unitNumber < 3 ? "green" : "blue" + indicatorKind: "small" + key: "super1" + visible: showSuper.value > 0 + width: visible ? 35 : 0 + x: root.expanded ? 12 : 2 + y: root.expanded ? 86 : 4 + } + } + Rectangle { + anchors.fill: parent + border.color: LateNightTheme.deckPanelBorderDark + border.width: 1 + color: "transparent" + radius: 1 + } + Item { + id: header + + height: root.expanded ? 35 : root.height + width: root.headerWidth + x: root.width - width + y: 0 + + Rectangle { + anchors.fill: parent + border.color: LateNightTheme.deckPanelBorderDark + border.width: 1 + color: LateNightTheme.effectsHeaderColor + radius: 1 + } + Rectangle { + color: root.controllerColor + height: parent.height - 4 + visible: controllerActive.value > 0 + width: 2 + x: 1 + y: 2 + } + Text { + color: controllerActive.value > 0 ? root.controllerColor : "#686666" + font.family: "Open Sans" + font.pixelSize: 16 + font.weight: Font.Medium + height: parent.height + horizontalAlignment: Text.AlignLeft + text: "FX\u200a" + root.unitNumber + verticalAlignment: Text.AlignVCenter + width: 31 + x: root.expanded ? 8 : 5 + } + Image { + fillMode: Image.PreserveAspectFit + height: 18 + source: root.expanded ? LateNightTheme.assetFxCollapseButton : LateNightTheme.assetFxExpandButton + width: 16 + x: parent.width - width - 1 + y: Math.round((parent.height - height) / 2) + } + Rectangle { + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + color: root.controllerColor + height: 1 + visible: controllerActive.value > 0 + } + Rectangle { + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.top: parent.top + color: root.controllerColor + visible: controllerActive.value > 0 + width: 1 + } + MouseArea { + anchors.fill: parent + + onClicked: expandedControl.value = root.expanded ? 0 : 1 + } + } + Skin.EffectChainPresetPopup { + id: presetPopup + + backgroundColor: LateNightTheme.isClassic ? "#0f0f0f" : "#151517" + borderColor: LateNightTheme.isClassic ? "#888888" : "#333333" + checkedSource: LateNightTheme.lateNightAsset("buttons", LateNightTheme.isClassic ? "btn__lib_checkmark_orange.svg" : "btn__lib_checkmark_blue.svg") + disabledTextColor: "#c2b3a5" + hoverColor: LateNightTheme.isClassic ? "#5e4507" : "#2c454f" + separatorBottomColor: LateNightTheme.isClassic ? "#222222" : "#333333" + slot1: slot1.slot + slot2: slot2.slot + slot3: slot3.slot + submenuArrowSource: LateNightTheme.lateNightAsset("style", LateNightTheme.isClassic ? "menu_arrow_yellow.svg" : "menu_arrow_ivory.svg") + unit: root.unit + x: masterControls.x + presetButton.x - 3 + y: masterControls.y + presetButton.y + presetButton.height + 2 + } + Mixxx.ControlProxy { + id: expandedControl + + group: root.group + key: "show_parameters" + } + Mixxx.ControlProxy { + id: controllerActive + + group: root.group + key: "controller_input_active" + } + Mixxx.ControlProxy { + id: showSuper + + group: "[Skin]" + key: "show_superknobs" + } +} diff --git a/res/skins/LateNightQML/Effects/EffectsRack.qml b/res/skins/LateNightQML/Effects/EffectsRack.qml new file mode 100644 index 000000000000..b8b23f9fd819 --- /dev/null +++ b/res/skins/LateNightQML/Effects/EffectsRack.qml @@ -0,0 +1,136 @@ +import Mixxx 1.0 as Mixxx +import QtQuick +import "../LateNightTheme" + +Rectangle { + id: root + + readonly property real firstPairHeight: Math.max(unit1.implicitHeight, unit2.implicitHeight) + readonly property int rowSpacing: LateNightTheme.isClassic ? 4 : 3 + readonly property real secondPairHeight: showFourUnits ? Math.max(unit3.implicitHeight, unit4.implicitHeight) : 0 + readonly property bool showFourUnits: showFourUnitsControl.value > 0 + + color: LateNightTheme.effectsRackGutterColor + implicitHeight: firstPairHeight + secondPairHeight + (showFourUnits ? rowSpacing : 0) + rowSpacing * 2 + + Item { + id: pair12 + + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + anchors.topMargin: root.rowSpacing + height: root.firstPairHeight + + EffectUnit { + id: unit1 + + height: implicitHeight + unitNumber: 1 + width: Math.round((parent.width - 4) / 2) + x: 0 + y: 0 + } + RackFiller { + height: Math.max(0, parent.height - y) + visible: height > 0 + width: unit1.width - 1 + x: unit1.x + y: unit1.height + root.rowSpacing + } + EffectUnit { + id: unit2 + + height: implicitHeight + unitNumber: 2 + width: parent.width - x + x: unit1.width + 4 + y: 0 + } + RackFiller { + height: Math.max(0, parent.height - y) + visible: height > 0 + width: unit2.width - 1 + x: unit2.x + 1 + y: unit2.height + root.rowSpacing + } + } + Item { + id: pair34 + + anchors.left: parent.left + anchors.right: parent.right + anchors.top: pair12.bottom + anchors.topMargin: root.rowSpacing + height: root.secondPairHeight + visible: root.showFourUnits + + EffectUnit { + id: unit3 + + height: implicitHeight + unitNumber: 3 + width: Math.round((parent.width - 4) / 2) + x: 0 + y: 0 + } + RackFiller { + height: Math.max(0, parent.height - y) + visible: height > 0 + width: unit3.width - 1 + x: unit3.x + y: unit3.height + root.rowSpacing + } + EffectUnit { + id: unit4 + + height: implicitHeight + unitNumber: 4 + width: parent.width - x + x: unit3.width + 4 + y: 0 + } + RackFiller { + height: Math.max(0, parent.height - y) + visible: height > 0 + width: unit4.width - 1 + x: unit4.x + 1 + y: unit4.height + root.rowSpacing + } + } + Mixxx.ControlProxy { + id: showFourUnitsControl + + group: "[Skin]" + key: "show_4effectunits" + } + + component RackFiller: Rectangle { + border.color: "#111111" + border.width: 1 + color: LateNightTheme.effectsFillerColor + radius: LateNightTheme.isClassic ? 2 : 1 + + Rectangle { + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + color: LateNightTheme.isClassic ? "#222222" : "#1c1c1c" + height: 1 + } + Rectangle { + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.top: parent.top + color: LateNightTheme.isClassic ? "#222222" : "#191919" + width: 1 + } + Rectangle { + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + color: LateNightTheme.isClassic ? "#111111" : "#020202" + height: 1 + } + } +} diff --git a/res/skins/LateNightQML/LateNightTheme/LateNightTheme.qml b/res/skins/LateNightQML/LateNightTheme/LateNightTheme.qml index c97e2a44f073..b913ad996507 100644 --- a/res/skins/LateNightQML/LateNightTheme/LateNightTheme.qml +++ b/res/skins/LateNightQML/LateNightTheme/LateNightTheme.qml @@ -3,112 +3,33 @@ import QtQuick import "." QtObject { - readonly property bool isClassic: ColorScheme.name === "classic" - readonly property bool isPaleMoon: ColorScheme.name === "palemoon" - - readonly property color backgroundColor: "#1e1e1e" - readonly property color buttonActiveColor: white - readonly property color buttonNormalColor: "#696969" - readonly property color buttonPressedColor: white - readonly property color darkGray: "#0f0f0f" readonly property color accentColor: ColorScheme.accentColor readonly property color activePlayCueColor: isClassic ? "#db0000" : "#b24c12" - readonly property color deckTimeTextColor: isClassic ? "#f0bb2b" : "#777777" - readonly property color deckButtonInactiveColor: isClassic ? "#262626" : "#121213" - readonly property color deckDimButtonInactiveColor: isClassic ? "#262626" : "#171719" - readonly property color deckEmbeddedButtonInactiveColor: isClassic ? "#262626" : "#1e1e20" - readonly property color deckBeatSpinBoxTextColor: isClassic ? "#888888" : "#a7998b" - readonly property color deckReadonlyTextColor: isClassic ? "#888888" : "#777777" - readonly property color deckTopRowBackgroundColor: "#181818" - readonly property color deckPanelColor: "#1e1e20" - readonly property color deckPanelBorderDark: "#0c0c0c" - readonly property color deckPanelBorderLight: "#333333" - readonly property color deckPanelBorderLeft: "#282828" - readonly property color deckPanelBorderRight: deckTopRowBackgroundColor - readonly property color overviewBorderTopColor: "#0d0d0d" - readonly property color overviewBorderLeftColor: "#121212" - readonly property color overviewBorderBottomColor: "#2a2a2a" - readonly property color overviewBorderRightColor: "#252525" - readonly property color libraryPanelSplitterBackground: "#1e1e1e" - readonly property color libraryPanelSplitterHandle: "#5f5f5f" - readonly property color libraryPanelSplitterHandleActive: "#7a7a7a" - readonly property color textColor: white - readonly property color textColorMuted: "#696969" - readonly property color primaryDeckTextColor: isClassic ? "#f0bb2b" : "#c2b3a5" - readonly property color overviewSettingsBackgroundColor: isClassic ? "#151515" : "#19191a" - readonly property color primaryOverviewBackgroundColor: isClassic ? "#0f0f0f" : "#19191a" - readonly property color primaryWaveformSignalColor: isClassic ? "#e7c413" : "#d9b28c" - readonly property color secondaryDeckTextColor: isClassic ? "#0bd9d1" : "#85bdbb" - readonly property color secondaryOverviewBackgroundColor: "#001b23" - readonly property color secondaryWaveformSignalColor: isClassic ? "#09b2ae" : "#7bc6c3" - readonly property color starsColor12: isClassic ? "#f0bb2b" : "#988f86" - readonly property color starsColor34: isClassic ? "#0bd9d1" : "#559b99" - readonly property color toolbarActiveColor: white - readonly property color toolbarBackgroundColor: "#242424" - readonly property color toolbarRootBackgroundColor: "#151517" - readonly property color toolbarBottomBorderColor: "#020202" - readonly property color toolbarButtonActiveBackgroundColor: isClassic ? "#d09300" : "#777777" - readonly property color toolbarButtonInactiveBackgroundColor: isClassic ? "#262626" : "#151517" - readonly property color toolbarButtonActiveTextColor: "#000000" - readonly property color toolbarButtonInactiveTextColor: isClassic ? "#d2d2d1" : "#777777" - readonly property color toolbarClockTextColor: isClassic ? "#f0bb2b" : "#c2b3a5" - readonly property color toolbarMenuHoverColor: isPaleMoon ? "#2c454f" : "#5e4507" - readonly property color toolbarMenuHoverTextColor: "#ffffff" - readonly property color toolbarMenuTextColor: "#c2b3a5" - readonly property color toolbarMenuDisabledTextColor: "#777777" - readonly property color toolbarBroadcastOnColor: isClassic ? "#659f08" : "#438225" - readonly property color toolbarRecordInitColor: "#d09300" - readonly property color toolbarRecordOnColor: isClassic ? "#db0000" : "#a80000" - readonly property color toolbarStatusErrorColor: "#f856e7" - readonly property int toolbarButtonHeight: 26 - readonly property int toolbarButtonWidth: 52 - readonly property url assetToolbarDropdownIcon: lateNightAsset("buttons", "btn__fx_selector_down.svg") - readonly property url assetToolbarMenuIcon: lateNightAsset("buttons", "btn__menu.svg") - readonly property color toolbarMeterBackgroundColor: darkGray - readonly property color toolbarLatencyBorderColor: "#040404" - readonly property color toolbarLatencyOverloadColor: "#ffff00" - readonly property color toolbarLatencyLabelColor: "#444444" - readonly property color toolbarPopupBorderColor: "#585858" - readonly property color toolbarPopupBackgroundColor: "#0f0f0f" - readonly property color toolbarStatusOkColor: "#54c76a" - readonly property color toolbarStatusWarnColor: "#d89124" - readonly property color toolbarRecordingColor: "#db0000" - readonly property color toolbarRecordingTextColor: "#ff7373" - readonly property color syncExplicitLeaderColor: activePlayCueColor - readonly property color syncInactiveBackgroundColor: "#1e1e1e" - readonly property color syncImplicitLeaderColor: isPaleMoon ? "#7d350d" : "#db7700" - readonly property int syncButtonHorizontalPadding: isClassic ? 3 : 0 - readonly property color keyControlsPressedColor: isPaleMoon ? "#7d350d" : "#db0000" - readonly property string keyControlsPressedIconSuffix: isPaleMoon ? "active" : "" - readonly property string playCueActiveIconSuffix: isPaleMoon ? "active" : "" - readonly property color white: "#D9D9D9" - readonly property url assetDeckArrowLeftUpButton: lateNightAsset("buttons", "btn__arrow_left_up.svg") readonly property url assetDeckArrowRightDownButton: lateNightAsset("buttons", "btn__arrow_right_down.svg") - readonly property url assetDeckBeatjumpLeftButton: lateNightAsset("buttons", "btn__beatjump_left.svg") - readonly property url assetDeckBeatjumpRightButton: lateNightAsset("buttons", "btn__beatjump_right.svg") + readonly property url assetDeckBeatCurposButton: lateNightAsset("buttons", "btn__beat_curpos.svg") readonly property url assetDeckBeatSpinBoxBorder: isClassic ? lateNightAsset("buttons", "spinbox_elevated_border.svg") : lateNightSubRegionButton("wide") readonly property url assetDeckBeatSpinBoxDownButton: isClassic ? lateNightAsset("buttons", "spinbox_down.svg") : lateNightAsset("buttons", "btn__spinbox_down.svg") readonly property url assetDeckBeatSpinBoxUpButton: isClassic ? lateNightAsset("buttons", "spinbox_up.svg") : lateNightAsset("buttons", "btn__spinbox_up.svg") - readonly property url optionalDeckControlsBackgroundTile: isClassic ? lateNightAsset("style", "background_tile.png") : "" + readonly property url assetDeckBeatjumpLeftButton: lateNightAsset("buttons", "btn__beatjump_left.svg") + readonly property url assetDeckBeatjumpRightButton: lateNightAsset("buttons", "btn__beatjump_right.svg") readonly property url assetDeckCoverDefault: lateNightAsset("style", "cover_default.svg") - readonly property url assetDeckBeatCurposButton: lateNightAsset("buttons", "btn__beat_curpos.svg") readonly property url assetDeckCueButton: lateNightAsset("buttons", "btn__cue_deck.svg") readonly property url assetDeckEjectButton: lateNightAsset("buttons", "btn__eject.svg") readonly property url assetDeckIntroEndButton: lateNightAsset("buttons", "btn__intro_end.svg") readonly property url assetDeckIntroStartButton: lateNightAsset("buttons", "btn__intro_start.svg") + readonly property url assetDeckKeyButtonBackground: lateNightAsset("buttons", "btn_embedded_library.svg") readonly property url assetDeckKeyDownButton: lateNightAsset("buttons", "btn__key_down.svg") - readonly property url assetDeckKeylockButton: lateNightAsset("buttons", "btn__keylock.svg") readonly property url assetDeckKeyMatchButton: lateNightAsset("buttons", "btn__key_match.svg") readonly property url assetDeckKeyUpButton: lateNightAsset("buttons", "btn__key_up.svg") - readonly property url assetDeckKeyButtonBackground: lateNightAsset("buttons", "btn_embedded_library.svg") + readonly property url assetDeckKeylockButton: lateNightAsset("buttons", "btn__keylock.svg") readonly property url assetDeckLeaderBackground: lateNightAsset("buttons", "btn_embedded_grid.svg") readonly property url assetDeckLeaderButton: lateNightAsset("buttons", "btn__sync_leader.svg") readonly property url assetDeckLeaderExplicitButton: isPaleMoon ? lateNightAsset("buttons", "btn__sync_leader_explicit.svg") : lateNightAsset("buttons", "btn__sync_leader_active.svg") readonly property url assetDeckLeaderImplicitButton: isPaleMoon ? lateNightAsset("buttons", "btn__sync_leader_implicit.svg") : lateNightAsset("buttons", "btn__sync_leader_active.svg") - readonly property url assetDeckLoopButton: lateNightAsset("buttons", "btn__loop.svg") readonly property url assetDeckLoopAnchorEndButton: lateNightAsset("buttons", "btn__loop_anchor_end.svg") readonly property url assetDeckLoopAnchorStartButton: lateNightAsset("buttons", "btn__loop_anchor_start.svg") + readonly property url assetDeckLoopButton: lateNightAsset("buttons", "btn__loop.svg") readonly property url assetDeckLoopInButton: lateNightAsset("buttons", "btn__loop_in.svg") readonly property url assetDeckLoopOutButton: lateNightAsset("buttons", "btn__loop_out.svg") readonly property url assetDeckMinusButton: lateNightAsset("buttons", "btn__minus.svg") @@ -117,8 +38,6 @@ QtObject { readonly property url assetDeckPlayButton: lateNightAsset("buttons", "btn__play_deck.svg") readonly property url assetDeckPlusButton: lateNightAsset("buttons", "btn__plus.svg") readonly property url assetDeckQuantizeButton: lateNightAsset("buttons", "btn__quantize.svg") - readonly property url optionalDeckRateCenterActive: isPaleMoon ? lateNightAsset("buttons", "btn__rate_center_cyan.svg") : "" - readonly property url optionalDeckRateCenterInactive: isPaleMoon ? lateNightAsset("buttons", "btn__rate_center_off.svg") : "" readonly property url assetDeckRateSliderBackground: lateNightAsset("sliders", "slider_pitch_deck.svg") readonly property url assetDeckRateSliderHandle: lateNightAsset("sliders", "knob_pitch_deck.svg") readonly property url assetDeckReloopButton: lateNightAsset("buttons", "btn__reloop.svg") @@ -132,40 +51,204 @@ QtObject { readonly property url assetDeckSpinnyIndicator: lateNightAsset("style", "spinny_indicator.svg") readonly property url assetDeckSpinnyMask12: lateNightAsset("style", "spinny_mask_12.svg") readonly property url assetDeckSpinnyMask34: lateNightAsset("style", "spinny_mask_34.svg") + readonly property url assetDeckSyncActiveButton: isPaleMoon ? lateNightAsset("buttons", "btn__sync_deck_active.svg") : lateNightAsset("buttons", "btn__sync_deck.svg") readonly property url assetDeckSyncBackground: lateNightAsset("buttons", "btn_embedded_library.svg") readonly property url assetDeckSyncButton: lateNightAsset("buttons", "btn__sync_deck.svg") - readonly property url assetDeckSyncActiveButton: isPaleMoon ? lateNightAsset("buttons", "btn__sync_deck_active.svg") : lateNightAsset("buttons", "btn__sync_deck.svg") - readonly property url assetDeckVolumeSliderBackground: lateNightAsset("sliders", "slider_volume_deck.svg") - readonly property url assetDeckVolumeSliderHandle: lateNightAsset("sliders", "knob_volume_deck.svg") readonly property url assetDeckVinylControl0: lateNightAsset("style", "vinyl_control_0.svg") readonly property url assetDeckVinylControl1: lateNightAsset("style", "vinyl_control_1.svg") readonly property url assetDeckVinylControl2: lateNightAsset("style", "vinyl_control_2.svg") readonly property url assetDeckVinylControl3: lateNightAsset("style", "vinyl_control_3.svg") + readonly property url assetDeckVolumeSliderBackground: lateNightAsset("sliders", "slider_volume_deck.svg") + readonly property url assetDeckVolumeSliderHandle: lateNightAsset("sliders", "knob_volume_deck.svg") + readonly property url assetFxCollapseButton: lateNightAsset("buttons", isClassic ? "btn__collapse.svg" : "btn__collapse_dim.svg") + readonly property url assetFxExpandButton: lateNightAsset("buttons", isClassic ? "btn__expand.svg" : "btn__expand_dim.svg") + readonly property url assetFxFlowHorizontal: lateNightAsset("style", isClassic ? "fx_separator.svg" : "fx_flow_horizontal.svg") + readonly property url assetFxFlowVertical: lateNightAsset("style", isClassic ? "fx_separator.svg" : "fx_flow_vertical.svg") + readonly property url assetFxFocusActiveButton: lateNightAsset("buttons", "btn__fx_focus_active.svg") + readonly property url assetFxFocusButton: lateNightAsset("buttons", "btn__fx_focus.svg") readonly property url assetFxKnobBackground: lateNightAsset("knobs", "knob_bg_fx.svg") + readonly property url assetFxMixModeButton: lateNightAsset("buttons", "btn_embedded_mixmode.svg") + readonly property url assetFxMixModeDryWetButton: lateNightAsset("buttons", "btn__fx_mixmode_d-w.svg") + readonly property url assetFxMixModeDryWetSumButton: lateNightAsset("buttons", "btn__fx_mixmode_d+w.svg") + readonly property url assetFxParameterActiveButton: lateNightAsset("buttons", "btn_embedded_fx_parameter_active.svg") + readonly property url assetFxParameterButton: lateNightAsset("buttons", "btn_embedded_fx_parameter.svg") + readonly property url assetFxSelectorActiveBorder: lateNightAsset("buttons", "btn_embedded_library_active.svg") + readonly property url assetFxSelectorBorder: lateNightAsset("buttons", "btn_embedded_library.svg") + readonly property url assetFxSelectorDownButton: lateNightAsset("buttons", "btn__fx_selector_down.svg") + readonly property url assetFxSettingsButton: lateNightAsset("buttons", "btn__fx_settings.svg") + readonly property url assetFxSlotButtonActiveBackground: lateNightAsset("buttons", "btn_embedded_square_active.svg") + readonly property url assetFxSlotButtonBackground: lateNightAsset("buttons", "btn_embedded_square.svg") + readonly property url assetFxToggleActiveButton: lateNightAsset("buttons", "btn__fx_toggle_active.svg") + readonly property url assetFxToggleButton: lateNightAsset("buttons", "btn__fx_toggle.svg") readonly property url assetMainKnobBackground: lateNightAsset("knobs", "knob_bg_main.svg") + readonly property url assetMixerCrossfaderBackground: lateNightAsset("sliders", "slider_crossfader.svg") + readonly property url assetMixerCrossfaderHandle: lateNightAsset("sliders", "knob_crossfader.svg") + readonly property url assetMixerCrossfaderSmallBackground: lateNightAsset("sliders", "slider_crossfader_small.svg") + readonly property url assetMixerEqKillHighIcon: lateNightAsset("buttons", "btn__eq_kill_high.svg") + readonly property url assetMixerEqKillLowIcon: lateNightAsset("buttons", "btn__eq_kill_low.svg") + readonly property url assetMixerEqKillMidIcon: lateNightAsset("buttons", "btn__eq_kill_mid.svg") + readonly property url assetMixerPflActiveIcon: isPaleMoon ? lateNightAsset("buttons", "btn__pfl_active.svg") : lateNightAsset("buttons", "btn__pfl.svg") + readonly property url assetMixerPflBackground: lateNightTopRegionButton("square") + readonly property url assetMixerPflIcon: lateNightAsset("buttons", "btn__pfl.svg") + readonly property url assetMixerQuickEffectIcon: lateNightAsset("buttons", "btn__star.svg") + readonly property url assetMixerSplitActiveIcon: lateNightAsset("buttons", isClassic ? "btn_elevated_headsplit_active.svg" : "btn_embedded_headsplit_active.svg") + readonly property url assetMixerSplitIcon: lateNightAsset("buttons", isClassic ? "btn_elevated_headsplit.svg" : "btn_embedded_headsplit.svg") + readonly property url assetMixerVolumeSliderBackground: lateNightAsset("sliders", "slider_volume_deck.svg") + readonly property url assetMixerVolumeSliderHandle: lateNightAsset("sliders", "knob_volume_deck.svg") readonly property url assetRegularKnobBackground: lateNightAsset("knobs", "knob_bg_regular.svg") readonly property url assetSmallKnobBackground: lateNightAsset("knobs", "knob_bg_small.svg") + readonly property url assetToolbarDropdownIcon: lateNightAsset("buttons", "btn__fx_selector_down.svg") + readonly property url assetToolbarMenuIcon: lateNightAsset("buttons", "btn__menu.svg") + readonly property color backgroundColor: "#1e1e1e" + readonly property color buttonActiveColor: white + readonly property color buttonNormalColor: "#696969" + readonly property color buttonPressedColor: white + readonly property color darkGray: "#0f0f0f" + readonly property color deckBeatSpinBoxTextColor: isClassic ? "#888888" : "#a7998b" + readonly property color deckButtonInactiveColor: isClassic ? "#262626" : "#121213" + readonly property color deckDimButtonInactiveColor: isClassic ? "#262626" : "#171719" + readonly property color deckEmbeddedButtonInactiveColor: isClassic ? "#262626" : "#1e1e20" + readonly property color deckPanelBorderDark: "#0c0c0c" + readonly property color deckPanelBorderLeft: "#282828" + readonly property color deckPanelBorderLight: "#333333" + readonly property color deckPanelBorderRight: deckTopRowBackgroundColor + readonly property color deckPanelColor: "#1e1e20" + readonly property color deckReadonlyTextColor: isClassic ? "#888888" : "#777777" + readonly property color deckTimeTextColor: isClassic ? "#f0bb2b" : "#777777" + readonly property color deckTopRowBackgroundColor: "#181818" + readonly property color effectsAssignmentActiveTextColor: isClassic ? "#000000" : "#a7998b" + readonly property color effectsAssignmentInactiveColor: isClassic ? "#262626" : "#151517" + readonly property color effectsAssignmentInactiveTextColor: isClassic ? "#d2d2d1" : "#555555" + readonly property color effectsControllerColor12: isClassic ? "#73b508" : "#518f00" + readonly property color effectsControllerColor34: isClassic ? "#0795b5" : "#028392" + readonly property color effectsFillerColor: isClassic ? "#171717" : "#151517" + readonly property color effectsFocusBorderColor: isClassic ? "#d08e00" : "#257b82" + readonly property color effectsHeaderColor: isClassic ? "#1e1e1e" : "#151517" + readonly property color effectsMasterButtonInactiveColor: isClassic ? "#262626" : "#1e1e20" + readonly property color effectsPanelColor: isClassic ? "#1e1e1e" : "#1e1e20" + readonly property color effectsParameterActiveColor: "#888888" + readonly property color effectsParameterArcColor: "#6d6d6d" + readonly property color effectsParameterInactiveColor: isClassic ? "#333333" : "#2a2a2c" + readonly property color effectsParameterPanelColor: isClassic ? "#151515" : "#1e1e20" + readonly property color effectsRackGutterColor: "#060606" + readonly property color effectsSlotToggleInactiveColor: isClassic ? "#262626" : "#121213" + readonly property color effectsUnitColor12: isClassic ? "#659f08" : "#438225" + readonly property color effectsUnitColor34: isClassic ? "#0895bc" : "#257b82" + readonly property color effectsUnitDimColor12: isClassic ? "#426b00" : "#236b00" + readonly property color effectsUnitDimColor34: isClassic ? "#00696b" : "#146674" + readonly property bool isClassic: ColorScheme.name === "classic" + readonly property bool isPaleMoon: ColorScheme.name === "palemoon" + readonly property color keyControlsPressedColor: isPaleMoon ? "#7d350d" : "#db0000" + readonly property string keyControlsPressedIconSuffix: isPaleMoon ? "active" : "" + readonly property color libraryPanelSplitterBackground: "#1e1e1e" + readonly property color libraryPanelSplitterHandle: "#5f5f5f" + readonly property color libraryPanelSplitterHandleActive: "#7a7a7a" + readonly property color mixerAccentCyan: "#0bd9d1" + readonly property color mixerAccentOrange: isClassic ? "#db7700" : "#b24c12" + readonly property color mixerAccentRed: isClassic ? "#db0000" : "#a80000" + readonly property color mixerArcEqColor: "#858585" + readonly property color mixerArcGainColor: "#b96300" + readonly property color mixerArcGainLowColor: "#8d3b11" + readonly property color mixerArcMainBalanceColor: "#a00000" + readonly property color mixerArcQuickEffectColor: "#518f00" + readonly property real mixerArcRadiusBig: 14.5 + readonly property real mixerArcRadiusCompact: 12.5 + readonly property real mixerArcWidth: 2 + readonly property color mixerControlTextColor: isClassic ? "#d2d2d1" : "#a7998b" + readonly property color mixerDimTextColor: "#696969" + readonly property color mixerEqKillActiveColor: isClassic ? "#db0000" : "#a80000" + readonly property color mixerMainSeparatorDarkColor: isPaleMoon ? "#0c0c0c" : mixerPanelBorderDark + readonly property color mixerMainSeparatorLightColor: isPaleMoon ? "#222222" : mixerPanelBorderLight + readonly property color mixerPanelBorderDark: "#080808" + readonly property color mixerPanelBorderLight: "#343434" + readonly property color mixerPanelColor: "#1d1d1f" + readonly property color mixerPflActiveFillColor: isClassic ? "#db0000" : "#666666" + readonly property color mixerQuickEffectActiveColor: isClassic ? "#659f08" : "#236b00" + readonly property color mixerQuickEffectSelectorTextColor: "#918273" + readonly property color mixerSliderBarColor: "#257b82" + readonly property color mixerVuClipColor: mixerAccentRed + readonly property color mixerVuLevelColor: mixerAccentRed + readonly property url optionalDeckControlsBackgroundTile: isClassic ? lateNightAsset("style", "background_tile.png") : "" + readonly property url optionalDeckRateCenterActive: isPaleMoon ? lateNightAsset("buttons", "btn__rate_center_cyan.svg") : "" + readonly property url optionalDeckRateCenterInactive: isPaleMoon ? lateNightAsset("buttons", "btn__rate_center_off.svg") : "" + readonly property url optionalMixerEqKillDotActiveGreen: isPaleMoon ? lateNightAsset("buttons", "btn__eq_kill_dot_active_green.svg") : "" + readonly property url optionalMixerEqKillDotActiveRed: isPaleMoon ? lateNightAsset("buttons", "btn__eq_kill_dot_active_red.svg") : "" + readonly property url optionalMixerEqKillDotOff: isPaleMoon ? lateNightAsset("buttons", "btn__eq_kill_dot_off.svg") : "" + readonly property color overviewBorderBottomColor: "#2a2a2a" + readonly property color overviewBorderLeftColor: "#121212" + readonly property color overviewBorderRightColor: "#252525" + readonly property color overviewBorderTopColor: "#0d0d0d" + readonly property color overviewSettingsBackgroundColor: isClassic ? "#151515" : "#19191a" + readonly property string playCueActiveIconSuffix: isPaleMoon ? "active" : "" + readonly property color primaryDeckTextColor: isClassic ? "#f0bb2b" : "#c2b3a5" + readonly property color primaryOverviewBackgroundColor: isClassic ? "#0f0f0f" : "#19191a" + readonly property color primaryWaveformSignalColor: isClassic ? "#e7c413" : "#d9b28c" + readonly property color secondaryDeckTextColor: isClassic ? "#0bd9d1" : "#85bdbb" + readonly property color secondaryOverviewBackgroundColor: "#001b23" + readonly property color secondaryWaveformSignalColor: isClassic ? "#09b2ae" : "#7bc6c3" + readonly property color starsColor12: isClassic ? "#f0bb2b" : "#988f86" + readonly property color starsColor34: isClassic ? "#0bd9d1" : "#559b99" + readonly property int syncButtonHorizontalPadding: isClassic ? 3 : 0 + readonly property color syncExplicitLeaderColor: activePlayCueColor + readonly property color syncImplicitLeaderColor: isPaleMoon ? "#7d350d" : "#db7700" + readonly property color syncInactiveBackgroundColor: "#1e1e1e" + readonly property color textColor: white + readonly property color textColorMuted: "#696969" + readonly property color toolbarActiveColor: white + readonly property color toolbarBackgroundColor: "#242424" + readonly property color toolbarBottomBorderColor: "#020202" + readonly property color toolbarBroadcastOnColor: isClassic ? "#659f08" : "#438225" + readonly property color toolbarButtonActiveBackgroundColor: isClassic ? "#d09300" : "#777777" + readonly property color toolbarButtonActiveTextColor: "#000000" + readonly property int toolbarButtonHeight: 26 + readonly property color toolbarButtonInactiveBackgroundColor: isClassic ? "#262626" : "#151517" + readonly property color toolbarButtonInactiveTextColor: isClassic ? "#d2d2d1" : "#777777" + readonly property int toolbarButtonWidth: 52 + readonly property color toolbarClockTextColor: isClassic ? "#f0bb2b" : "#c2b3a5" + readonly property color toolbarLatencyBorderColor: "#040404" + readonly property color toolbarLatencyLabelColor: "#444444" + readonly property color toolbarLatencyOverloadColor: "#ffff00" + readonly property color toolbarMenuDisabledTextColor: "#777777" + readonly property color toolbarMenuHoverColor: isPaleMoon ? "#2c454f" : "#5e4507" + readonly property color toolbarMenuHoverTextColor: "#ffffff" + readonly property color toolbarMenuTextColor: "#c2b3a5" + readonly property color toolbarMeterBackgroundColor: darkGray + readonly property color toolbarPopupBackgroundColor: "#0f0f0f" + readonly property color toolbarPopupBorderColor: "#585858" + readonly property color toolbarRecordInitColor: "#d09300" + readonly property color toolbarRecordOnColor: isClassic ? "#db0000" : "#a80000" + readonly property color toolbarRecordingColor: "#db0000" + readonly property color toolbarRecordingTextColor: "#ff7373" + readonly property color toolbarRootBackgroundColor: "#151517" + readonly property color toolbarStatusErrorColor: "#f856e7" + readonly property color toolbarStatusOkColor: "#54c76a" + readonly property color toolbarStatusWarnColor: "#d89124" + readonly property color white: "#D9D9D9" + function lateNightAsset(directory, fileName) { + return Qt.resolvedUrl("../../LateNight/" + ColorScheme.name + "/" + directory + "/" + fileName); + } function lateNightButton(fileName) { return lateNightAsset("buttons", fileName); } - function lateNightRegionButton(regionButtonType, buttonSize) { return lateNightButton("btn_" + regionButtonType + "_" + buttonSize + ".svg"); } - function lateNightSubRegionButton(buttonSize) { return lateNightRegionButton(isClassic ? "elevated" : "embedded", buttonSize); } - function lateNightTopRegionButton(buttonSize) { return lateNightRegionButton("embedded", buttonSize); } - - function lateNightAsset(directory, fileName) { - return Qt.resolvedUrl("../../LateNight/" + ColorScheme.name + "/" + directory + "/" + fileName); + function mixerKnobIndicator(kind, colorName) { + return lateNightAsset("knobs", "knob_indicator_" + kind + "_" + colorName + ".svg"); + } + function mixerVuClipBackground(colorVariant) { + return lateNightAsset("style", "vu_deck_clipping_bg_" + colorVariant + ".png"); + } + function mixerVuLevelBackground(colorVariant) { + return lateNightAsset("style", "vu_deck_level_bg_" + colorVariant + ".png"); } - function sharedImage(fileName) { return Qt.resolvedUrl("../../../qml/images/" + fileName); } diff --git a/res/skins/LateNightQML/Mixer/Crossfader.qml b/res/skins/LateNightQML/Mixer/Crossfader.qml new file mode 100644 index 000000000000..dd89fe062278 --- /dev/null +++ b/res/skins/LateNightQML/Mixer/Crossfader.qml @@ -0,0 +1,46 @@ +import Mixxx 1.0 as Mixxx +import QtQuick +import "../Controls" as LateNightControls +import "../LateNightTheme" + +Item { + id: root + + property bool compact: false + required property var groups + property bool showAssignments: true + + implicitHeight: 40 + implicitWidth: compact ? 85 : 115 + + LateNightControls.Fader { + anchors.centerIn: parent + backgroundSource: root.compact ? LateNightTheme.assetMixerCrossfaderSmallBackground : LateNightTheme.assetMixerCrossfaderBackground + bar.margin: 7 + bar.start: 0.5 + group: "[Master]" + handleSource: LateNightTheme.assetMixerCrossfaderHandle + height: implicitHeight + key: "crossfader" + orientation: Qt.Horizontal + width: root.compact ? 85 : 115 + } + Row { + anchors.bottom: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + opacity: root.showAssignments ? 1 : 0 + spacing: 0 + + Repeater { + model: root.groups.length + + CrossfaderAssignButton { + required property int index + + group: root.groups[index] + leftStyle: index < 2 ? "default" : "warning" + rightStyle: index < 2 ? "warning" : "default" + } + } + } +} diff --git a/res/skins/LateNightQML/Mixer/CrossfaderAssignButton.qml b/res/skins/LateNightQML/Mixer/CrossfaderAssignButton.qml new file mode 100644 index 000000000000..a790b679133a --- /dev/null +++ b/res/skins/LateNightQML/Mixer/CrossfaderAssignButton.qml @@ -0,0 +1,41 @@ +import Mixxx 1.0 as Mixxx +import QtQuick +import "../LateNightTheme" + +Item { + id: root + + required property string group + property string leftStyle: "default" + property string rightStyle: "default" + readonly property int state: Math.max(0, Math.min(2, Math.round(orientationControl.value))) + readonly property string stateLabel: ["left", "mid", "right"][state] + readonly property string stateStyle: { + if (state === 0) { + return root.leftStyle; + } else if (state === 2) { + return root.rightStyle; + } + return "warning"; + } + + implicitHeight: 15 + implicitWidth: 33 + + Image { + anchors.fill: parent + fillMode: Image.PreserveAspectFit + source: LateNightTheme.lateNightAsset("buttons", "btn__xfader_deck_" + root.stateLabel + "_" + root.stateStyle + ".svg") + } + MouseArea { + anchors.fill: parent + + onClicked: orientationControl.value = (Math.round(orientationControl.value) + 1) % 3 + } + Mixxx.ControlProxy { + id: orientationControl + + group: root.group + key: "orientation" + } +} diff --git a/res/skins/LateNightQML/Mixer/EqKillButton.qml b/res/skins/LateNightQML/Mixer/EqKillButton.qml new file mode 100644 index 000000000000..5b9ca3031003 --- /dev/null +++ b/res/skins/LateNightQML/Mixer/EqKillButton.qml @@ -0,0 +1,45 @@ +import Mixxx 1.0 as Mixxx +import QtQuick +import "../LateNightTheme" + +Item { + id: root + + required property string group + required property url iconSource + required property string key + property alias value: killControl.value + + function toggle() { + killControl.value = killControl.value > 0 ? 0 : 1; + } + + implicitHeight: 18 + implicitWidth: 18 + + Rectangle { + anchors.fill: parent + border.color: "#050505" + border.width: 2 + color: killControl.value > 0 ? LateNightTheme.mixerEqKillActiveColor : "#111113" + } + Image { + anchors.centerIn: parent + fillMode: Image.PreserveAspectFit + height: 18 + source: root.iconSource + visible: killControl.value <= 0 + width: 18 + } + MouseArea { + anchors.fill: parent + + onClicked: killControl.value = killControl.value > 0 ? 0 : 1 + } + Mixxx.ControlProxy { + id: killControl + + group: root.group + key: root.key + } +} diff --git a/res/skins/LateNightQML/Mixer/EqKnobRow.qml b/res/skins/LateNightQML/Mixer/EqKnobRow.qml new file mode 100644 index 000000000000..a14f76acb53d --- /dev/null +++ b/res/skins/LateNightQML/Mixer/EqKnobRow.qml @@ -0,0 +1,67 @@ +import Mixxx 1.0 as Mixxx +import QtQuick +import "../Controls" as LateNightControls +import "../LateNightTheme" + +Item { + id: root + + readonly property string effectButtonKey: "button_" + effectParameterKey + readonly property string effectGroup: "[EqualizerRack1_" + group + "_Effect1]" + readonly property string effectParameterKey: root.key === "filterLow" ? "parameter1" : (root.key === "filterMid" ? "parameter2" : "parameter3") + required property string group + required property string key + required property url killIcon + property bool mirror: false + property bool showKill: true + + implicitHeight: 34 + implicitWidth: 58 + + EqKillButton { + id: eqKillButton + + anchors.left: root.mirror ? undefined : parent.left + anchors.right: root.mirror ? parent.right : undefined + anchors.verticalCenter: parent.verticalCenter + group: root.effectGroup + iconSource: root.killIcon + key: root.effectButtonKey + visible: root.showKill + } + LateNightControls.Knob { + id: eqKnob + + anchors.left: root.mirror ? parent.left : undefined + anchors.right: root.mirror ? undefined : parent.right + anchors.verticalCenter: parent.verticalCenter + backgroundSource: LateNightTheme.assetRegularKnobBackground + displayArc: true + displayArcColor: LateNightTheme.mixerArcEqColor + displayArcRadius: LateNightTheme.mixerArcRadiusBig + displayArcStart: LateNightControls.Knob.ArcStart.Center + displayArcWidth: LateNightTheme.mixerArcWidth + group: root.effectGroup + height: 34 + indicatorColor: LateNightTheme.isClassic ? "white" : "grey" + indicatorKind: "regular" + key: root.effectParameterKey + width: 40 + } + Image { + id: statusDot + + height: 9 + source: eqKillButton.value > 0 ? LateNightTheme.optionalMixerEqKillDotActiveRed : LateNightTheme.optionalMixerEqKillDotOff + visible: LateNightTheme.isPaleMoon && !root.showKill && root.visible + width: 9 + x: root.mirror ? eqKnob.x + 32 : eqKnob.x + y: 27 + + MouseArea { + anchors.fill: parent + + onClicked: eqKillButton.toggle() + } + } +} diff --git a/res/skins/LateNightQML/Mixer/EqQuickColumn.qml b/res/skins/LateNightQML/Mixer/EqQuickColumn.qml new file mode 100644 index 000000000000..812e45b9375e --- /dev/null +++ b/res/skins/LateNightQML/Mixer/EqQuickColumn.qml @@ -0,0 +1,45 @@ +import QtQuick +import QtQuick.Layouts +import "../LateNightTheme" + +Column { + id: root + + property bool alignRight: false + required property string group + property bool showEqKillButtons: true + property bool showEqKnobs: true + + spacing: 2 + + EqKnobRow { + group: root.group + key: "filterHigh" + killIcon: LateNightTheme.assetMixerEqKillHighIcon + mirror: root.alignRight + showKill: root.showEqKillButtons + visible: root.showEqKnobs + } + EqKnobRow { + group: root.group + key: "filterMid" + killIcon: LateNightTheme.assetMixerEqKillMidIcon + mirror: root.alignRight + showKill: root.showEqKillButtons + visible: root.showEqKnobs + } + EqKnobRow { + group: root.group + key: "filterLow" + killIcon: LateNightTheme.assetMixerEqKillLowIcon + mirror: root.alignRight + showKill: root.showEqKillButtons + visible: root.showEqKnobs + } + QuickEffectRow { + group: root.group + mirror: root.alignRight + showEqKillButtons: root.showEqKillButtons + visible: root.showEqKnobs + } +} diff --git a/res/skins/LateNightQML/Mixer/FxAssignButtons.qml b/res/skins/LateNightQML/Mixer/FxAssignButtons.qml new file mode 100644 index 000000000000..0166228e6226 --- /dev/null +++ b/res/skins/LateNightQML/Mixer/FxAssignButtons.qml @@ -0,0 +1,66 @@ +import Mixxx 1.0 as Mixxx +import QtQuick +import "../LateNightTheme" + +Row { + id: root + + required property string groupName + readonly property int unitCount: showFourUnitsControl.value > 0 ? 4 : 2 + + spacing: 0 + + Repeater { + model: root.unitCount + + Item { + id: cell + + readonly property color activeColor: cell.index < 2 ? (LateNightTheme.isClassic ? LateNightTheme.effectsUnitColor12 : LateNightTheme.effectsUnitDimColor12) : (LateNightTheme.isClassic ? LateNightTheme.effectsUnitColor34 : LateNightTheme.effectsUnitDimColor34) + readonly property int buttonWidth: root.unitCount === 2 || cell.index === 0 ? 26 : 20 + required property int index + + height: 20 + implicitHeight: 20 + implicitWidth: buttonWidth + width: buttonWidth + + Rectangle { + anchors.fill: parent + color: assignControl.value > 0 ? cell.activeColor : LateNightTheme.effectsAssignmentInactiveColor + } + Image { + anchors.fill: parent + fillMode: Image.Stretch + source: { + const suffix = assignControl.value > 0 ? "_active" : ""; + return LateNightTheme.lateNightAsset("buttons", cell.index === 0 ? "btn_embedded_library" + suffix + ".svg" : "btn_embedded_grid" + suffix + ".svg"); + } + } + Text { + anchors.centerIn: parent + color: assignControl.value > 0 ? LateNightTheme.effectsAssignmentActiveTextColor : LateNightTheme.effectsAssignmentInactiveTextColor + font.bold: true + font.pixelSize: 12 + text: root.unitCount === 2 || cell.index === 0 ? "FX" + (cell.index + 1) : cell.index + 1 + } + MouseArea { + anchors.fill: parent + + onClicked: assignControl.value = assignControl.value > 0 ? 0 : 1 + } + Mixxx.ControlProxy { + id: assignControl + + group: "[EffectRack1_EffectUnit" + (cell.index + 1) + "]" + key: "group_" + root.groupName + "_enable" + } + } + } + Mixxx.ControlProxy { + id: showFourUnitsControl + + group: "[Skin]" + key: "show_4effectunits" + } +} diff --git a/res/skins/LateNightQML/Mixer/MainHeadphoneKnob.qml b/res/skins/LateNightQML/Mixer/MainHeadphoneKnob.qml new file mode 100644 index 000000000000..967fc9191fce --- /dev/null +++ b/res/skins/LateNightQML/Mixer/MainHeadphoneKnob.qml @@ -0,0 +1,42 @@ +import QtQuick +import "../Controls" as LateNightControls +import "../LateNightTheme" + +Item { + id: root + + property color arcColor: indicatorColor === "red" ? LateNightTheme.mixerArcMainBalanceColor : LateNightTheme.mixerArcGainLowColor + property int arcStart: key === "gain" || key === "headGain" ? LateNightControls.Knob.ArcStart.Minimum : LateNightControls.Knob.ArcStart.Center + required property string group + property string indicatorColor: "orange" + required property string key + required property string label + + implicitHeight: 47 + implicitWidth: 42 + + LateNightControls.Knob { + anchors.horizontalCenter: parent.horizontalCenter + backgroundSource: LateNightTheme.assetMainKnobBackground + displayArc: true + displayArcColor: root.arcColor + displayArcRadius: LateNightTheme.mixerArcRadiusCompact + displayArcStart: root.arcStart + displayArcWidth: LateNightTheme.mixerArcWidth + group: root.group + height: 30 + indicatorColor: root.indicatorColor + indicatorKind: "main" + key: root.key + width: 35 + y: 0 + } + Text { + anchors.bottom: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + color: LateNightTheme.mixerDimTextColor + font.bold: true + font.pixelSize: 13 + text: root.label + } +} diff --git a/res/skins/LateNightQML/Mixer/MainHeadphonePanel.qml b/res/skins/LateNightQML/Mixer/MainHeadphonePanel.qml new file mode 100644 index 000000000000..25224e26c990 --- /dev/null +++ b/res/skins/LateNightQML/Mixer/MainHeadphonePanel.qml @@ -0,0 +1,146 @@ +import Mixxx 1.0 as Mixxx +import QtQuick +import "../Controls" as LateNightControls +import "../LateNightTheme" + +Item { + id: root + + readonly property int separatorY: root.show4decks ? 210 : 100 + property bool show4decks: false + + implicitHeight: root.show4decks ? 405 : 203 + implicitWidth: 96 + + Rectangle { + anchors.fill: parent + color: "#171719" + } + Rectangle { + color: LateNightTheme.mixerPanelBorderDark + height: 1 + width: parent.width + y: root.separatorY + } + Rectangle { + color: LateNightTheme.mixerPanelBorderLight + height: 1 + width: parent.width + y: root.separatorY + 1 + } + Item { + height: root.separatorY + width: parent.width + + MainHeadphoneKnob { + group: "[Master]" + indicatorColor: "orange" + key: "gain" + label: "MAIN" + x: 7 + y: root.show4decks ? 10 : 20 + } + MainHeadphoneKnob { + group: "[Master]" + indicatorColor: "red" + key: "balance" + label: "BAL" + x: 49 + y: root.show4decks ? 10 : 20 + } + Rectangle { + color: "#040404" + height: 96 + visible: root.show4decks + width: 14 + x: 38 + y: 91 + } + LateNightControls.ImageVuMeter { + backgroundVariant: 0 + drawGroove: false + group: "[Main]" + height: 96 + levelKey: "vu_meter_left" + peakKey: "peak_indicator_left" + visible: root.show4decks + width: 6 + x: 39 + y: 91 + } + LateNightControls.ImageVuMeter { + backgroundVariant: 0 + drawGroove: false + group: "[Main]" + height: 96 + levelKey: "vu_meter_right" + peakKey: "peak_indicator_right" + visible: root.show4decks + width: 6 + x: 45 + y: 91 + } + FxAssignButtons { + anchors.horizontalCenter: parent.horizontalCenter + groupName: "[MasterOutput]" + y: root.show4decks ? 56 : 76 + } + } + Item { + height: parent.height - y + width: parent.width + y: root.show4decks ? root.separatorY : root.separatorY + 2 + + MainHeadphoneKnob { + group: "[Master]" + indicatorColor: "orange" + key: "headGain" + label: "HEAD" + x: 7 + y: root.show4decks ? 58 : 9 + } + MainHeadphoneKnob { + group: "[Master]" + indicatorColor: "red" + key: "headMix" + label: "MIX" + x: 49 + y: root.show4decks ? 58 : 9 + } + Item { + anchors.horizontalCenter: parent.horizontalCenter + height: 22 + width: 48 + y: root.show4decks ? 106 : 51 + + Image { + anchors.fill: parent + fillMode: Image.PreserveAspectFit + source: splitControl.value > 0 ? LateNightTheme.assetMixerSplitActiveIcon : LateNightTheme.assetMixerSplitIcon + } + Text { + anchors.centerIn: parent + color: splitControl.value > 0 ? LateNightTheme.mixerControlTextColor : LateNightTheme.mixerDimTextColor + font.bold: true + font.pixelSize: 12 + text: "SPLIT" + } + MouseArea { + anchors.fill: parent + + onClicked: splitControl.value = splitControl.value > 0 ? 0 : 1 + } + } + FxAssignButtons { + anchors.horizontalCenter: parent.horizontalCenter + groupName: "[Headphone]" + y: root.show4decks ? 135 : 74 + } + } + Mixxx.ControlProxy { + id: splitControl + + group: "[Master]" + key: "headSplit" + } +} diff --git a/res/skins/LateNightQML/Mixer/Mixer.qml b/res/skins/LateNightQML/Mixer/Mixer.qml new file mode 100644 index 000000000000..60cb0be77f15 --- /dev/null +++ b/res/skins/LateNightQML/Mixer/Mixer.qml @@ -0,0 +1,89 @@ +import Mixxx 1.0 as Mixxx +import QtQuick +import "../LateNightTheme" + +Item { + id: root + + required property var groups + property bool show4decks: false + + implicitHeight: Math.max(decks.implicitHeight, showMainHeadMixerControl.value > 0 ? mainHeadphonePanel.implicitHeight : 0) + implicitWidth: decks.implicitWidth + (showMainHeadMixerControl.value > 0 ? 98 : 0) + + Rectangle { + anchors.fill: parent + color: LateNightTheme.mixerPanelColor + } + Rectangle { + anchors.fill: parent + border.color: LateNightTheme.mixerPanelBorderLight + border.width: 1 + color: "transparent" + } + MixerDecks { + id: decks + + groups: root.groups + height: root.implicitHeight + show4decks: root.show4decks + showEqKillButtons: showEqKillButtonsControl.value > 0 + showEqKnobs: showEqKnobsControl.value > 0 + showXfader: showXfaderControl.value > 0 + x: 0 + y: 0 + } + Item { + height: parent.height + visible: showMainHeadMixerControl.value > 0 + width: 2 + x: decks.implicitWidth + + Rectangle { + anchors.left: parent.left + color: LateNightTheme.mixerMainSeparatorDarkColor + height: parent.height + width: 1 + } + Rectangle { + anchors.right: parent.right + color: LateNightTheme.mixerMainSeparatorLightColor + height: parent.height + width: 1 + } + } + MainHeadphonePanel { + id: mainHeadphonePanel + + height: root.implicitHeight + show4decks: root.show4decks + visible: showMainHeadMixerControl.value > 0 + width: 96 + x: decks.implicitWidth + 2 + y: 0 + } + Mixxx.ControlProxy { + id: showEqKnobsControl + + group: "[Skin]" + key: "show_eq_knobs" + } + Mixxx.ControlProxy { + id: showEqKillButtonsControl + + group: "[Skin]" + key: "show_eq_kill_buttons" + } + Mixxx.ControlProxy { + id: showXfaderControl + + group: "[Skin]" + key: "show_xfader" + } + Mixxx.ControlProxy { + id: showMainHeadMixerControl + + group: "[Skin]" + key: "show_main_head_mixer" + } +} diff --git a/res/skins/LateNightQML/Mixer/MixerChannel2Deck.qml b/res/skins/LateNightQML/Mixer/MixerChannel2Deck.qml new file mode 100644 index 000000000000..b051f67e382c --- /dev/null +++ b/res/skins/LateNightQML/Mixer/MixerChannel2Deck.qml @@ -0,0 +1,61 @@ +import QtQuick +import "../Controls" as LateNightControls +import "../LateNightTheme" + +Item { + id: root + + required property string group + property bool mirror: false + property bool showEqKillButtons: true + property bool showEqKnobs: true + property bool showXfader: true + + implicitHeight: 203 + implicitWidth: root.showEqKnobs ? (root.showEqKillButtons ? 109 : 91) : 42 + + Item { + anchors.left: root.mirror ? parent.left : undefined + anchors.right: root.mirror ? undefined : parent.right + height: parent.height + width: 109 + + EqQuickColumn { + id: eqColumn + + alignRight: root.mirror + group: root.group + showEqKillButtons: root.showEqKillButtons + showEqKnobs: root.showEqKnobs + x: root.mirror ? 42 : 0 + y: 10 + } + LateNightControls.Fader { + backgroundSource: LateNightTheme.assetMixerVolumeSliderBackground + group: root.group + handleSource: LateNightTheme.assetMixerVolumeSliderHandle + height: 107 + key: "volume" + orientation: Qt.Vertical + width: 42 + x: root.mirror ? 3 : 64 + y: 49 + } + QuickEffectSelector { + arrowOnRight: root.mirror + group: root.group + visible: root.showEqKnobs + width: root.showEqKillButtons ? 62 : 40 + x: root.mirror ? 42 : (root.showEqKillButtons ? 5 : 18) + y: 156 + } + CrossfaderAssignButton { + group: root.group + leftStyle: root.mirror ? "warning" : "default" + rightStyle: root.mirror ? "default" : "warning" + visible: root.showXfader + x: root.showEqKnobs ? (root.mirror ? 46 : 22) : (root.mirror ? 18 : 57) + y: 180 + } + } +} diff --git a/res/skins/LateNightQML/Mixer/MixerChannel4Deck.qml b/res/skins/LateNightQML/Mixer/MixerChannel4Deck.qml new file mode 100644 index 000000000000..33d5fa87a33f --- /dev/null +++ b/res/skins/LateNightQML/Mixer/MixerChannel4Deck.qml @@ -0,0 +1,115 @@ +import QtQuick +import "../Controls" as LateNightControls +import "../LateNightTheme" + +Item { + id: root + + required property string group + property string leftStyle: "default" + property string rightStyle: "default" + property bool showEqKillButtons: true + property bool showEqKnobs: true + property bool showXfader: true + + implicitHeight: root.showXfader ? 359 : 344 + implicitWidth: !root.showEqKnobs || root.showEqKillButtons ? 62 : 44 + + Item { + anchors.right: parent.right + height: parent.height + width: 62 + + LateNightControls.Knob { + backgroundSource: LateNightTheme.assetRegularKnobBackground + displayArc: true + displayArcColor: LateNightTheme.mixerArcGainColor + displayArcRadius: LateNightTheme.mixerArcRadiusBig + displayArcStart: LateNightControls.Knob.ArcStart.Center + displayArcWidth: LateNightTheme.mixerArcWidth + group: root.group + height: 34 + indicatorColor: "orange" + indicatorKind: "regular" + key: "pregain" + width: 40 + x: 18 + y: 4 + } + EqKnobRow { + group: root.group + key: "filterHigh" + killIcon: LateNightTheme.assetMixerEqKillHighIcon + showKill: root.showEqKillButtons + visible: root.showEqKnobs + width: 58 + x: 0 + y: 43 + } + EqKnobRow { + group: root.group + key: "filterMid" + killIcon: LateNightTheme.assetMixerEqKillMidIcon + showKill: root.showEqKillButtons + visible: root.showEqKnobs + width: 58 + x: 0 + y: 80 + } + EqKnobRow { + group: root.group + key: "filterLow" + killIcon: LateNightTheme.assetMixerEqKillLowIcon + showKill: root.showEqKillButtons + visible: root.showEqKnobs + width: 58 + x: 0 + y: 117 + } + QuickEffectRow { + group: root.group + visible: root.showEqKnobs + width: 58 + x: 0 + y: 154 + } + QuickEffectSelector { + group: root.group + visible: root.showEqKnobs + width: 62 + x: 0 + y: 188 + } + PflButton { + group: root.group + x: 28 + y: 210 + } + LateNightControls.ImageVuMeter { + group: root.group + height: 96 + width: 20 + x: 0 + y: 237 + } + LateNightControls.Fader { + backgroundSource: LateNightTheme.assetMixerVolumeSliderBackground + group: root.group + handleSource: LateNightTheme.assetMixerVolumeSliderHandle + height: 107 + key: "volume" + orientation: Qt.Vertical + width: 42 + x: 20 + y: 237 + } + CrossfaderAssignButton { + group: root.group + leftStyle: root.leftStyle + rightStyle: root.rightStyle + visible: root.showXfader + x: 14 + y: 344 + } + } +} diff --git a/res/skins/LateNightQML/Mixer/MixerDecks.qml b/res/skins/LateNightQML/Mixer/MixerDecks.qml new file mode 100644 index 000000000000..d3155325ab6a --- /dev/null +++ b/res/skins/LateNightQML/Mixer/MixerDecks.qml @@ -0,0 +1,184 @@ +import QtQuick +import "../Controls" as LateNightControls +import "../LateNightTheme" + +Item { + id: root + + required property var groups + property bool show4decks: false + property bool showEqKillButtons: true + property bool showEqKnobs: true + property bool showXfader: true + + implicitHeight: root.show4decks ? (root.showXfader ? 405 : 352) : (root.showXfader ? 203 : (root.showEqKnobs ? 182 : 164)) + implicitWidth: root.show4decks ? ((!root.showEqKnobs || root.showEqKillButtons) ? 278 : 206) : (root.showEqKnobs ? (root.showEqKillButtons ? 278 : 242) : (root.showXfader ? 163 : 151)) + + Item { + anchors.horizontalCenter: parent.horizontalCenter + height: parent.height + visible: !root.show4decks + width: 278 + y: root.showXfader ? 0 : Math.max(0, (parent.height - (root.showEqKnobs ? 174 : 156)) / 2) + + MixerChannel2Deck { + anchors.right: parent.horizontalCenter + anchors.rightMargin: 25 + group: root.groups[0] + showEqKillButtons: root.showEqKillButtons + showEqKnobs: root.showEqKnobs + showXfader: root.showXfader + } + MixerChannel2Deck { + anchors.left: parent.horizontalCenter + anchors.leftMargin: 27 + group: root.groups[1] + mirror: true + showEqKillButtons: root.showEqKillButtons + showEqKnobs: root.showEqKnobs + showXfader: root.showXfader + } + PflButton { + group: root.groups[0] + x: 110 + y: 17 + } + PflButton { + group: root.groups[1] + x: 139 + y: 17 + } + LateNightControls.Knob { + backgroundSource: LateNightTheme.assetRegularKnobBackground + displayArc: true + displayArcColor: LateNightTheme.mixerArcGainColor + displayArcRadius: LateNightTheme.mixerArcRadiusBig + displayArcStart: LateNightControls.Knob.ArcStart.Center + displayArcWidth: LateNightTheme.mixerArcWidth + group: root.groups[0] + height: 34 + indicatorColor: "orange" + indicatorKind: "regular" + key: "pregain" + width: 40 + x: 70 + y: 10 + } + LateNightControls.Knob { + backgroundSource: LateNightTheme.assetRegularKnobBackground + displayArc: true + displayArcColor: LateNightTheme.mixerArcGainColor + displayArcRadius: LateNightTheme.mixerArcRadiusBig + displayArcStart: LateNightControls.Knob.ArcStart.Center + displayArcWidth: LateNightTheme.mixerArcWidth + group: root.groups[1] + height: 34 + indicatorColor: "orange" + indicatorKind: "regular" + key: "pregain" + width: 40 + x: 170 + y: 10 + } + LateNightControls.ImageVuMeter { + group: root.groups[0] + height: 96 + width: 8 + x: 115 + y: 55 + } + Rectangle { + color: "#040404" + height: 96 + width: 14 + x: 130 + y: 55 + } + LateNightControls.ImageVuMeter { + backgroundVariant: 0 + drawGroove: false + group: "[Main]" + height: 96 + levelKey: "vu_meter_left" + peakKey: "peak_indicator_left" + width: 6 + x: 131 + y: 55 + } + LateNightControls.ImageVuMeter { + backgroundVariant: 0 + drawGroove: false + group: "[Main]" + height: 96 + levelKey: "vu_meter_right" + peakKey: "peak_indicator_right" + width: 6 + x: 137 + y: 55 + } + LateNightControls.ImageVuMeter { + group: root.groups[1] + height: 96 + width: 8 + x: 151 + y: 55 + } + Crossfader { + anchors.horizontalCenter: parent.horizontalCenter + compact: !root.showEqKnobs + groups: [root.groups[0], root.groups[1]] + showAssignments: false + visible: root.showXfader + y: 155 + } + } + Item { + anchors.fill: parent + visible: root.show4decks + + Row { + anchors.horizontalCenter: parent.horizontalCenter + spacing: 2 + + MixerChannel4Deck { + group: root.groups[2] + leftStyle: "default" + rightStyle: "warning" + showEqKillButtons: root.showEqKillButtons + showEqKnobs: root.showEqKnobs + showXfader: root.showXfader + } + MixerChannel4Deck { + group: root.groups[0] + leftStyle: "default" + rightStyle: "warning" + showEqKillButtons: root.showEqKillButtons + showEqKnobs: root.showEqKnobs + showXfader: root.showXfader + } + MixerChannel4Deck { + group: root.groups[1] + leftStyle: "warning" + rightStyle: "default" + showEqKillButtons: root.showEqKillButtons + showEqKnobs: root.showEqKnobs + showXfader: root.showXfader + } + MixerChannel4Deck { + group: root.groups[3] + leftStyle: "warning" + rightStyle: "default" + showEqKillButtons: root.showEqKillButtons + showEqKnobs: root.showEqKnobs + showXfader: root.showXfader + } + } + Crossfader { + anchors.horizontalCenter: parent.horizontalCenter + groups: root.groups + showAssignments: false + visible: root.showXfader + y: 365 + } + } +} diff --git a/res/skins/LateNightQML/Mixer/PflButton.qml b/res/skins/LateNightQML/Mixer/PflButton.qml new file mode 100644 index 000000000000..c0e79f83c8e1 --- /dev/null +++ b/res/skins/LateNightQML/Mixer/PflButton.qml @@ -0,0 +1,42 @@ +import Mixxx 1.0 as Mixxx +import QtQuick +import "../LateNightTheme" + +Item { + id: root + + required property string group + property string key: "pfl" + + implicitHeight: 26 + implicitWidth: 26 + + Rectangle { + anchors.fill: parent + anchors.margins: 2 + color: pflControl.value > 0 ? LateNightTheme.mixerPflActiveFillColor : LateNightTheme.deckEmbeddedButtonInactiveColor + } + Image { + anchors.fill: parent + fillMode: Image.Stretch + source: LateNightTheme.assetMixerPflBackground + } + Image { + anchors.centerIn: parent + fillMode: Image.PreserveAspectFit + height: 26 + source: pflControl.value > 0 ? LateNightTheme.assetMixerPflActiveIcon : LateNightTheme.assetMixerPflIcon + width: 26 + } + MouseArea { + anchors.fill: parent + + onClicked: pflControl.value = pflControl.value > 0 ? 0 : 1 + } + Mixxx.ControlProxy { + id: pflControl + + group: root.group + key: root.key + } +} diff --git a/res/skins/LateNightQML/Mixer/QuickEffectEnableButton.qml b/res/skins/LateNightQML/Mixer/QuickEffectEnableButton.qml new file mode 100644 index 000000000000..4481a3200938 --- /dev/null +++ b/res/skins/LateNightQML/Mixer/QuickEffectEnableButton.qml @@ -0,0 +1,43 @@ +import Mixxx 1.0 as Mixxx +import QtQuick +import "../LateNightTheme" + +Item { + id: root + + readonly property real enabledValue: enabledControl.value + required property string quickEffectGroup + + function toggle() { + enabledControl.value = enabledControl.value > 0 ? 0 : 1; + } + + implicitHeight: 18 + implicitWidth: 18 + + Rectangle { + anchors.fill: parent + border.color: "#050505" + border.width: 2 + color: enabledControl.value > 0 ? LateNightTheme.mixerQuickEffectActiveColor : "#111113" + } + Image { + anchors.centerIn: parent + fillMode: Image.PreserveAspectFit + height: 18 + source: LateNightTheme.assetMixerQuickEffectIcon + visible: enabledControl.value <= 0 + width: 18 + } + MouseArea { + anchors.fill: parent + + onClicked: root.toggle() + } + Mixxx.ControlProxy { + id: enabledControl + + group: root.quickEffectGroup + key: "enabled" + } +} diff --git a/res/skins/LateNightQML/Mixer/QuickEffectRow.qml b/res/skins/LateNightQML/Mixer/QuickEffectRow.qml new file mode 100644 index 000000000000..3e9d425818d0 --- /dev/null +++ b/res/skins/LateNightQML/Mixer/QuickEffectRow.qml @@ -0,0 +1,60 @@ +import QtQuick +import "../Controls" as LateNightControls +import "../LateNightTheme" + +Item { + id: root + + required property string group + property bool mirror: false + readonly property string quickEffectGroup: "[QuickEffectRack1_" + group + "]" + property bool showEqKillButtons: true + + implicitHeight: 34 + implicitWidth: 58 + + QuickEffectEnableButton { + id: enableButton + + anchors.left: root.mirror ? undefined : parent.left + anchors.right: root.mirror ? parent.right : undefined + anchors.verticalCenter: parent.verticalCenter + quickEffectGroup: root.quickEffectGroup + visible: root.showEqKillButtons + } + Image { + id: statusDot + + height: 9 + source: enableButton.enabledValue > 0 ? LateNightTheme.optionalMixerEqKillDotActiveGreen : LateNightTheme.optionalMixerEqKillDotOff + visible: LateNightTheme.isPaleMoon && !root.showEqKillButtons + width: 9 + x: root.mirror ? quickEffectKnob.x + 32 : quickEffectKnob.x + y: 27 + + MouseArea { + anchors.fill: parent + + onClicked: enableButton.toggle() + } + } + LateNightControls.Knob { + id: quickEffectKnob + + anchors.left: root.mirror ? parent.left : undefined + anchors.right: root.mirror ? undefined : parent.right + anchors.verticalCenter: parent.verticalCenter + backgroundSource: LateNightTheme.assetRegularKnobBackground + displayArc: true + displayArcColor: LateNightTheme.mixerArcQuickEffectColor + displayArcRadius: LateNightTheme.mixerArcRadiusBig + displayArcStart: LateNightControls.Knob.ArcStart.Center + displayArcWidth: LateNightTheme.mixerArcWidth + group: root.quickEffectGroup + height: 34 + indicatorColor: "green" + indicatorKind: "regular" + key: "super1" + width: 40 + } +} diff --git a/res/skins/LateNightQML/Mixer/QuickEffectSelector.qml b/res/skins/LateNightQML/Mixer/QuickEffectSelector.qml new file mode 100644 index 000000000000..fcf4e135acb8 --- /dev/null +++ b/res/skins/LateNightQML/Mixer/QuickEffectSelector.qml @@ -0,0 +1,121 @@ +import Mixxx 1.0 as Mixxx +import QtQuick +import QtQuick.Controls +import "../LateNightTheme" + +ComboBox { + id: root + + property bool arrowOnRight: false + readonly property bool compact: width <= 40 + required property string group + property int popupMaxItem: 44 + property int popupWidth: 160 + readonly property string quickEffectGroup: "[QuickEffectRack1_" + group + "]" + + currentIndex: root.count > 0 ? Math.max(0, Math.min(root.count - 1, Math.round(presetControl.value))) : -1 + font.family: "Open Sans" + font.pixelSize: 13 + font.weight: Font.Medium + implicitHeight: 18 + implicitWidth: 62 + model: Mixxx.EffectsManager.quickChainPresetModel + textRole: "display" + + background: Item { + } + contentItem: Item { + implicitHeight: root.implicitHeight + implicitWidth: root.implicitWidth + + Image { + id: arrow + + anchors.verticalCenter: parent.verticalCenter + fillMode: Image.PreserveAspectFit + height: 24 + source: LateNightTheme.assetToolbarDropdownIcon + width: 16 + x: root.arrowOnRight ? parent.width - width + 1 : -3 + } + Text { + anchors.fill: parent + anchors.leftMargin: root.compact && root.arrowOnRight ? -6 : 0 + anchors.rightMargin: root.compact && !root.arrowOnRight ? -6 : 0 + color: LateNightTheme.mixerQuickEffectSelectorTextColor + elide: Text.ElideRight + font: root.font + horizontalAlignment: root.arrowOnRight ? Text.AlignRight : Text.AlignLeft + leftPadding: root.arrowOnRight ? 0 : (root.compact ? 10 : 13) + rightPadding: root.arrowOnRight ? (root.compact ? 10 : 13) : 0 + text: root.displayText || qsTr("Filter") + verticalAlignment: Text.AlignVCenter + } + } + delegate: ItemDelegate { + id: presetDelegate + + required property int index + + checkable: false + checked: root.currentIndex === index + height: 20 + highlighted: root.highlightedIndex === index + padding: 0 + width: ListView.view ? ListView.view.width : root.popupWidth + + background: Rectangle { + color: presetDelegate.highlighted ? "#2c454f" : "transparent" + radius: presetDelegate.highlighted ? 1 : 0 + } + contentItem: Text { + color: presetDelegate.checked || presetDelegate.highlighted ? "#ffffff" : LateNightTheme.mixerQuickEffectSelectorTextColor + elide: Text.ElideRight + font: root.font + leftPadding: 20 + rightPadding: 4 + text: root.textAt(presetDelegate.index) + verticalAlignment: Text.AlignVCenter + } + } + indicator: Item { + width: 0 + } + popup: Popup { + height: Math.min(contentItem.contentHeight, root.popupMaxItem * 20) + 2 + padding: 1 + width: root.popupWidth + x: root.arrowOnRight ? root.width - width : 0 + y: root.height + + background: Rectangle { + border.color: "#333333" + border.width: 1 + color: "#151517" + radius: 1 + } + contentItem: ListView { + id: presetList + + clip: true + currentIndex: root.highlightedIndex + implicitHeight: contentHeight + model: root.popup.visible ? root.delegateModel : null + + ScrollBar.vertical: ScrollBar { + policy: ScrollBar.AlwaysOff + } + } + + onOpened: presetList.contentY = 0 + } + + onActivated: index => presetControl.value = index + + Mixxx.ControlProxy { + id: presetControl + + group: root.quickEffectGroup + key: "loaded_chain_preset" + } +} diff --git a/res/skins/LateNightQML/main.qml b/res/skins/LateNightQML/main.qml index c731ee4b2726..931703112354 100644 --- a/res/skins/LateNightQML/main.qml +++ b/res/skins/LateNightQML/main.qml @@ -1,6 +1,8 @@ import "../../qml" as Skin import "LateNightTheme" import "Deck" as LateNightDeck +import "Effects" as LateNightEffects +import "Mixer" as LateNightMixer import "Toolbar" as LateNightToolbar import "Waveforms" as LateNightWaveforms import Mixxx 1.0 as Mixxx @@ -13,15 +15,15 @@ ApplicationWindow { property alias editDeck: toolbar.editDeck property var focusedDeck: null - property alias maximizeLibrary: toolbar.maximizeLibrary readonly property int fullDeckHeight: 206 + property alias maximizeLibrary: toolbar.maximizeLibrary readonly property int minimizedDeckHeight: 80 readonly property int numDecks: 4 readonly property int numSamplers: 64 readonly property bool show4decks: toolbar.show4decks + property alias showEffects: toolbar.showEffects readonly property bool showMaximizedDecks: toolbar.showMaximizedDecks readonly property bool showMixer: toolbar.showMixer - property alias showEffects: toolbar.showEffects property alias showSamplers: toolbar.showSamplers readonly property bool showWaveforms: toolbar.showWaveforms @@ -49,58 +51,58 @@ ApplicationWindow { } } Mixxx.SkinControlCreator { + defaultValue: 1.0 group: "[Skin]" key: "show_waveforms" persist: true - defaultValue: 1.0 } Mixxx.SkinControlCreator { + defaultValue: 1.0 group: "[Skin]" key: "show_hotcues" persist: true - defaultValue: 1.0 } Mixxx.SkinControlCreator { + defaultValue: 1.0 group: "[Skin]" key: "show_8_hotcues" persist: true - defaultValue: 1.0 } Mixxx.SkinControlCreator { + defaultValue: 1.0 group: "[Skin]" key: "show_intro_outro_cues" persist: true - defaultValue: 1.0 } Mixxx.SkinControlCreator { + defaultValue: 1.0 group: "[Skin]" key: "show_loop_controls" persist: true - defaultValue: 1.0 } Mixxx.SkinControlCreator { + defaultValue: 1.0 group: "[Skin]" key: "show_beatjump_controls" persist: true - defaultValue: 1.0 } Mixxx.SkinControlCreator { + defaultValue: 1.0 group: "[Skin]" key: "show_rate_controls" persist: true - defaultValue: 1.0 } Mixxx.SkinControlCreator { + defaultValue: 1.0 group: "[Skin]" key: "show_rate_control_buttons" persist: true - defaultValue: 1.0 } Mixxx.SkinControlCreator { + defaultValue: 1.0 group: "[Skin]" key: "show_key_controls" persist: true - defaultValue: 1.0 } Mixxx.SkinControlCreator { group: "[Skin]" @@ -108,50 +110,62 @@ ApplicationWindow { persist: true } Mixxx.SkinControlCreator { + defaultValue: 1.0 group: "[Skin]" key: "show_spinnies" persist: true - defaultValue: 1.0 } Mixxx.SkinControlCreator { + defaultValue: 1.0 group: "[Skin]" key: "show_coverart" persist: true - defaultValue: 1.0 } Mixxx.SkinControlCreator { group: "[Skin]" key: "select_big_spinny_or_cover" persist: true } + Mixxx.SkinControlCreator { + defaultValue: 1.0 + group: "[Skin]" + key: "show_effectrack" + persist: true + } Mixxx.SkinControlCreator { group: "[Skin]" key: "show_4effectunits" persist: true } Mixxx.SkinControlCreator { + defaultValue: 0.0 group: "[Skin]" - key: "show_eq_knobs" + key: "show_superknobs" persist: true + } + Mixxx.SkinControlCreator { defaultValue: 1.0 + group: "[Skin]" + key: "show_eq_knobs" + persist: true } Mixxx.SkinControlCreator { + defaultValue: 1.0 group: "[Skin]" key: "show_eq_kill_buttons" persist: true - defaultValue: 1.0 } Mixxx.SkinControlCreator { + defaultValue: 1.0 group: "[Skin]" key: "show_xfader" persist: true - defaultValue: 1.0 } Mixxx.SkinControlCreator { + defaultValue: 1.0 group: "[Skin]" key: "show_main_head_mixer" persist: true - defaultValue: 1.0 } Mixxx.SkinControlCreator { group: "[Skin]" @@ -163,11 +177,6 @@ ApplicationWindow { key: "timing_shift_buttons" persist: true } - Mixxx.SkinControlCreator { - group: "[Skin]" - key: "show_superknobs" - persist: true - } Mixxx.SkinControlCreator { group: "[Skin]" key: "show_sampler_fx" @@ -236,8 +245,8 @@ ApplicationWindow { SplitView.fillHeight: !library.active SplitView.preferredHeight: library.active ? 120 : undefined - visible: root.showWaveforms && !root.maximizeLibrary show4decks: root.show4decks + visible: root.showWaveforms && !root.maximizeLibrary Skin.FadeBehavior on visible { fadeTarget: waveforms @@ -246,9 +255,15 @@ ApplicationWindow { Item { id: deckPane + readonly property real basePaneHeight: Math.max(deckRowsHeight, mixer.visible ? mixer.implicitHeight : 0) + readonly property real deckRowsHeight: root.show4decks ? visibleDeckHeight * 2 : visibleDeckHeight + readonly property real requiredPaneHeight: basePaneHeight + effectsSection.height + readonly property real visibleDeckHeight: root.maximizeLibrary ? (root.showMaximizedDecks ? root.minimizedDeckHeight : 0) : root.fullDeckHeight + SplitView.fillHeight: library.active - SplitView.maximumHeight: library.active ? undefined : mixer.height - SplitView.minimumHeight: mixer.height + SplitView.maximumHeight: library.active ? undefined : requiredPaneHeight + SplitView.minimumHeight: requiredPaneHeight + implicitHeight: requiredPaneHeight width: splitView.width LateNightDeck.Deck { @@ -290,15 +305,16 @@ ApplicationWindow { top: parent.top } } - Skin.Mixer { + LateNightMixer.Mixer { id: mixer anchors.horizontalCenter: parent.horizontalCenter anchors.top: parent.top groups: [deck1.group, deck2.group, deck3.group, deck4.group] - width: visible ? implicitWidth : 0 + height: visible ? implicitHeight : 0 show4decks: root.show4decks visible: root.showMixer && !root.maximizeLibrary + width: visible ? implicitWidth : 0 Behavior on height { SpringAnimation { @@ -513,19 +529,41 @@ ApplicationWindow { // fadeTarget: samplers // } // } - // Skin.EffectRow { - // id: effects - // visible: root.showEffects - // width: parent.width + Item { + id: effectsSection - // Skin.FadeBehavior on visible { - // fadeTarget: effects - // } - // } + clip: true + height: root.showEffects && !root.maximizeLibrary ? effectsRack.implicitHeight : 0 + opacity: root.showEffects && !root.maximizeLibrary ? 1 : 0 + visible: height > 0 + width: parent.width + y: deckPane.basePaneHeight + z: 2 + + Behavior on height { + NumberAnimation { + duration: 150 + easing.type: Easing.OutCubic + } + } + Behavior on opacity { + NumberAnimation { + duration: 120 + } + } + + LateNightEffects.EffectsRack { + id: effectsRack + + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + } + } Loader { id: library - active: root.maximizeLibrary || root.height - mixer.height >= 400 + active: root.maximizeLibrary || root.height - deckPane.requiredPaneHeight >= 400 width: parent.width sourceComponent: Component { @@ -559,7 +597,7 @@ ApplicationWindow { } }, State { - when: !root.maximizeLibrary && root.height - mixer.height < 400 + when: !root.maximizeLibrary && root.height - deckPane.requiredPaneHeight < 400 PropertyChanges { target: library @@ -570,11 +608,10 @@ ApplicationWindow { anchors { bottom: parent.bottom - top: mixer.bottom + top: effectsSection.bottom } } } } } - } diff --git a/src/qml/qmlchainpresetmodel.cpp b/src/qml/qmlchainpresetmodel.cpp index 4a3755243339..15fcff826d9c 100644 --- a/src/qml/qmlchainpresetmodel.cpp +++ b/src/qml/qmlchainpresetmodel.cpp @@ -5,6 +5,7 @@ #include "effects/backends/effectmanifest.h" #include "effects/effectsmanager.h" #include "effects/presets/effectchainpreset.h" +#include "effects/presets/effectpreset.h" #include "moc_qmlchainpresetmodel.cpp" namespace mixxx { @@ -13,37 +14,74 @@ namespace { const QHash kRoleNames = { {Qt::DisplayRole, "display"}, {Qt::ToolTipRole, "tooltip"}, + {QmlChainPresetModel::NameRole, "name"}, + {QmlChainPresetModel::ReadOnlyRole, "readOnly"}, + {QmlChainPresetModel::PresetDisplayRole, "presetDisplay"}, }; } QmlChainPresetModel::QmlChainPresetModel( - EffectChainPresetManagerPointer effectChainPresetManager, + std::shared_ptr pEffectsManager, + PresetType presetType, QObject* parent) : QAbstractListModel(parent), - m_pEffectChainPresetManager(effectChainPresetManager) { + m_pEffectsManager(std::move(pEffectsManager)), + m_pEffectChainPresetManager(m_pEffectsManager->getChainPresetManager()), + m_presetType(presetType) { slotUpdated(); - connect(m_pEffectChainPresetManager.get(), - &EffectChainPresetManager::quickEffectChainPresetListUpdated, - this, - &QmlChainPresetModel::slotUpdated); + if (m_presetType == PresetType::Quick) { + connect(m_pEffectChainPresetManager.get(), + &EffectChainPresetManager::quickEffectChainPresetListUpdated, + this, + &QmlChainPresetModel::slotUpdated); + } else { + connect(m_pEffectChainPresetManager.get(), + &EffectChainPresetManager::effectChainPresetListUpdated, + this, + &QmlChainPresetModel::slotUpdated); + } } void QmlChainPresetModel::slotUpdated() { beginResetModel(); - m_effectChainPresets = m_pEffectChainPresetManager->getQuickEffectPresetsSorted(); + m_effectChainPresets = m_presetType == PresetType::Quick + ? m_pEffectChainPresetManager->getQuickEffectPresetsSorted() + : m_pEffectChainPresetManager->getPresetsSorted(); endResetModel(); } QVariant QmlChainPresetModel::data(const QModelIndex& index, int role) const { - if (index.row() >= m_effectChainPresets.size()) { + if (!index.isValid() || index.row() < 0 || index.row() >= m_effectChainPresets.size()) { return QVariant(); } const EffectChainPresetPointer pPreset = m_effectChainPresets.at(index.row()); switch (role) { case Qt::DisplayRole: - case Qt::ToolTipRole: + case NameRole: + case PresetDisplayRole: return pPreset->name(); + case ReadOnlyRole: + return pPreset->isReadOnly(); + case Qt::ToolTipRole: { + QStringList effectNames; + for (const auto& pEffectPreset : pPreset->effectPresets()) { + if (pEffectPreset->isEmpty()) { + continue; + } + const EffectManifestPointer pManifest = + m_pEffectsManager->getBackendManager()->getManifest(pEffectPreset); + if (pManifest) { + effectNames.append(pManifest->name()); + } + } + QString tooltip = QStringLiteral("%1").arg(pPreset->name().toHtmlEscaped()); + if (effectNames.size() > 1) { + tooltip.append(QStringLiteral("
")); + tooltip.append(effectNames.join(QStringLiteral("
"))); + } + return tooltip; + } default: return QVariant(); } diff --git a/src/qml/qmlchainpresetmodel.h b/src/qml/qmlchainpresetmodel.h index a86c7182288a..73dd096f79d3 100644 --- a/src/qml/qmlchainpresetmodel.h +++ b/src/qml/qmlchainpresetmodel.h @@ -1,24 +1,35 @@ #pragma once #include #include +#include #include "effects/defs.h" +class EffectsManager; + namespace mixxx { namespace qml { class QmlChainPresetModel : public QAbstractListModel { Q_OBJECT QML_NAMED_ELEMENT(ChainPresetModel) - QML_UNCREATABLE("Only accessible via Mixxx.EffectsManager.quickEffectPresetsModel") + QML_UNCREATABLE("Only accessible via Mixxx.EffectsManager preset model properties") public: + enum class PresetType { + Standard, + Quick, + }; + enum Roles { - EffectIdRole = Qt::UserRole + 1, + NameRole = Qt::UserRole + 1, + ReadOnlyRole, + PresetDisplayRole, }; Q_ENUM(Roles) explicit QmlChainPresetModel( - EffectChainPresetManagerPointer effectChainPresetManager, + std::shared_ptr pEffectsManager, + PresetType presetType, QObject* parent = nullptr); QVariant data(const QModelIndex& index, int role) const override; @@ -30,7 +41,9 @@ class QmlChainPresetModel : public QAbstractListModel { void slotUpdated(); private: + const std::shared_ptr m_pEffectsManager; const EffectChainPresetManagerPointer m_pEffectChainPresetManager; + const PresetType m_presetType; QList m_effectChainPresets; }; diff --git a/src/qml/qmleffectmanifestparametersmodel.cpp b/src/qml/qmleffectmanifestparametersmodel.cpp index 4f5eac87ad03..dd3a259f7a2e 100644 --- a/src/qml/qmleffectmanifestparametersmodel.cpp +++ b/src/qml/qmleffectmanifestparametersmodel.cpp @@ -3,30 +3,59 @@ #include #include "effects/backends/effectmanifest.h" +#include "effects/effectparameter.h" +#include "effects/effectslot.h" #include "moc_qmleffectmanifestparametersmodel.cpp" namespace mixxx { namespace qml { namespace { const QHash kRoleNames = { - {QmlEffectManifestParametersModel::IdRole, "id"}, + {QmlEffectManifestParametersModel::IdRole, "parameterId"}, {QmlEffectManifestParametersModel::NameRole, "name"}, {QmlEffectManifestParametersModel::ShortNameRole, "shortName"}, {QmlEffectManifestParametersModel::DescriptionRole, "description"}, {QmlEffectManifestParametersModel::TypeRole, "type"}, {QmlEffectManifestParametersModel::ControlKeyRole, "controlKey"}, + {QmlEffectManifestParametersModel::LoadedRole, "loaded"}, }; } QmlEffectManifestParametersModel::QmlEffectManifestParametersModel( - EffectManifestPointer pEffectManifest, + EffectSlotPointer pEffectSlot, QObject* parent) - : QAbstractListModel(parent), m_pEffectManifest(pEffectManifest) { + : QAbstractListModel(parent), + m_pEffectSlot(std::move(pEffectSlot)) { +} + +EffectParameterPointer QmlEffectManifestParametersModel::loadedParameterForRow( + int row, int* pSlotNumber) const { + const auto pManifest = m_pEffectSlot->getManifest(); + if (!pManifest || row < 0 || row >= pManifest->parameters().size()) { + return nullptr; + } + const auto pManifestParameter = pManifest->parameters().at(row); + const auto loadedParameters = + m_pEffectSlot->getLoadedParameters().value(pManifestParameter->parameterType()); + for (int index = 0; index < loadedParameters.size(); ++index) { + const auto& pParameter = loadedParameters.at(index); + if (pParameter->manifest()->id() == pManifestParameter->id()) { + if (pSlotNumber) { + *pSlotNumber = index + 1; + } + return pParameter; + } + } + return nullptr; } QVariant QmlEffectManifestParametersModel::data(const QModelIndex& index, int role) const { - const QList& parameters = m_pEffectManifest->parameters(); - if (index.row() >= parameters.size()) { + const auto pManifest = m_pEffectSlot->getManifest(); + if (!pManifest) { + return QVariant(); + } + const QList& parameters = pManifest->parameters(); + if (!index.isValid() || index.row() < 0 || index.row() >= parameters.size()) { return QVariant(); } @@ -45,60 +74,28 @@ QVariant QmlEffectManifestParametersModel::data(const QModelIndex& index, int ro // Q_ENUM after #2618 has been merged. return static_cast(pParameter->parameterType()); case QmlEffectManifestParametersModel::ControlKeyRole: { - // FIXME: Unfortunately our effect parameter controls are messed up. - // Even though we only have a single, ordered list of parameters, our - // COs splits up this list into two distinct list (`parameter_N` and - // `button_parameter_M`), and their indices don't match up with the - // original list. - // - // For example, if you have 4 parameters (A: Knob, B: Button, C: Knob, - // D: Knob), one would expect the following control keys: - // parameter1 -> A - // button_parameter2 -> B - // parameter3 -> C - // parameter4 -> D - // - // But in reality, this will lead to the following control keys: - // parameter1 -> A - // button_parameter1 -> B - // parameter2 -> C - // parameter3 -> D - // - // This makes it extremely hard to show the parameters in the correct - // order, because you also need to know how many parameters of the same - // type are in that list. - // - // Due to backwards compatibility, we cannot fix this. This attempts to - // solve this problem by letting the user fetch the appropriate key - // from the model. + int keyNumber = 0; + if (!loadedParameterForRow(index.row(), &keyNumber)) { + return QString(); + } const bool isButton = pParameter->parameterType() == EffectManifestParameter::ParameterType::Button; - int keyNumber = 1; - for (int i = 0; i < index.row(); i++) { - const EffectManifestParameterPointer pPrevParameter = parameters.at(i); - if (isButton == - (pPrevParameter->parameterType() == - EffectManifestParameter::ParameterType::Button)) { - keyNumber++; - } - } - return (isButton ? QStringLiteral("button_parameter%1") : QStringLiteral("parameter%1")) .arg(QString::number(keyNumber)); } + case QmlEffectManifestParametersModel::LoadedRole: + return static_cast(loadedParameterForRow(index.row())); default: return QVariant(); } } int QmlEffectManifestParametersModel::rowCount(const QModelIndex& parent) const { - if (parent.isValid()) { + if (parent.isValid() || !m_pEffectSlot->getManifest()) { return 0; } - - // Add +1 because we also include "no effect" in the model - return m_pEffectManifest->parameters().size(); + return m_pEffectSlot->getManifest()->parameters().size(); } QHash QmlEffectManifestParametersModel::roleNames() const { diff --git a/src/qml/qmleffectmanifestparametersmodel.h b/src/qml/qmleffectmanifestparametersmodel.h index a85660c39359..8483eba7274a 100644 --- a/src/qml/qmleffectmanifestparametersmodel.h +++ b/src/qml/qmleffectmanifestparametersmodel.h @@ -20,11 +20,12 @@ class QmlEffectManifestParametersModel : public QAbstractListModel { DescriptionRole, TypeRole, ControlKeyRole, + LoadedRole, }; Q_ENUM(Roles) explicit QmlEffectManifestParametersModel( - EffectManifestPointer pManifest, + EffectSlotPointer pEffectSlot, QObject* parent = nullptr); QVariant data(const QModelIndex& index, int role) const override; @@ -33,7 +34,9 @@ class QmlEffectManifestParametersModel : public QAbstractListModel { Q_INVOKABLE QVariant get(int row) const; private: - const EffectManifestPointer m_pEffectManifest; + EffectParameterPointer loadedParameterForRow(int row, int* pSlotNumber = nullptr) const; + + const EffectSlotPointer m_pEffectSlot; }; } // namespace qml diff --git a/src/qml/qmleffectslotproxy.cpp b/src/qml/qmleffectslotproxy.cpp index ffd78f3e267f..f772484fe7db 100644 --- a/src/qml/qmleffectslotproxy.cpp +++ b/src/qml/qmleffectslotproxy.cpp @@ -4,7 +4,9 @@ #include #include "effects/effectchain.h" +#include "effects/effectparameter.h" #include "effects/effectslot.h" +#include "effects/presets/effectpresetmanager.h" #include "moc_qmleffectslotproxy.cpp" namespace mixxx { @@ -57,6 +59,20 @@ QString QmlEffectSlotProxy::getEffectId() const { return m_pEffectSlot->id(); } +bool QmlEffectSlotProxy::isLoaded() const { + return m_pEffectSlot->isLoaded(); +} + +QString QmlEffectSlotProxy::getEffectName() const { + const auto pManifest = m_pEffectSlot->getManifest(); + return pManifest ? pManifest->displayName() : kNoEffectString; +} + +QString QmlEffectSlotProxy::getEffectDescription() const { + const auto pManifest = m_pEffectSlot->getManifest(); + return pManifest ? pManifest->description() : QString(); +} + void QmlEffectSlotProxy::setEffectId(const QString& effectId) { const EffectManifestPointer pManifest = m_pEffectsManager->getBackendManager()->getManifestFromUniqueId( @@ -65,15 +81,47 @@ void QmlEffectSlotProxy::setEffectId(const QString& effectId) { } QmlEffectManifestParametersModel* QmlEffectSlotProxy::getParametersModel() const { - const EffectManifestPointer pManifest = m_pEffectSlot->getManifest(); - if (!pManifest) { + if (!m_pEffectSlot->getManifest()) { return nullptr; } - QmlEffectManifestParametersModel* pModel = new QmlEffectManifestParametersModel(pManifest); + QmlEffectManifestParametersModel* pModel = + new QmlEffectManifestParametersModel(m_pEffectSlot); QQmlEngine::setObjectOwnership(pModel, QQmlEngine::JavaScriptOwnership); return pModel; } +void QmlEffectSlotProxy::setParameterVisible(const QString& parameterId, bool visible) { + const auto parameterMaps = { + m_pEffectSlot->getLoadedParameters(), m_pEffectSlot->getHiddenParameters()}; + for (const auto& parameterMap : parameterMaps) { + for (const auto& parameters : parameterMap) { + for (const auto& pParameter : parameters) { + if (pParameter->manifest()->id() != parameterId) { + continue; + } + const bool isVisible = + m_pEffectSlot->getLoadedParameters() + .value(pParameter->manifest()->parameterType()) + .contains(pParameter); + if (visible != isVisible) { + if (visible) { + m_pEffectSlot->showParameter(pParameter); + } else { + m_pEffectSlot->hideParameter(pParameter); + } + } + return; + } + } + } +} + +void QmlEffectSlotProxy::saveDefaultSnapshot() { + if (m_pEffectSlot->isLoaded()) { + m_pEffectsManager->getEffectPresetManager()->saveDefaultForEffect(m_pEffectSlot); + } +} + } // namespace qml } // namespace mixxx diff --git a/src/qml/qmleffectslotproxy.h b/src/qml/qmleffectslotproxy.h index 99b26ec93785..053607a0437a 100644 --- a/src/qml/qmleffectslotproxy.h +++ b/src/qml/qmleffectslotproxy.h @@ -14,6 +14,9 @@ class QmlEffectSlotProxy : public QObject { Q_PROPERTY(QString chainSlotGroup READ getChainSlotGroup CONSTANT) Q_PROPERTY(int number READ getNumber CONSTANT) Q_PROPERTY(QString group READ getGroup CONSTANT) + Q_PROPERTY(bool loaded READ isLoaded NOTIFY effectIdChanged) + Q_PROPERTY(QString effectName READ getEffectName NOTIFY effectIdChanged) + Q_PROPERTY(QString effectDescription READ getEffectDescription NOTIFY effectIdChanged) Q_PROPERTY(QString effectId READ getEffectId WRITE setEffectId NOTIFY effectIdChanged) Q_PROPERTY(mixxx::qml::QmlEffectManifestParametersModel* parametersModel READ getParametersModel NOTIFY parametersModelChanged) @@ -36,11 +39,18 @@ class QmlEffectSlotProxy : public QObject { int getNumber() const; QString getGroup() const; QString getEffectId() const; + bool isLoaded() const; + QString getEffectName() const; + QString getEffectDescription() const; QmlEffectManifestParametersModel* getParametersModel() const; public slots: void setEffectId(const QString& effectId); + public: + Q_INVOKABLE void setParameterVisible(const QString& parameterId, bool visible); + Q_INVOKABLE void saveDefaultSnapshot(); + signals: void effectIdChanged(); void parametersModelChanged(); diff --git a/src/qml/qmleffectsmanagerproxy.cpp b/src/qml/qmleffectsmanagerproxy.cpp index 927126e0f4b0..ce3cebf2330e 100644 --- a/src/qml/qmleffectsmanagerproxy.cpp +++ b/src/qml/qmleffectsmanagerproxy.cpp @@ -17,7 +17,30 @@ QmlEffectsManagerProxy::QmlEffectsManagerProxy( m_pVisibleEffectsModel( new QmlVisibleEffectsModel(pEffectsManager, this)), m_pQuickChainPresetModel( - new QmlChainPresetModel(m_pEffectsManager->getChainPresetManager(), this)) { + new QmlChainPresetModel(m_pEffectsManager, + QmlChainPresetModel::PresetType::Quick, + this)), + m_pStandardChainPresetModel( + new QmlChainPresetModel(m_pEffectsManager, + QmlChainPresetModel::PresetType::Standard, + this)) { + for (int unitIndex = 0; unitIndex < kNumStandardEffectUnits; ++unitIndex) { + const auto pEffectUnit = m_pEffectsManager->getStandardEffectChain(unitIndex); + if (pEffectUnit) { + m_effectUnitProxies.append(new QmlEffectUnitProxy( + m_pEffectsManager, unitIndex + 1, pEffectUnit, this)); + } + } +} + +QmlEffectUnitProxy* QmlEffectsManagerProxy::getEffectUnit(int unitNumber) const { + const int unitIndex = unitNumber - 1; + if (unitIndex < 0 || unitIndex >= m_effectUnitProxies.size()) { + qWarning() << "QmlEffectsManagerProxy: Effect Unit" << unitNumber + << "not found!"; + return nullptr; + } + return m_effectUnitProxies.at(unitIndex); } QmlEffectSlotProxy* QmlEffectsManagerProxy::getEffectSlot(int unitNumber, int effectNumber) const { diff --git a/src/qml/qmleffectsmanagerproxy.h b/src/qml/qmleffectsmanagerproxy.h index 410a4d4a2f0e..38a5e6df0593 100644 --- a/src/qml/qmleffectsmanagerproxy.h +++ b/src/qml/qmleffectsmanagerproxy.h @@ -5,6 +5,7 @@ #include "effects/effectsmanager.h" #include "qml/qmlchainpresetmodel.h" #include "qml/qmleffectslotproxy.h" +#include "qml/qmleffectunitproxy.h" #include "qml/qmlvisibleeffectsmodel.h" namespace mixxx { @@ -18,6 +19,8 @@ class QmlEffectsManagerProxy : public QObject { MEMBER m_pVisibleEffectsModel CONSTANT); Q_PROPERTY(mixxx::qml::QmlChainPresetModel* quickChainPresetModel MEMBER m_pQuickChainPresetModel CONSTANT); + Q_PROPERTY(mixxx::qml::QmlChainPresetModel* standardChainPresetModel + MEMBER m_pStandardChainPresetModel CONSTANT); QML_NAMED_ELEMENT(EffectsManager) QML_SINGLETON @@ -28,6 +31,7 @@ class QmlEffectsManagerProxy : public QObject { Q_INVOKABLE mixxx::qml::QmlEffectSlotProxy* getEffectSlot( int unitNumber, int effectNumber) const; + Q_INVOKABLE mixxx::qml::QmlEffectUnitProxy* getEffectUnit(int unitNumber) const; static QmlEffectsManagerProxy* create(QQmlEngine* pQmlEngine, QJSEngine* pJsEngine); static void registerEffectsManager(std::shared_ptr pEffectsManager) { @@ -40,6 +44,8 @@ class QmlEffectsManagerProxy : public QObject { const std::shared_ptr m_pEffectsManager; QmlVisibleEffectsModel* m_pVisibleEffectsModel; QmlChainPresetModel* m_pQuickChainPresetModel; + QmlChainPresetModel* m_pStandardChainPresetModel; + QList m_effectUnitProxies; }; } // namespace qml diff --git a/src/qml/qmleffectunitproxy.cpp b/src/qml/qmleffectunitproxy.cpp new file mode 100644 index 000000000000..4c881bc3921e --- /dev/null +++ b/src/qml/qmleffectunitproxy.cpp @@ -0,0 +1,93 @@ +#include "qml/qmleffectunitproxy.h" + +#include "effects/effectchain.h" +#include "effects/presets/effectchainpreset.h" +#include "moc_qmleffectunitproxy.cpp" + +namespace mixxx { +namespace qml { + +QmlEffectUnitProxy::QmlEffectUnitProxy(std::shared_ptr pEffectsManager, + int unitNumber, + EffectChainPointer pEffectUnit, + QObject* parent) + : QObject(parent), + m_pEffectsManager(std::move(pEffectsManager)), + m_pPresetManager(m_pEffectsManager->getChainPresetManager()), + m_unitNumber(unitNumber), + m_pEffectUnit(std::move(pEffectUnit)) { + connect(m_pEffectUnit.get(), + &EffectChain::chainPresetChanged, + this, + &QmlEffectUnitProxy::presetChanged); + connect(m_pPresetManager.get(), + &EffectChainPresetManager::effectChainPresetListUpdated, + this, + &QmlEffectUnitProxy::presetChanged); + connect(m_pPresetManager.get(), + &EffectChainPresetManager::effectChainPresetRenamed, + this, + &QmlEffectUnitProxy::presetChanged); +} + +int QmlEffectUnitProxy::getUnitNumber() const { + return m_unitNumber; +} + +QString QmlEffectUnitProxy::getGroup() const { + return m_pEffectUnit->getGroup(); +} + +QString QmlEffectUnitProxy::getPresetName() const { + return m_pEffectUnit->presetName(); +} + +int QmlEffectUnitProxy::getPresetIndex() const { + return m_pPresetManager->presetIndex(m_pEffectUnit->presetName()); +} + +EffectChainPresetPointer QmlEffectUnitProxy::currentPreset() const { + return m_pPresetManager->getPreset(m_pEffectUnit->presetName()); +} + +bool QmlEffectUnitProxy::isPresetReadOnly() const { + const auto pPreset = currentPreset(); + return !pPreset || pPreset->isReadOnly(); +} + +bool QmlEffectUnitProxy::canUpdatePreset() const { + const auto pPreset = currentPreset(); + return pPreset && !pPreset->isReadOnly(); +} + +bool QmlEffectUnitProxy::canRenamePreset() const { + return canUpdatePreset() && !m_pEffectUnit->presetName().isEmpty(); +} + +void QmlEffectUnitProxy::loadPreset(int index) { + if (index < 0 || index >= m_pPresetManager->numPresets()) { + return; + } + m_pEffectUnit->loadChainPreset(m_pPresetManager->presetAtIndex(index)); +} + +void QmlEffectUnitProxy::updatePreset() { + if (canUpdatePreset()) { + m_pPresetManager->updatePreset(m_pEffectUnit); + emit presetChanged(); + } +} + +void QmlEffectUnitProxy::renamePreset() { + if (canRenamePreset() && m_pPresetManager->renamePreset(m_pEffectUnit->presetName())) { + emit presetChanged(); + } +} + +void QmlEffectUnitProxy::savePresetAs() { + m_pPresetManager->savePresetAndReload(m_pEffectUnit); + emit presetChanged(); +} + +} // namespace qml +} // namespace mixxx diff --git a/src/qml/qmleffectunitproxy.h b/src/qml/qmleffectunitproxy.h new file mode 100644 index 000000000000..b85850df8340 --- /dev/null +++ b/src/qml/qmleffectunitproxy.h @@ -0,0 +1,56 @@ +#pragma once + +#include +#include +#include + +#include "effects/effectsmanager.h" + +namespace mixxx { +namespace qml { + +class QmlEffectUnitProxy : public QObject { + Q_OBJECT + Q_PROPERTY(int unitNumber READ getUnitNumber CONSTANT) + Q_PROPERTY(QString group READ getGroup CONSTANT) + Q_PROPERTY(QString presetName READ getPresetName NOTIFY presetChanged) + Q_PROPERTY(int presetIndex READ getPresetIndex NOTIFY presetChanged) + Q_PROPERTY(bool presetReadOnly READ isPresetReadOnly NOTIFY presetChanged) + Q_PROPERTY(bool canUpdatePreset READ canUpdatePreset NOTIFY presetChanged) + Q_PROPERTY(bool canRenamePreset READ canRenamePreset NOTIFY presetChanged) + QML_NAMED_ELEMENT(EffectUnitProxy) + QML_UNCREATABLE("Only accessible via Mixxx.EffectsManager.getEffectUnit(unitNumber)") + + public: + explicit QmlEffectUnitProxy(std::shared_ptr pEffectsManager, + int unitNumber, + EffectChainPointer pEffectUnit, + QObject* parent = nullptr); + + int getUnitNumber() const; + QString getGroup() const; + QString getPresetName() const; + int getPresetIndex() const; + bool isPresetReadOnly() const; + bool canUpdatePreset() const; + bool canRenamePreset() const; + + Q_INVOKABLE void loadPreset(int index); + Q_INVOKABLE void updatePreset(); + Q_INVOKABLE void renamePreset(); + Q_INVOKABLE void savePresetAs(); + + signals: + void presetChanged(); + + private: + EffectChainPresetPointer currentPreset() const; + + const std::shared_ptr m_pEffectsManager; + const EffectChainPresetManagerPointer m_pPresetManager; + const int m_unitNumber; + const EffectChainPointer m_pEffectUnit; +}; + +} // namespace qml +} // namespace mixxx diff --git a/src/qml/qmlskincontrolcreator.cpp b/src/qml/qmlskincontrolcreator.cpp index 69aae488c35c..d93c9fa355a0 100644 --- a/src/qml/qmlskincontrolcreator.cpp +++ b/src/qml/qmlskincontrolcreator.cpp @@ -18,6 +18,7 @@ QmlSkinControlCreator::QmlSkinControlCreator(QObject* parent) m_persist(false), m_persistConfigured(false), m_defaultValue(0.0), + m_defaultValueConfigured(false), m_buttonMode(ButtonMode::Toggle), m_isComponentComplete(false) { } @@ -73,11 +74,12 @@ bool QmlSkinControlCreator::getPersist() const { } void QmlSkinControlCreator::setDefaultValue(double defaultValue) { - if (m_defaultValue == defaultValue) { - return; - } + const bool changed = m_defaultValue != defaultValue; m_defaultValue = defaultValue; - emit defaultValueChanged(defaultValue); + m_defaultValueConfigured = true; + if (changed) { + emit defaultValueChanged(defaultValue); + } createDefaultControlBeforeComponentComplete(); createControl(); } @@ -147,8 +149,9 @@ void QmlSkinControlCreator::createControl(bool allowBeforeComponentComplete) { } void QmlSkinControlCreator::createDefaultControlBeforeComponentComplete() { - if (m_isComponentComplete || m_pControl || m_defaultValue == 0.0 || - !m_persistConfigured || !m_key.isValid() || m_key.group != kSkinGroup || + if (m_isComponentComplete || m_pControl || !m_persistConfigured || + !m_defaultValueConfigured || + !m_key.isValid() || m_key.group != kSkinGroup || ControlObject::exists(m_key)) { return; } diff --git a/src/qml/qmlskincontrolcreator.h b/src/qml/qmlskincontrolcreator.h index 8a9edc3e0504..3ba37f4f7362 100644 --- a/src/qml/qmlskincontrolcreator.h +++ b/src/qml/qmlskincontrolcreator.h @@ -70,6 +70,7 @@ class QmlSkinControlCreator : public QObject, public QQmlParserStatus { bool m_persist; bool m_persistConfigured; double m_defaultValue; + bool m_defaultValueConfigured; ButtonMode m_buttonMode; bool m_isComponentComplete; std::unique_ptr m_pControl; diff --git a/src/test/qmleffectsproxytest.cpp b/src/test/qmleffectsproxytest.cpp new file mode 100644 index 000000000000..63f01a945ded --- /dev/null +++ b/src/test/qmleffectsproxytest.cpp @@ -0,0 +1,131 @@ +#include + +#include +#include + +#include "effects/backends/builtin/echoeffect.h" +#include "effects/effectchain.h" +#include "effects/effectsmanager.h" +#include "effects/presets/effectchainpreset.h" +#include "effects/presets/effectchainpresetmanager.h" +#include "engine/channelhandle.h" +#include "qml/qmlchainpresetmodel.h" +#include "qml/qmleffectmanifestparametersmodel.h" +#include "qml/qmleffectslotproxy.h" +#include "qml/qmleffectsmanagerproxy.h" +#include "qml/qmleffectunitproxy.h" +#include "test/mixxxtest.h" + +namespace { + +class QmlEffectsProxyTest : public MixxxTest { + protected: + void SetUp() override { + auto pChannelHandleFactory = std::make_shared(); + m_pEffectsManager = + std::make_shared(config(), pChannelHandleFactory); + const QString mainOutputGroup = QStringLiteral("[MasterOutput]"); + m_pEffectsManager->registerInputChannel(ChannelHandleAndGroup( + pChannelHandleFactory->getOrCreateHandle(mainOutputGroup), mainOutputGroup)); + m_pEffectsManager->setup(); + m_pProxy = std::make_unique( + m_pEffectsManager); + } + + void TearDown() override { + m_pProxy.reset(); + for (int unitIndex = 0; unitIndex < kNumStandardEffectUnits; ++unitIndex) { + const auto pChain = m_pEffectsManager->getStandardEffectChain(unitIndex); + auto pEmptyPreset = EffectChainPresetPointer::create(); + pEmptyPreset->setName(QString()); + pChain->loadChainPreset(pEmptyPreset); + EXPECT_TRUE(pChain->isEmpty()); + EXPECT_TRUE(pChain->presetName().isEmpty()); + } + m_pEffectsManager.reset(); + } + + std::shared_ptr m_pEffectsManager; + std::unique_ptr m_pProxy; +}; + +TEST_F(QmlEffectsProxyTest, UnitPresetModelsAndParameterVisibility) { + auto* pUnit1 = m_pProxy->getEffectUnit(1); + ASSERT_NE(nullptr, pUnit1); + EXPECT_EQ(pUnit1, m_pProxy->getEffectUnit(1)); + EXPECT_EQ(1, pUnit1->getUnitNumber()); + EXPECT_EQ(QStringLiteral("[EffectRack1_EffectUnit1]"), pUnit1->getGroup()); + EXPECT_EQ(nullptr, m_pProxy->getEffectUnit(0)); + EXPECT_EQ(nullptr, m_pProxy->getEffectUnit(5)); + + auto* pStandardModel = m_pProxy->property("standardChainPresetModel") + .value(); + auto* pQuickModel = m_pProxy->property("quickChainPresetModel") + .value(); + ASSERT_NE(nullptr, pStandardModel); + ASSERT_NE(nullptr, pQuickModel); + EXPECT_GT(pStandardModel->rowCount({}), 0); + EXPECT_GT(pQuickModel->rowCount({}), 0); + EXPECT_TRUE(pStandardModel->roleNames().values().contains("name")); + EXPECT_TRUE(pStandardModel->roleNames().values().contains("readOnly")); + + QSignalSpy standardResetSpy(pStandardModel, &QAbstractItemModel::modelReset); + QSignalSpy quickResetSpy(pQuickModel, &QAbstractItemModel::modelReset); + QMetaObject::invokeMethod(m_pEffectsManager->getChainPresetManager().get(), + "effectChainPresetListUpdated"); + EXPECT_EQ(1, standardResetSpy.count()); + EXPECT_EQ(0, quickResetSpy.count()); + QMetaObject::invokeMethod(m_pEffectsManager->getChainPresetManager().get(), + "quickEffectChainPresetListUpdated"); + EXPECT_EQ(1, standardResetSpy.count()); + EXPECT_EQ(1, quickResetSpy.count()); + + QSignalSpy presetSpy(pUnit1, &mixxx::qml::QmlEffectUnitProxy::presetChanged); + pUnit1->loadPreset(-1); + EXPECT_EQ(0, presetSpy.count()); + pUnit1->loadPreset(0); + EXPECT_EQ(0, pUnit1->getPresetIndex()); + EXPECT_FALSE(pUnit1->getPresetName().isEmpty()); + EXPECT_EQ(!pUnit1->isPresetReadOnly(), pUnit1->canUpdatePreset()); + EXPECT_EQ(pUnit1->canUpdatePreset() && !pUnit1->getPresetName().isEmpty(), + pUnit1->canRenamePreset()); + EXPECT_GE(presetSpy.count(), 1); + + auto* pSlot = m_pProxy->getEffectSlot(1, 1); + ASSERT_NE(nullptr, pSlot); + QSignalSpy effectSpy(pSlot, &mixxx::qml::QmlEffectSlotProxy::effectIdChanged); + QSignalSpy parametersSpy( + pSlot, &mixxx::qml::QmlEffectSlotProxy::parametersModelChanged); + pSlot->setEffectId(EchoEffect::getId()); + ASSERT_TRUE(pSlot->isLoaded()); + EXPECT_GE(effectSpy.count(), 1); + EXPECT_GE(parametersSpy.count(), 1); + + std::unique_ptr pModel( + pSlot->getParametersModel()); + ASSERT_NE(nullptr, pModel); + ASSERT_EQ(6, pModel->rowCount({})); + EXPECT_EQ(QStringLiteral("parameter1"), + pModel->get(0).toMap().value("controlKey").toString()); + EXPECT_EQ(QStringLiteral("parameter2"), + pModel->get(1).toMap().value("controlKey").toString()); + EXPECT_EQ(QStringLiteral("button_parameter1"), + pModel->get(4).toMap().value("controlKey").toString()); + + pSlot->setParameterVisible(QStringLiteral("feedback_amount"), false); + EXPECT_GE(parametersSpy.count(), 2); + pModel.reset(pSlot->getParametersModel()); + EXPECT_FALSE(pModel->get(1).toMap().value("loaded").toBool()); + EXPECT_TRUE(pModel->get(1).toMap().value("controlKey").toString().isEmpty()); + EXPECT_EQ(QStringLiteral("parameter2"), + pModel->get(2).toMap().value("controlKey").toString()); + + pSlot->setParameterVisible(QStringLiteral("feedback_amount"), true); + pModel.reset(pSlot->getParametersModel()); + EXPECT_TRUE(pModel->get(1).toMap().value("loaded").toBool()); + pSlot->saveDefaultSnapshot(); + pModel.reset(); + delete pSlot; +} + +} // namespace diff --git a/src/test/qmlskincontrolcreatortest.cpp b/src/test/qmlskincontrolcreatortest.cpp index dad042d7507a..43cbef8971e3 100644 --- a/src/test/qmlskincontrolcreatortest.cpp +++ b/src/test/qmlskincontrolcreatortest.cpp @@ -82,6 +82,25 @@ TEST_F(QmlSkinControlCreatorTest, AppliesDefaultBeforeComponentCompleteInAnyOrde EXPECT_DOUBLE_EQ(1.0, ControlObject::get(key)); } +TEST_F(QmlSkinControlCreatorTest, AppliesZeroDefaultBeforeComponentComplete) { + const ConfigKey key(QStringLiteral("[Skin]"), + QStringLiteral("qml_skin_control_creator_precomplete_zero_default_test")); + + auto creator = std::make_unique(); + creator->setGroup(key.group); + creator->setKey(key.item); + creator->setPersist(true); + creator->setDefaultValue(0.0); + + EXPECT_TRUE(ControlObject::exists(key)); + EXPECT_DOUBLE_EQ(0.0, ControlObject::get(key)); + + creator->componentComplete(); + + EXPECT_TRUE(ControlObject::exists(key)); + EXPECT_DOUBLE_EQ(0.0, ControlObject::get(key)); +} + TEST_F(QmlSkinControlCreatorTest, RejectsExistingSkinControl) { const ConfigKey key(QStringLiteral("[Skin]"), QStringLiteral("qml_skin_control_creator_duplicate_test"));