Merge pull request #10926 from cbjeukendrup/onscreen_piano
Implemented On-screen Piano Keyboard
This commit is contained in:
commit
064eb06ab7
26 changed files with 1400 additions and 58 deletions
|
@ -32,11 +32,14 @@ using DockName = QString;
|
|||
static const DockName PALETTES_PANEL_NAME("palettesPanel");
|
||||
static const DockName INSTRUMENTS_PANEL_NAME("instrumentsPanel");
|
||||
static const DockName INSPECTOR_PANEL_NAME("inspectorPanel");
|
||||
static const DockName NOTATION_NAVIGATOR_PANEL_NAME("notationNavigatorPanel");
|
||||
static const DockName TIMELINE_PANEL_NAME("timelinePanel");
|
||||
static const DockName MIXER_PANEL_NAME("mixerPanel");
|
||||
static const DockName SELECTION_FILTERS_PANEL_NAME("selectionFiltersPanel");
|
||||
static const DockName PIANO_PANEL_NAME("pianoPanel");
|
||||
|
||||
static const DockName NOTATION_NAVIGATOR_PANEL_NAME("notationNavigatorPanel");
|
||||
|
||||
static const DockName MIXER_PANEL_NAME("mixerPanel");
|
||||
static const DockName PIANO_ROLL_PANEL_NAME("pianoRollPanel");
|
||||
static const DockName PIANO_KEYBOARD_PANEL_NAME("pianoKeyboardPanel");
|
||||
static const DockName TIMELINE_PANEL_NAME("timelinePanel");
|
||||
static const DockName DRUMSET_PANEL_NAME("drumsetPanel");
|
||||
|
||||
// Toolbars:
|
||||
|
|
|
@ -81,24 +81,29 @@ const UiActionList ApplicationUiActions::m_actions = {
|
|||
QT_TRANSLATE_NOOP("action", "Revert to factory settings"),
|
||||
QT_TRANSLATE_NOOP("action", "Revert to factory settings")
|
||||
),
|
||||
|
||||
// Docking
|
||||
UiAction("dock-restore-default-layout",
|
||||
mu::context::UiCtxAny,
|
||||
QT_TRANSLATE_NOOP("action", "Restore the default layout"),
|
||||
QT_TRANSLATE_NOOP("action", "Restore the default layout")
|
||||
),
|
||||
UiAction("toggle-mixer",
|
||||
|
||||
// Toolbars
|
||||
UiAction("toggle-transport",
|
||||
mu::context::UiCtxNotationOpened,
|
||||
QT_TRANSLATE_NOOP("action", "Mixer"),
|
||||
QT_TRANSLATE_NOOP("action", "Toggle mixer"),
|
||||
IconCode::Code::MIXER,
|
||||
QT_TRANSLATE_NOOP("action", "Playback Controls"),
|
||||
QT_TRANSLATE_NOOP("action", "Toggle Playback Controls toolbar"),
|
||||
Checkable::Yes
|
||||
),
|
||||
UiAction("toggle-navigator",
|
||||
UiAction("toggle-noteinput",
|
||||
mu::context::UiCtxNotationOpened,
|
||||
QT_TRANSLATE_NOOP("action", "Navigator"),
|
||||
QT_TRANSLATE_NOOP("action", "Toggle 'Navigator'"),
|
||||
QT_TRANSLATE_NOOP("action", "Note Input"),
|
||||
QT_TRANSLATE_NOOP("action", "Toggle 'Note Input' toolbar"),
|
||||
Checkable::Yes
|
||||
),
|
||||
|
||||
// Vertical panels
|
||||
UiAction("toggle-palettes",
|
||||
mu::context::UiCtxNotationOpened,
|
||||
QT_TRANSLATE_NOOP("action", "Palettes"),
|
||||
|
@ -123,40 +128,39 @@ const UiActionList ApplicationUiActions::m_actions = {
|
|||
QT_TRANSLATE_NOOP("action", "Toggle 'Selection filter'"),
|
||||
Checkable::Yes
|
||||
),
|
||||
UiAction("toggle-statusbar",
|
||||
|
||||
// Navigator
|
||||
UiAction("toggle-navigator",
|
||||
mu::context::UiCtxNotationOpened,
|
||||
QT_TRANSLATE_NOOP("action", "Status bar"),
|
||||
QT_TRANSLATE_NOOP("action", "Toggle 'Status bar'"),
|
||||
Checkable::Yes
|
||||
),
|
||||
UiAction("toggle-noteinput",
|
||||
mu::context::UiCtxNotationOpened,
|
||||
QT_TRANSLATE_NOOP("action", "Note Input"),
|
||||
QT_TRANSLATE_NOOP("action", "Toggle 'Note Input' toolbar"),
|
||||
Checkable::Yes
|
||||
),
|
||||
UiAction("toggle-transport",
|
||||
mu::context::UiCtxNotationOpened,
|
||||
QT_TRANSLATE_NOOP("action", "Playback Controls"),
|
||||
QT_TRANSLATE_NOOP("action", "Toggle Playback Controls toolbar"),
|
||||
QT_TRANSLATE_NOOP("action", "Navigator"),
|
||||
QT_TRANSLATE_NOOP("action", "Toggle 'Navigator'"),
|
||||
Checkable::Yes
|
||||
),
|
||||
|
||||
// Horizontal panels
|
||||
UiAction("toggle-timeline",
|
||||
mu::context::UiCtxNotationOpened,
|
||||
QT_TRANSLATE_NOOP("action", "Timeline"),
|
||||
QT_TRANSLATE_NOOP("action", "Toggle timeline"),
|
||||
Checkable::Yes
|
||||
),
|
||||
UiAction("synth-control",
|
||||
UiAction("toggle-mixer",
|
||||
mu::context::UiCtxNotationOpened,
|
||||
QT_TRANSLATE_NOOP("action", "Synthesizer"),
|
||||
QT_TRANSLATE_NOOP("action", "Toggle synthesizer"),
|
||||
QT_TRANSLATE_NOOP("action", "Mixer"),
|
||||
QT_TRANSLATE_NOOP("action", "Toggle mixer"),
|
||||
IconCode::Code::MIXER,
|
||||
Checkable::Yes
|
||||
),
|
||||
UiAction("toggle-piano",
|
||||
UiAction("toggle-piano-roll",
|
||||
mu::context::UiCtxNotationOpened,
|
||||
QT_TRANSLATE_NOOP("action", "Piano"),
|
||||
QT_TRANSLATE_NOOP("action", "Toggle piano"),
|
||||
QT_TRANSLATE_NOOP("action", "Piano roll"),
|
||||
QT_TRANSLATE_NOOP("action", "Toggle piano roll"),
|
||||
Checkable::Yes
|
||||
),
|
||||
UiAction("toggle-piano-keyboard",
|
||||
mu::context::UiCtxNotationOpened,
|
||||
QT_TRANSLATE_NOOP("action", "Piano keyboard"),
|
||||
QT_TRANSLATE_NOOP("action", "Toggle piano keyboard"),
|
||||
Checkable::Yes
|
||||
),
|
||||
UiAction("toggle-scorecmp-tool",
|
||||
|
@ -165,6 +169,15 @@ const UiActionList ApplicationUiActions::m_actions = {
|
|||
QT_TRANSLATE_NOOP("action", "Toggle score comparison tool"),
|
||||
Checkable::Yes
|
||||
),
|
||||
|
||||
// Status bar
|
||||
UiAction("toggle-statusbar",
|
||||
mu::context::UiCtxNotationOpened,
|
||||
QT_TRANSLATE_NOOP("action", "Status bar"),
|
||||
QT_TRANSLATE_NOOP("action", "Toggle 'Status bar'"),
|
||||
Checkable::Yes
|
||||
),
|
||||
|
||||
UiAction("preference-dialog",
|
||||
mu::context::UiCtxAny,
|
||||
QT_TRANSLATE_NOOP("action", "&Preferences"),
|
||||
|
@ -260,16 +273,22 @@ mu::async::Channel<mu::actions::ActionCodeList> ApplicationUiActions::actionChec
|
|||
const QMap<mu::actions::ActionCode, DockName>& ApplicationUiActions::toggleDockActions()
|
||||
{
|
||||
static const QMap<mu::actions::ActionCode, DockName> actionsMap {
|
||||
{ TOGGLE_NAVIGATOR_ACTION_CODE, NOTATION_NAVIGATOR_PANEL_NAME },
|
||||
{ "toggle-mixer", MIXER_PANEL_NAME },
|
||||
{ "toggle-timeline", TIMELINE_PANEL_NAME },
|
||||
{ "toggle-transport", PLAYBACK_TOOLBAR_NAME },
|
||||
{ "toggle-noteinput", NOTE_INPUT_BAR_NAME },
|
||||
|
||||
{ "toggle-palettes", PALETTES_PANEL_NAME },
|
||||
{ "toggle-instruments", INSTRUMENTS_PANEL_NAME },
|
||||
{ "inspector", INSPECTOR_PANEL_NAME },
|
||||
{ "toggle-selection-filter", SELECTION_FILTERS_PANEL_NAME },
|
||||
|
||||
{ TOGGLE_NAVIGATOR_ACTION_CODE, NOTATION_NAVIGATOR_PANEL_NAME },
|
||||
|
||||
{ "toggle-timeline", TIMELINE_PANEL_NAME },
|
||||
{ "toggle-mixer", MIXER_PANEL_NAME },
|
||||
{ "toggle-piano-roll", PIANO_ROLL_PANEL_NAME },
|
||||
{ "toggle-piano-keyboard", PIANO_KEYBOARD_PANEL_NAME },
|
||||
|
||||
{ "toggle-statusbar", NOTATION_STATUSBAR_NAME },
|
||||
{ "toggle-noteinput", NOTE_INPUT_BAR_NAME },
|
||||
{ "toggle-transport", PLAYBACK_TOOLBAR_NAME }
|
||||
};
|
||||
|
||||
return actionsMap;
|
||||
|
|
|
@ -335,8 +335,33 @@ DockPage {
|
|||
DockPanel {
|
||||
id: pianoRollPanel
|
||||
|
||||
objectName: pageModel.pianoPanelName()
|
||||
title: qsTrc("appshell", "Piano Roll")
|
||||
objectName: pageModel.pianoRollPanelName()
|
||||
title: qsTrc("appshell", "Piano roll")
|
||||
|
||||
height: 200
|
||||
minimumHeight: root.horizontalPanelMinHeight
|
||||
maximumHeight: root.horizontalPanelMaxHeight
|
||||
|
||||
tabifyPanel: pianoKeyboardPanel
|
||||
|
||||
//! NOTE: hidden by default
|
||||
visible: false
|
||||
|
||||
location: Location.Bottom
|
||||
|
||||
dropDestinations: root.horizontalPanelDropDestinations
|
||||
|
||||
StyledTextLabel {
|
||||
anchors.centerIn: parent
|
||||
text: pianoRollPanel.title
|
||||
}
|
||||
},
|
||||
|
||||
DockPanel {
|
||||
id: pianoKeyboardPanel
|
||||
|
||||
objectName: pageModel.pianoKeyboardPanelName()
|
||||
title: qsTrc("appshell", "Piano keyboard")
|
||||
|
||||
height: 200
|
||||
minimumHeight: root.horizontalPanelMinHeight
|
||||
|
@ -351,9 +376,10 @@ DockPage {
|
|||
|
||||
dropDestinations: root.horizontalPanelDropDestinations
|
||||
|
||||
StyledTextLabel {
|
||||
anchors.centerIn: parent
|
||||
text: pianoRollPanel.title
|
||||
PianoKeyboardPanel {
|
||||
Component.onCompleted: {
|
||||
pianoKeyboardPanel.contextMenuModel = contextMenuModel
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -194,8 +194,9 @@ MenuItem* AppMenuModel::makeViewMenu()
|
|||
makeMenuItem("toggle-navigator"),
|
||||
makeMenuItem("toggle-timeline"),
|
||||
makeMenuItem("toggle-mixer"),
|
||||
// TODO: https://github.com/musescore/MuseScore/issues/9168
|
||||
// makeMenuItem("toggle-piano"), // need implement
|
||||
makeMenuItem("toggle-piano-roll"),
|
||||
makeMenuItem("toggle-piano-keyboard"),
|
||||
//makeMenuItem("toggle-scorecmp-tool"), // not implemented
|
||||
makeSeparator(),
|
||||
makeMenu(qtrc("appshell", "&Toolbars"), makeToolbarsItems(), "menu-toolbars"),
|
||||
makeMenu(qtrc("appshell", "W&orkspaces"), makeWorkspacesItems(), "menu-workspaces"),
|
||||
|
|
|
@ -97,16 +97,21 @@ QString NotationPageModel::selectionFiltersPanelName() const
|
|||
return SELECTION_FILTERS_PANEL_NAME;
|
||||
}
|
||||
|
||||
QString NotationPageModel::pianoPanelName() const
|
||||
{
|
||||
return PIANO_PANEL_NAME;
|
||||
}
|
||||
|
||||
QString NotationPageModel::mixerPanelName() const
|
||||
{
|
||||
return MIXER_PANEL_NAME;
|
||||
}
|
||||
|
||||
QString NotationPageModel::pianoRollPanelName() const
|
||||
{
|
||||
return PIANO_ROLL_PANEL_NAME;
|
||||
}
|
||||
|
||||
QString NotationPageModel::pianoKeyboardPanelName() const
|
||||
{
|
||||
return PIANO_KEYBOARD_PANEL_NAME;
|
||||
}
|
||||
|
||||
QString NotationPageModel::timelinePanelName() const
|
||||
{
|
||||
return TIMELINE_PANEL_NAME;
|
||||
|
|
|
@ -61,8 +61,9 @@ public:
|
|||
Q_INVOKABLE QString inspectorPanelName() const;
|
||||
Q_INVOKABLE QString selectionFiltersPanelName() const;
|
||||
|
||||
Q_INVOKABLE QString pianoPanelName() const;
|
||||
Q_INVOKABLE QString mixerPanelName() const;
|
||||
Q_INVOKABLE QString pianoRollPanelName() const;
|
||||
Q_INVOKABLE QString pianoKeyboardPanelName() const;
|
||||
Q_INVOKABLE QString timelinePanelName() const;
|
||||
Q_INVOKABLE QString drumsetPanelName() const;
|
||||
|
||||
|
|
|
@ -109,7 +109,7 @@ inline auto take(Map& m, const K& k) -> typename Map::mapped_type
|
|||
m.erase(it);
|
||||
return v;
|
||||
}
|
||||
typename Map::mapped_type def;
|
||||
typename Map::mapped_type def {};
|
||||
return def;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,7 +66,7 @@ public:
|
|||
|
||||
std::string fontFamily() const override;
|
||||
void setFontFamily(const std::string& family) override;
|
||||
int fontSize(FontSizeType type) const override;
|
||||
int fontSize(FontSizeType type = FontSizeType::BODY) const override;
|
||||
void setBodyFontSize(int size) override;
|
||||
async::Notification fontChanged() const override;
|
||||
|
||||
|
|
|
@ -65,7 +65,7 @@ public:
|
|||
|
||||
virtual std::string fontFamily() const = 0;
|
||||
virtual void setFontFamily(const std::string& family) = 0;
|
||||
virtual int fontSize(FontSizeType type) const = 0;
|
||||
virtual int fontSize(FontSizeType type = FontSizeType::BODY) const = 0;
|
||||
virtual void setBodyFontSize(int size) = 0;
|
||||
virtual async::Notification fontChanged() const = 0;
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ ScrollBar {
|
|||
property real thickness: 8
|
||||
property real minimumSizeInPixels: 30
|
||||
|
||||
readonly property bool isVertical: orientation === Qt.Vertical && root.policy !== ScrollBar.AlwaysOff
|
||||
readonly property bool isVertical: orientation === Qt.Vertical
|
||||
readonly property bool isScrollbarNeeded: size > 0.0 && size < 1.0
|
||||
|
||||
visible: isScrollbarNeeded
|
||||
|
@ -83,8 +83,7 @@ ScrollBar {
|
|||
}
|
||||
}
|
||||
|
||||
function setPosition(position) {
|
||||
root.position = position
|
||||
function activate() {
|
||||
root.active = true
|
||||
Qt.callLater(function() {
|
||||
root.active = Qt.binding( function() { return root.hovered || root.pressed } )
|
||||
|
|
|
@ -139,6 +139,14 @@ set(MODULE_SRC
|
|||
${CMAKE_CURRENT_LIST_DIR}/view/selectionfiltermodel.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/view/editgridsizedialogmodel.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/view/editgridsizedialogmodel.h
|
||||
|
||||
${CMAKE_CURRENT_LIST_DIR}/view/pianokeyboard/pianokeyboardtypes.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/view/pianokeyboard/pianokeyboardcontroller.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/view/pianokeyboard/pianokeyboardcontroller.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/view/pianokeyboard/pianokeyboardview.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/view/pianokeyboard/pianokeyboardview.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/view/pianokeyboard/pianokeyboardpanelcontextmenumodel.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/view/pianokeyboard/pianokeyboardpanelcontextmenumodel.h
|
||||
|
||||
${WIDGETS_SRC}
|
||||
${STYLEDIALOG_SRC}
|
||||
|
|
|
@ -170,6 +170,9 @@ public:
|
|||
|
||||
virtual bool needToShowAddBoxesErrorMessage() const = 0;
|
||||
virtual void setNeedToShowAddBoxesErrorMessage(bool show) = 0;
|
||||
|
||||
virtual ValCh<int> pianoKeyboardNumberOfKeys() const = 0;
|
||||
virtual void setPianoKeyboardNumberOfKeys(int number) = 0;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -92,6 +92,8 @@ static const Settings::Key NEED_TO_SHOW_ADD_TEXT_ERROR_MESSAGE_KEY(module_name,
|
|||
static const Settings::Key NEED_TO_SHOW_ADD_FIGURED_BASS_ERROR_MESSAGE_KEY(module_name, "ui/dialogs/needToShowAddFiguredBassErrorMessage");
|
||||
static const Settings::Key NEED_TO_SHOW_ADD_BOXES_ERROR_MESSAGE_KEY(module_name, "ui/dialogs/needToShowAddBoxesErrorMessage");
|
||||
|
||||
static const Settings::Key PIANO_KEYBOARD_NUMBER_OF_KEYS(module_name, "pianoKeyboard/numberOfKeys");
|
||||
|
||||
static constexpr int DEFAULT_GRID_SIZE_SPATIUM = 2;
|
||||
|
||||
void NotationConfiguration::init()
|
||||
|
@ -207,6 +209,12 @@ void NotationConfiguration::init()
|
|||
settings()->setDefaultValue(NEED_TO_SHOW_ADD_FIGURED_BASS_ERROR_MESSAGE_KEY, Val(true));
|
||||
settings()->setDefaultValue(NEED_TO_SHOW_ADD_BOXES_ERROR_MESSAGE_KEY, Val(true));
|
||||
|
||||
settings()->setDefaultValue(PIANO_KEYBOARD_NUMBER_OF_KEYS, Val(88));
|
||||
m_pianoKeyboardNumberOfKeys.val = settings()->value(PIANO_KEYBOARD_NUMBER_OF_KEYS).toInt();
|
||||
settings()->valueChanged(PIANO_KEYBOARD_NUMBER_OF_KEYS).onReceive(this, [this](const Val& val) {
|
||||
m_pianoKeyboardNumberOfKeys.set(val.toInt());
|
||||
});
|
||||
|
||||
engravingConfiguration()->scoreInversionChanged().onNotify(this, [this]() {
|
||||
m_foregroundChanged.notify();
|
||||
});
|
||||
|
@ -811,6 +819,16 @@ void NotationConfiguration::setNeedToShowAddBoxesErrorMessage(bool show)
|
|||
settings()->setSharedValue(NEED_TO_SHOW_ADD_BOXES_ERROR_MESSAGE_KEY, Val(show));
|
||||
}
|
||||
|
||||
ValCh<int> NotationConfiguration::pianoKeyboardNumberOfKeys() const
|
||||
{
|
||||
return m_pianoKeyboardNumberOfKeys;
|
||||
}
|
||||
|
||||
void NotationConfiguration::setPianoKeyboardNumberOfKeys(int number)
|
||||
{
|
||||
settings()->setSharedValue(PIANO_KEYBOARD_NUMBER_OF_KEYS, Val(number));
|
||||
}
|
||||
|
||||
io::path NotationConfiguration::firstScoreOrderListPath() const
|
||||
{
|
||||
return settings()->value(FIRST_SCORE_ORDER_LIST_KEY).toString();
|
||||
|
|
|
@ -176,6 +176,9 @@ public:
|
|||
bool needToShowAddBoxesErrorMessage() const override;
|
||||
void setNeedToShowAddBoxesErrorMessage(bool show) override;
|
||||
|
||||
ValCh<int> pianoKeyboardNumberOfKeys() const override;
|
||||
void setPianoKeyboardNumberOfKeys(int number) override;
|
||||
|
||||
private:
|
||||
io::path firstInstrumentListPath() const;
|
||||
void setFirstInstrumentListPath(const io::path& path);
|
||||
|
@ -198,6 +201,7 @@ private:
|
|||
async::Notification m_scoreOrderListPathsChanged;
|
||||
async::Notification m_isLimitCanvasScrollAreaChanged;
|
||||
async::Notification m_isPlayRepeatsChanged;
|
||||
ValCh<int> m_pianoKeyboardNumberOfKeys;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -50,6 +50,9 @@
|
|||
#include "view/selectionfiltermodel.h"
|
||||
#include "view/editgridsizedialogmodel.h"
|
||||
|
||||
#include "view/pianokeyboard/pianokeyboardview.h"
|
||||
#include "view/pianokeyboard/pianokeyboardpanelcontextmenumodel.h"
|
||||
|
||||
#include "ui/iinteractiveuriregister.h"
|
||||
#include "ui/uitypes.h"
|
||||
#include "view/widgets/editstyle.h"
|
||||
|
@ -186,6 +189,8 @@ void NotationModule::registerUiTypes()
|
|||
qmlRegisterType<TimelineView>("MuseScore.NotationScene", 1, 0, "TimelineView");
|
||||
qmlRegisterType<SelectionFilterModel>("MuseScore.NotationScene", 1, 0, "SelectionFilterModel");
|
||||
qmlRegisterType<EditGridSizeDialogModel>("MuseScore.NotationScene", 1, 0, "EditGridSizeDialogModel");
|
||||
qmlRegisterType<PianoKeyboardView>("MuseScore.NotationScene", 1, 0, "PianoKeyboardView");
|
||||
qmlRegisterType<PianoKeyboardPanelContextMenuModel>("MuseScore.NotationScene", 1, 0, "PianoKeyboardPanelContextMenuModel");
|
||||
|
||||
qmlRegisterUncreatableType<StyleItem>("MuseScore.NotationScene", 1, 0, "StyleItem", "Cannot create StyleItem from QML");
|
||||
qmlRegisterType<NotesPageModel>("MuseScore.NotationScene", 1, 0, "NotesPageModel");
|
||||
|
|
|
@ -33,5 +33,6 @@
|
|||
<file>qml/MuseScore/NotationScene/internal/CustomiseView.qml</file>
|
||||
<file>qml/MuseScore/NotationScene/internal/NoteInputBarCustomisePopup.qml</file>
|
||||
<file>qml/MuseScore/NotationScene/internal/NotationScrollBar.qml</file>
|
||||
<file>qml/MuseScore/NotationScene/PianoKeyboardPanel.qml</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
* MuseScore-CLA-applies
|
||||
*
|
||||
* MuseScore
|
||||
* Music Composition & Notation
|
||||
*
|
||||
* Copyright (C) 2022 MuseScore BVBA and others
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 3 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import QtQuick 2.15
|
||||
|
||||
import MuseScore.NotationScene 1.0
|
||||
import MuseScore.UiComponents 1.0
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property alias contextMenuModel: contextMenuModel
|
||||
|
||||
PianoKeyboardPanelContextMenuModel {
|
||||
id: contextMenuModel
|
||||
|
||||
keyWidthScaling: keyboardView.keyWidthScaling
|
||||
|
||||
onSetKeyWidthScalingRequested: function(scaling) {
|
||||
keyboardView.keyWidthScaling = scaling
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
contextMenuModel.load()
|
||||
}
|
||||
|
||||
PinchArea {
|
||||
anchors.fill: parent
|
||||
|
||||
onPinchUpdated: function(pinch) {
|
||||
keyboardView.scale(pinch.scale / pinch.previousScale, pinch.center.x)
|
||||
}
|
||||
|
||||
PianoKeyboardView {
|
||||
id: keyboardView
|
||||
anchors.fill: parent
|
||||
|
||||
numberOfKeys: contextMenuModel.numberOfKeys
|
||||
|
||||
//! NOTE: bidirectional binding; C++ code should guard against loops
|
||||
scrollBarPosition: scrollBar.position
|
||||
|
||||
StyledScrollBar {
|
||||
id: scrollBar
|
||||
|
||||
orientation: Qt.Horizontal
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
color: "black"
|
||||
border.color: "white"
|
||||
border.width: 1
|
||||
|
||||
position: keyboardView.scrollBarPosition
|
||||
size: keyboardView.scrollBarSize
|
||||
|
||||
onPositionChanged: {
|
||||
activate()
|
||||
}
|
||||
|
||||
onSizeChanged: {
|
||||
activate()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,3 +11,4 @@ UndoRedoToolBar 1.0 UndoRedoToolBar.qml
|
|||
Timeline 1.0 Timeline.qml
|
||||
SelectionFilterPanel 1.0 SelectionFilterPanel.qml
|
||||
NotationScrollAndZoomArea 1.0 NotationScrollAndZoomArea.qml
|
||||
PianoKeyboardPanel 1.0 PianoKeyboardPanel.qml
|
||||
|
|
161
src/notation/view/pianokeyboard/pianokeyboardcontroller.cpp
Normal file
161
src/notation/view/pianokeyboard/pianokeyboardcontroller.cpp
Normal file
|
@ -0,0 +1,161 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
* MuseScore-CLA-applies
|
||||
*
|
||||
* MuseScore
|
||||
* Music Composition & Notation
|
||||
*
|
||||
* Copyright (C) 2022 MuseScore BVBA and others
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 3 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "pianokeyboardcontroller.h"
|
||||
|
||||
#include "defer.h"
|
||||
#include "log.h"
|
||||
|
||||
using namespace mu::notation;
|
||||
using namespace mu::midi;
|
||||
|
||||
void PianoKeyboardController::init()
|
||||
{
|
||||
onNotationChanged();
|
||||
|
||||
context()->currentNotationChanged().onNotify(this, [this]() {
|
||||
onNotationChanged();
|
||||
});
|
||||
}
|
||||
|
||||
KeyState PianoKeyboardController::keyState(piano_key_t key) const
|
||||
{
|
||||
if (m_pressedKey == key) {
|
||||
return KeyState::Played;
|
||||
}
|
||||
|
||||
if (m_keysInSelection.find(key) != m_keysInSelection.cend()) {
|
||||
return KeyState::Selected;
|
||||
}
|
||||
|
||||
if (m_otherNotesInSelectedChord.find(key) != m_otherNotesInSelectedChord.cend()) {
|
||||
return KeyState::OtherInSelectedChord;
|
||||
}
|
||||
|
||||
return KeyState::None;
|
||||
}
|
||||
|
||||
mu::async::Notification PianoKeyboardController::keyStatesChanged() const
|
||||
{
|
||||
return m_keyStatesChanged;
|
||||
}
|
||||
|
||||
std::optional<piano_key_t> PianoKeyboardController::pressedKey() const
|
||||
{
|
||||
return m_pressedKey;
|
||||
}
|
||||
|
||||
void PianoKeyboardController::setPressedKey(std::optional<piano_key_t> key)
|
||||
{
|
||||
if (m_pressedKey == key) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_pressedKey.has_value()) {
|
||||
sendNoteOff(m_pressedKey.value());
|
||||
}
|
||||
|
||||
if (key.has_value()) {
|
||||
sendNoteOn(key.value());
|
||||
}
|
||||
|
||||
m_pressedKey = key;
|
||||
m_keyStatesChanged.notify();
|
||||
}
|
||||
|
||||
void PianoKeyboardController::onNotationChanged()
|
||||
{
|
||||
updateSelectedKeys();
|
||||
|
||||
if (auto notation = currentNotation()) {
|
||||
notation->interaction()->selectionChanged().onNotify(this, [this]() {
|
||||
updateSelectedKeys();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void PianoKeyboardController::updateSelectedKeys()
|
||||
{
|
||||
std::unordered_set<piano_key_t> newKeysInSelection;
|
||||
std::unordered_set<piano_key_t> newOtherNotesInSelectedChord;
|
||||
|
||||
DEFER {
|
||||
if (newKeysInSelection != m_keysInSelection
|
||||
|| newOtherNotesInSelectedChord != m_otherNotesInSelectedChord) {
|
||||
m_keysInSelection = newKeysInSelection;
|
||||
m_otherNotesInSelectedChord = newOtherNotesInSelectedChord;
|
||||
m_keyStatesChanged.notify();
|
||||
}
|
||||
};
|
||||
|
||||
auto notation = currentNotation();
|
||||
if (!notation) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto selection = notation->interaction()->selection();
|
||||
if (selection->isNone()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const Ms::Note* note : selection->notes()) {
|
||||
newKeysInSelection.insert(static_cast<piano_key_t>(note->epitch()));
|
||||
for (const Ms::Note* otherNote : note->chord()->notes()) {
|
||||
newOtherNotesInSelectedChord.insert(static_cast<piano_key_t>(otherNote->epitch()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PianoKeyboardController::sendNoteOn(piano_key_t key)
|
||||
{
|
||||
auto notation = currentNotation();
|
||||
if (!notation) {
|
||||
return;
|
||||
}
|
||||
|
||||
Event ev;
|
||||
ev.setMessageType(Event::MessageType::ChannelVoice10);
|
||||
ev.setOpcode(Event::Opcode::NoteOn);
|
||||
ev.setNote(key);
|
||||
ev.setVelocity(80);
|
||||
|
||||
notation->midiInput()->onMidiEventReceived(ev);
|
||||
}
|
||||
|
||||
void PianoKeyboardController::sendNoteOff(piano_key_t key)
|
||||
{
|
||||
auto notation = currentNotation();
|
||||
if (!notation) {
|
||||
return;
|
||||
}
|
||||
|
||||
Event ev;
|
||||
ev.setMessageType(Event::MessageType::ChannelVoice10);
|
||||
ev.setOpcode(Event::Opcode::NoteOff);
|
||||
ev.setNote(key);
|
||||
|
||||
notation->midiInput()->onMidiEventReceived(ev);
|
||||
}
|
||||
|
||||
INotationPtr PianoKeyboardController::currentNotation() const
|
||||
{
|
||||
return context()->currentNotation();
|
||||
}
|
65
src/notation/view/pianokeyboard/pianokeyboardcontroller.h
Normal file
65
src/notation/view/pianokeyboard/pianokeyboardcontroller.h
Normal file
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
* MuseScore-CLA-applies
|
||||
*
|
||||
* MuseScore
|
||||
* Music Composition & Notation
|
||||
*
|
||||
* Copyright (C) 2022 MuseScore BVBA and others
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 3 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef MU_NOTATION_PIANOKEYBOARDCONTROLLER_H
|
||||
#define MU_NOTATION_PIANOKEYBOARDCONTROLLER_H
|
||||
|
||||
#include "async/asyncable.h"
|
||||
|
||||
#include "modularity/ioc.h"
|
||||
#include "context/iglobalcontext.h"
|
||||
|
||||
#include "pianokeyboardtypes.h"
|
||||
|
||||
namespace mu::notation {
|
||||
class PianoKeyboardController : public async::Asyncable
|
||||
{
|
||||
INJECT(notation, context::IGlobalContext, context)
|
||||
|
||||
public:
|
||||
PianoKeyboardController() = default;
|
||||
|
||||
void init();
|
||||
|
||||
std::optional<piano_key_t> pressedKey() const;
|
||||
void setPressedKey(std::optional<piano_key_t> key);
|
||||
|
||||
KeyState keyState(piano_key_t key) const;
|
||||
async::Notification keyStatesChanged() const;
|
||||
|
||||
private:
|
||||
INotationPtr currentNotation() const;
|
||||
|
||||
void onNotationChanged();
|
||||
void updateSelectedKeys();
|
||||
|
||||
void sendNoteOn(piano_key_t key);
|
||||
void sendNoteOff(piano_key_t key);
|
||||
|
||||
std::optional<piano_key_t> m_pressedKey = std::nullopt;
|
||||
std::unordered_set<piano_key_t> m_keysInSelection;
|
||||
std::unordered_set<piano_key_t> m_otherNotesInSelectedChord;
|
||||
|
||||
async::Notification m_keyStatesChanged;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // MU_NOTATION_PIANOKEYBOARDCONTROLLER_H
|
|
@ -0,0 +1,188 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
* MuseScore-CLA-applies
|
||||
*
|
||||
* MuseScore
|
||||
* Music Composition & Notation
|
||||
*
|
||||
* Copyright (C) 2022 MuseScore BVBA and others
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 3 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "pianokeyboardpanelcontextmenumodel.h"
|
||||
|
||||
#include "actions/actiontypes.h"
|
||||
#include "pianokeyboardtypes.h"
|
||||
|
||||
#include "log.h"
|
||||
|
||||
using namespace mu::notation;
|
||||
using namespace mu::actions;
|
||||
using namespace mu::ui;
|
||||
using namespace mu::uicomponents;
|
||||
|
||||
static const ActionCode SET_KEY_WIDTH_SCALING_CODE("piano-keyboard-set-key-width-scaling");
|
||||
static const ActionCode SET_NUMBER_OF_KEYS_CODE("piano-keyboard-set-number-of-keys");
|
||||
|
||||
PianoKeyboardPanelContextMenuModel::PianoKeyboardPanelContextMenuModel(QObject* parent)
|
||||
: AbstractMenuModel(parent)
|
||||
{
|
||||
}
|
||||
|
||||
void PianoKeyboardPanelContextMenuModel::load()
|
||||
{
|
||||
MenuItemList items {
|
||||
makeViewMenu()
|
||||
};
|
||||
|
||||
setItems(items);
|
||||
}
|
||||
|
||||
qreal PianoKeyboardPanelContextMenuModel::keyWidthScaling() const
|
||||
{
|
||||
return m_currentKeyWidthScaling;
|
||||
}
|
||||
|
||||
void PianoKeyboardPanelContextMenuModel::setKeyWidthScaling(qreal scaling)
|
||||
{
|
||||
if (qFuzzyCompare(m_currentKeyWidthScaling, scaling)) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_currentKeyWidthScaling = scaling;
|
||||
updateKeyWidthScalingItems();
|
||||
emit keyWidthScalingChanged();
|
||||
}
|
||||
|
||||
int PianoKeyboardPanelContextMenuModel::numberOfKeys() const
|
||||
{
|
||||
return configuration()->pianoKeyboardNumberOfKeys().val;
|
||||
}
|
||||
|
||||
MenuItem* PianoKeyboardPanelContextMenuModel::makeViewMenu()
|
||||
{
|
||||
MenuItemList items;
|
||||
|
||||
std::vector<std::pair<QString, qreal> > possibleKeyWidthScalings {
|
||||
{ qtrc("notation", "Large"), LARGE_KEY_WIDTH_SCALING },
|
||||
{ qtrc("notation", "Normal"), NORMAL_KEY_WIDTH_SCALING },
|
||||
{ qtrc("notation", "Small"), SMALL_KEY_WIDTH_SCALING },
|
||||
};
|
||||
|
||||
for (auto [title, scaling] : possibleKeyWidthScalings) {
|
||||
items << makeKeyWidthScalingItem(title, scaling);
|
||||
}
|
||||
|
||||
updateKeyWidthScalingItems();
|
||||
|
||||
dispatcher()->reg(this, SET_KEY_WIDTH_SCALING_CODE, [this](const ActionData& args) {
|
||||
IF_ASSERT_FAILED(args.count() > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
emit setKeyWidthScalingRequested(args.arg<qreal>(0));
|
||||
});
|
||||
|
||||
items << makeSeparator();
|
||||
|
||||
std::vector<std::pair<QString, int> > possibleNumbersOfKeys {
|
||||
{ qtrc("notation", "128 notes (full)"), 128 },
|
||||
{ qtrc("notation", "88 notes (piano)"), 88 },
|
||||
{ qtrc("notation", "61 notes"), 61 },
|
||||
{ qtrc("notation", "49 notes"), 49 },
|
||||
{ qtrc("notation", "25 notes"), 25 },
|
||||
};
|
||||
|
||||
for (auto [title, numberOfKeys] : possibleNumbersOfKeys) {
|
||||
items << makeNumberOfKeysItem(title, numberOfKeys);
|
||||
}
|
||||
|
||||
configuration()->pianoKeyboardNumberOfKeys().ch.onReceive(this, [this](int) {
|
||||
emit numberOfKeysChanged();
|
||||
});
|
||||
|
||||
dispatcher()->reg(this, SET_NUMBER_OF_KEYS_CODE, [this](const ActionData& args) {
|
||||
IF_ASSERT_FAILED(args.count() > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
configuration()->setPianoKeyboardNumberOfKeys(args.arg<int>(0));
|
||||
});
|
||||
|
||||
return makeMenu(qtrc("notation", "View"), items);
|
||||
}
|
||||
|
||||
MenuItem* PianoKeyboardPanelContextMenuModel::makeKeyWidthScalingItem(const QString& title, qreal scaling)
|
||||
{
|
||||
UiAction action;
|
||||
action.title = title;
|
||||
action.code = SET_KEY_WIDTH_SCALING_CODE;
|
||||
action.checkable = Checkable::Yes;
|
||||
|
||||
MenuItem* item = new MenuItem(action, this);
|
||||
item->setId(QString::number(scaling));
|
||||
item->setState(UiActionState::make_enabled());
|
||||
item->setArgs(ActionData::make_arg1<qreal>(scaling));
|
||||
|
||||
m_keyWidthScalingItems << item;
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
MenuItem* PianoKeyboardPanelContextMenuModel::makeNumberOfKeysItem(const QString& title, int numberOfKeys)
|
||||
{
|
||||
UiAction action;
|
||||
action.title = title;
|
||||
action.code = SET_NUMBER_OF_KEYS_CODE;
|
||||
action.checkable = Checkable::Yes;
|
||||
|
||||
MenuItem* item = new MenuItem(action, this);
|
||||
item->setId(QString::number(numberOfKeys));
|
||||
|
||||
ValCh<int> currentNumberOfKeys = configuration()->pianoKeyboardNumberOfKeys();
|
||||
|
||||
bool checked = numberOfKeys == currentNumberOfKeys.val;
|
||||
item->setState(UiActionState::make_enabled(checked));
|
||||
|
||||
currentNumberOfKeys.ch.onReceive(item, [item, numberOfKeys](int num) {
|
||||
bool checked = numberOfKeys == num;
|
||||
item->setState(UiActionState::make_enabled(checked));
|
||||
});
|
||||
|
||||
item->setArgs(ActionData::make_arg1<int>(numberOfKeys));
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
void PianoKeyboardPanelContextMenuModel::updateKeyWidthScalingItems()
|
||||
{
|
||||
qreal roundedCurrentScaling = NORMAL_KEY_WIDTH_SCALING;
|
||||
|
||||
constexpr qreal betweenNormalAndSmall = (NORMAL_KEY_WIDTH_SCALING + SMALL_KEY_WIDTH_SCALING) / 2;
|
||||
constexpr qreal betweenNormalAndLarge = (NORMAL_KEY_WIDTH_SCALING + LARGE_KEY_WIDTH_SCALING) / 2;
|
||||
|
||||
if (m_currentKeyWidthScaling < betweenNormalAndSmall) {
|
||||
roundedCurrentScaling = SMALL_KEY_WIDTH_SCALING;
|
||||
} else if (m_currentKeyWidthScaling > betweenNormalAndLarge) {
|
||||
roundedCurrentScaling = LARGE_KEY_WIDTH_SCALING;
|
||||
}
|
||||
|
||||
for (MenuItem* item : m_keyWidthScalingItems) {
|
||||
IF_ASSERT_FAILED(item->args().count() > 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bool checked = qFuzzyCompare(item->args().arg<qreal>(0), roundedCurrentScaling);
|
||||
item->setState(UiActionState::make_enabled(checked));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
* MuseScore-CLA-applies
|
||||
*
|
||||
* MuseScore
|
||||
* Music Composition & Notation
|
||||
*
|
||||
* Copyright (C) 2022 MuseScore BVBA and others
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 3 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef MU_NOTATION_PIANOKEYBOARDPANELCONTEXTMENUMODEL_H
|
||||
#define MU_NOTATION_PIANOKEYBOARDPANELCONTEXTMENUMODEL_H
|
||||
|
||||
#include "uicomponents/view/abstractmenumodel.h"
|
||||
#include "actions/actionable.h"
|
||||
|
||||
#include "modularity/ioc.h"
|
||||
#include "inotationconfiguration.h"
|
||||
#include "actions/iactionsdispatcher.h"
|
||||
|
||||
namespace mu::notation {
|
||||
class PianoKeyboardPanelContextMenuModel : public uicomponents::AbstractMenuModel, public actions::Actionable
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
INJECT(notation, INotationConfiguration, configuration)
|
||||
INJECT(notation, actions::IActionsDispatcher, dispatcher)
|
||||
|
||||
Q_PROPERTY(int numberOfKeys READ numberOfKeys NOTIFY numberOfKeysChanged)
|
||||
Q_PROPERTY(qreal keyWidthScaling READ keyWidthScaling WRITE setKeyWidthScaling NOTIFY keyWidthScalingChanged)
|
||||
|
||||
public:
|
||||
explicit PianoKeyboardPanelContextMenuModel(QObject* parent = nullptr);
|
||||
|
||||
Q_INVOKABLE void load() override;
|
||||
|
||||
qreal keyWidthScaling() const;
|
||||
void setKeyWidthScaling(qreal scaling);
|
||||
|
||||
int numberOfKeys() const;
|
||||
|
||||
signals:
|
||||
void keyWidthScalingChanged();
|
||||
void setKeyWidthScalingRequested(qreal scaling);
|
||||
void numberOfKeysChanged();
|
||||
|
||||
private:
|
||||
uicomponents::MenuItem* makeViewMenu();
|
||||
|
||||
uicomponents::MenuItem* makeKeyWidthScalingItem(const QString& title, qreal scaling);
|
||||
uicomponents::MenuItem* makeNumberOfKeysItem(const QString& title, int numberOfKeys);
|
||||
|
||||
void updateKeyWidthScalingItems();
|
||||
|
||||
uicomponents::MenuItemList m_keyWidthScalingItems;
|
||||
|
||||
qreal m_currentKeyWidthScaling = 0.0;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // MU_NOTATION_PIANOKEYBOARDPANELCONTEXTMENUMODEL_H
|
40
src/notation/view/pianokeyboard/pianokeyboardtypes.h
Normal file
40
src/notation/view/pianokeyboard/pianokeyboardtypes.h
Normal file
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
* MuseScore-CLA-applies
|
||||
*
|
||||
* MuseScore
|
||||
* Music Composition & Notation
|
||||
*
|
||||
* Copyright (C) 2022 MuseScore BVBA and others
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 3 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef MU_NOTATION_PIANOKEYBOARDTYPES_H
|
||||
#define MU_NOTATION_PIANOKEYBOARDTYPES_H
|
||||
|
||||
namespace mu::notation {
|
||||
using piano_key_t = uint8_t;
|
||||
|
||||
static constexpr qreal SMALL_KEY_WIDTH_SCALING = 0.5;
|
||||
static constexpr qreal NORMAL_KEY_WIDTH_SCALING = 1.0;
|
||||
static constexpr qreal LARGE_KEY_WIDTH_SCALING = 2.0;
|
||||
|
||||
enum class KeyState {
|
||||
None,
|
||||
OtherInSelectedChord,
|
||||
Selected,
|
||||
Played
|
||||
};
|
||||
}
|
||||
|
||||
#endif // MU_NOTATION_PIANOKEYBOARDTYPES_H
|
512
src/notation/view/pianokeyboard/pianokeyboardview.cpp
Normal file
512
src/notation/view/pianokeyboard/pianokeyboardview.cpp
Normal file
|
@ -0,0 +1,512 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
* MuseScore-CLA-applies
|
||||
*
|
||||
* MuseScore
|
||||
* Music Composition & Notation
|
||||
*
|
||||
* Copyright (C) 2022 MuseScore BVBA and others
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 3 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "pianokeyboardview.h"
|
||||
|
||||
#include <QPainter>
|
||||
|
||||
#include "pianokeyboardcontroller.h"
|
||||
|
||||
#include "log.h"
|
||||
|
||||
using namespace mu::notation;
|
||||
|
||||
static const QColor backgroundColor(36, 36, 39);
|
||||
|
||||
static constexpr bool isBlackKey(piano_key_t key)
|
||||
{
|
||||
constexpr bool isBlack[12] { false, true, false, true, false, false, true, false, true, false, true, false };
|
||||
|
||||
return isBlack[key % 12];
|
||||
}
|
||||
|
||||
static qreal abs2d(qreal x, qreal y)
|
||||
{
|
||||
return sqrt(x * x + y * y) * (y > -x ? 1 : -1);
|
||||
}
|
||||
|
||||
static QColor mixedColors(QColor background, QColor foreground, qreal opacity)
|
||||
{
|
||||
QColor result;
|
||||
result.setRed(opacity * foreground.red() + (1 - opacity) * background.red());
|
||||
result.setGreen(opacity * foreground.green() + (1 - opacity) * background.green());
|
||||
result.setBlue(opacity * foreground.blue() + (1 - opacity) * background.blue());
|
||||
return result;
|
||||
}
|
||||
|
||||
PianoKeyboardView::PianoKeyboardView(QQuickItem* parent)
|
||||
: QQuickPaintedItem(parent), m_controller(new PianoKeyboardController())
|
||||
{
|
||||
calculateKeyRects();
|
||||
|
||||
connect(this, &QQuickItem::widthChanged, this, &PianoKeyboardView::calculateKeyRects);
|
||||
connect(this, &QQuickItem::heightChanged, this, &PianoKeyboardView::calculateKeyRects);
|
||||
|
||||
uiConfiguration()->fontChanged().onNotify(this, [this]() {
|
||||
determineOctaveLabelsFont();
|
||||
update();
|
||||
});
|
||||
|
||||
updateKeyStateColors();
|
||||
uiConfiguration()->currentThemeChanged().onNotify(this, [this]() {
|
||||
updateKeyStateColors();
|
||||
update();
|
||||
});
|
||||
|
||||
m_controller->init();
|
||||
|
||||
m_controller->keyStatesChanged().onNotify(this, [this]() {
|
||||
update();
|
||||
});
|
||||
|
||||
setAcceptedMouseButtons(Qt::LeftButton);
|
||||
}
|
||||
|
||||
PianoKeyboardView::~PianoKeyboardView()
|
||||
{
|
||||
delete m_controller;
|
||||
}
|
||||
|
||||
void PianoKeyboardView::calculateKeyRects()
|
||||
{
|
||||
TRACEFUNC;
|
||||
|
||||
determineOctaveLabelsFont();
|
||||
|
||||
m_blackKeyRects.clear();
|
||||
m_whiteKeyRects.clear();
|
||||
|
||||
constexpr qreal defaultSpacing = 2.0;
|
||||
constexpr qreal maxSpacing = 2.0;
|
||||
|
||||
m_spacing = std::min(defaultSpacing * m_keyWidthScaling, maxSpacing);
|
||||
|
||||
constexpr qreal defaultWhiteKeyWidth = 30.0;
|
||||
constexpr qreal defaultBlackKeyWidth = 20.0;
|
||||
|
||||
qreal whiteKeyWidth = defaultWhiteKeyWidth * m_keyWidthScaling + m_spacing;
|
||||
qreal blackKeyWidth = defaultBlackKeyWidth * m_keyWidthScaling;
|
||||
|
||||
constexpr qreal defaultWhiteKeyHeight = 128.0;
|
||||
constexpr qreal defaultBlackKeyHeight = 84.0;
|
||||
|
||||
m_whiteKeyHeight = std::min(height(), defaultWhiteKeyHeight * m_keyWidthScaling + m_spacing * 2);
|
||||
qreal actualKeyHeightScaling = (m_whiteKeyHeight - m_spacing * 2) / defaultWhiteKeyHeight;
|
||||
m_blackKeyHeight = defaultBlackKeyHeight * actualKeyHeightScaling + m_spacing;
|
||||
|
||||
qreal hPos = m_spacing / 2;
|
||||
|
||||
piano_key_t numKeys = std::min<piano_key_t>(m_lowestKey + m_numberOfKeys, MAX_NUM_KEYS);
|
||||
for (piano_key_t key = m_lowestKey; key < numKeys; ++key) {
|
||||
if (isBlackKey(key)) {
|
||||
constexpr qreal offsets[12] {
|
||||
0.0, -13.0, 0.0, -7.0, 0.0, 0.0, -13.0, 0.0, -10.0, 0.0, -7.0, 0.0
|
||||
};
|
||||
|
||||
qreal offset = offsets[key % 12] * m_keyWidthScaling;
|
||||
|
||||
m_blackKeyRects[key] = QRectF(hPos + offset, 0.0, blackKeyWidth, m_blackKeyHeight);
|
||||
} else {
|
||||
m_whiteKeyRects[key] = QRectF(hPos, 0.0, whiteKeyWidth, m_whiteKeyHeight);
|
||||
hPos += whiteKeyWidth;
|
||||
}
|
||||
}
|
||||
|
||||
m_keysAreaRect.setSize(QSizeF(hPos + m_spacing / 2, m_whiteKeyHeight));
|
||||
adjustKeysAreaPosition();
|
||||
}
|
||||
|
||||
void PianoKeyboardView::adjustKeysAreaPosition()
|
||||
{
|
||||
TRACEFUNC;
|
||||
|
||||
qreal keysAreaTop = (height() - m_keysAreaRect.height()) / 2;
|
||||
|
||||
qreal minScrollOffset = std::min(width() - m_keysAreaRect.width(), (width() - m_keysAreaRect.width()) / 2);
|
||||
qreal maxScrollOffset = std::max(0.0, (width() - m_keysAreaRect.width()) / 2);
|
||||
m_scrollOffset = std::clamp(m_scrollOffset, minScrollOffset, maxScrollOffset);
|
||||
|
||||
m_keysAreaRect.moveTo(QPointF(m_scrollOffset, keysAreaTop));
|
||||
|
||||
updateScrollBar();
|
||||
}
|
||||
|
||||
void PianoKeyboardView::determineOctaveLabelsFont()
|
||||
{
|
||||
m_octaveLabelsFont.setFamily(QString::fromStdString(uiConfiguration()->fontFamily()));
|
||||
m_octaveLabelsFont.setPixelSize(uiConfiguration()->fontSize() * std::min(m_keyWidthScaling, 1.5));
|
||||
}
|
||||
|
||||
void PianoKeyboardView::updateKeyStateColors()
|
||||
{
|
||||
auto themeValues = uiConfiguration()->currentTheme().values;
|
||||
|
||||
QColor accentColor = themeValues[ui::ACCENT_COLOR].toString();
|
||||
|
||||
m_whiteKeyStateColors[KeyState::None] = Qt::white;
|
||||
m_whiteKeyStateColors[KeyState::OtherInSelectedChord] = mixedColors(Qt::white, accentColor, 0.25);
|
||||
m_whiteKeyStateColors[KeyState::Selected] = mixedColors(Qt::white, accentColor, 0.5);
|
||||
m_whiteKeyStateColors[KeyState::Played] = mixedColors(Qt::white, accentColor, 0.8);
|
||||
|
||||
QColor blackKeysBaseColor(78, 78, 78);
|
||||
m_blackKeyStateColors[KeyState::None] = blackKeysBaseColor;
|
||||
m_blackKeyStateColors[KeyState::OtherInSelectedChord] = mixedColors(blackKeysBaseColor, accentColor, 0.4);
|
||||
m_blackKeyStateColors[KeyState::Selected] = mixedColors(blackKeysBaseColor, accentColor, 0.8);
|
||||
m_blackKeyStateColors[KeyState::Played] = mixedColors(blackKeysBaseColor, accentColor, 1.0);
|
||||
}
|
||||
|
||||
void PianoKeyboardView::paint(QPainter* painter)
|
||||
{
|
||||
TRACEFUNC;
|
||||
|
||||
painter->setRenderHint(QPainter::Antialiasing);
|
||||
|
||||
paintBackground(painter);
|
||||
|
||||
QPointF pos = m_keysAreaRect.topLeft();
|
||||
painter->translate(pos);
|
||||
|
||||
QRectF viewport = QRectF(0.0, 0.0, width(), height()).translated(-pos);
|
||||
paintWhiteKeys(painter, viewport);
|
||||
paintBlackKeys(painter, viewport);
|
||||
}
|
||||
|
||||
void PianoKeyboardView::paintBackground(QPainter* painter)
|
||||
{
|
||||
painter->fillRect(m_keysAreaRect, backgroundColor);
|
||||
}
|
||||
|
||||
void PianoKeyboardView::paintWhiteKeys(QPainter* painter, const QRectF& viewport)
|
||||
{
|
||||
QPainterPath path;
|
||||
|
||||
for (auto [key, rect] : m_whiteKeyRects) {
|
||||
if (!viewport.intersects(rect)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
qreal inset = m_spacing / 2;
|
||||
|
||||
qreal left = inset, top = m_spacing,
|
||||
right = rect.width() - inset, bottom = rect.height() - m_spacing;
|
||||
|
||||
if (path.isEmpty()) {
|
||||
qreal cornerRadius = 2.0 * m_keyWidthScaling;
|
||||
|
||||
path.moveTo(left, top);
|
||||
path.lineTo(left, bottom - cornerRadius);
|
||||
path.quadTo(left, bottom, left + cornerRadius, bottom);
|
||||
path.lineTo(right - cornerRadius, bottom);
|
||||
path.quadTo(right, bottom, right, bottom - cornerRadius);
|
||||
path.lineTo(right, top);
|
||||
path.closeSubpath();
|
||||
}
|
||||
|
||||
painter->translate(rect.topLeft());
|
||||
|
||||
QColor fillColor = m_whiteKeyStateColors[m_controller->keyState(key)];
|
||||
|
||||
painter->fillPath(path, fillColor);
|
||||
|
||||
if (key % 12 == 0) {
|
||||
// Draw octave label
|
||||
qreal availableHeight = m_whiteKeyHeight - m_blackKeyHeight;
|
||||
qreal requiredHeight = m_octaveLabelsFont.pixelSize();
|
||||
qreal bottomOffsetToCenterTextInAvailableSpace = (availableHeight - requiredHeight) / 2;
|
||||
|
||||
constexpr qreal maxBottomOffset = 8.0;
|
||||
constexpr qreal defaultBottomOffset = 8.0;
|
||||
|
||||
qreal bottomOffset = std::min({
|
||||
bottomOffsetToCenterTextInAvailableSpace,
|
||||
defaultBottomOffset * m_keyWidthScaling,
|
||||
maxBottomOffset
|
||||
});
|
||||
|
||||
int octaveNumber = (key / 12) - 1;
|
||||
|
||||
QString octaveLabel = "C" + QString::number(octaveNumber);
|
||||
QRect octaveLabelRect(left, top, right - left, bottom - top - bottomOffset);
|
||||
|
||||
painter->setPen(backgroundColor);
|
||||
painter->setFont(m_octaveLabelsFont);
|
||||
painter->drawText(octaveLabelRect, Qt::AlignHCenter | Qt::AlignBottom, octaveLabel);
|
||||
}
|
||||
|
||||
painter->translate(-rect.topLeft());
|
||||
}
|
||||
}
|
||||
|
||||
void PianoKeyboardView::paintBlackKeys(QPainter* painter, const QRectF& viewport)
|
||||
{
|
||||
QLinearGradient topPieceGradient;
|
||||
QLinearGradient bottomPieceGradient;
|
||||
|
||||
QRectF backgroundRect;
|
||||
QPainterPath topPiecePath;
|
||||
QPainterPath bottomPiecePath;
|
||||
|
||||
for (auto [key, rect] : m_blackKeyRects) {
|
||||
if (!viewport.intersects(rect)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (backgroundRect.isEmpty()) {
|
||||
qreal blackKeyInset = 2.0 * m_keyWidthScaling;
|
||||
qreal cornerRadius = blackKeyInset;
|
||||
qreal bottomPieceHeight = 8.0 * m_keyWidthScaling;
|
||||
|
||||
// Make background rect
|
||||
// Adjust the top, to make sure that the rect fully covers the white keys,
|
||||
// but does not extend beyond the background of the whole keyboard (which
|
||||
// theoretically shouldn't happen, but in practice does, probably due to
|
||||
// antialiasing)
|
||||
backgroundRect.setSize(rect.size());
|
||||
backgroundRect.setTop(0.5 * m_spacing);
|
||||
|
||||
// Make top piece
|
||||
qreal top = m_spacing + 0.5 * blackKeyInset,
|
||||
left = blackKeyInset,
|
||||
right = rect.width() - blackKeyInset,
|
||||
bottom = rect.height() - blackKeyInset - bottomPieceHeight - 0.5 * blackKeyInset;
|
||||
|
||||
qreal center = rect.width() / 2;
|
||||
|
||||
topPiecePath.moveTo(left, top);
|
||||
topPiecePath.lineTo(left, bottom - cornerRadius);
|
||||
topPiecePath.quadTo(left, bottom, center, bottom);
|
||||
topPiecePath.quadTo(right, bottom, right, bottom - cornerRadius);
|
||||
topPiecePath.lineTo(right, top);
|
||||
topPiecePath.closeSubpath();
|
||||
|
||||
topPieceGradient.setColorAt(0.0, backgroundColor);
|
||||
topPieceGradient.setStart(0.0, top);
|
||||
topPieceGradient.setFinalStop(0.0, bottom);
|
||||
|
||||
// Make bottom piece
|
||||
top = rect.height() - blackKeyInset - bottomPieceHeight;
|
||||
bottom = rect.height() - blackKeyInset;
|
||||
|
||||
bottomPiecePath.moveTo(left, top + cornerRadius);
|
||||
bottomPiecePath.quadTo(left, top, center, top);
|
||||
bottomPiecePath.quadTo(right, top, right, top + cornerRadius);
|
||||
bottomPiecePath.lineTo(right, bottom);
|
||||
bottomPiecePath.lineTo(left, bottom);
|
||||
bottomPiecePath.closeSubpath();
|
||||
|
||||
bottomPieceGradient.setColorAt(1.0, backgroundColor);
|
||||
bottomPieceGradient.setStart(0.0, top);
|
||||
bottomPieceGradient.setFinalStop(0.0, bottom);
|
||||
}
|
||||
|
||||
QColor highlightColor = m_blackKeyStateColors[m_controller->keyState(key)];
|
||||
topPieceGradient.setColorAt(1.0, highlightColor);
|
||||
bottomPieceGradient.setColorAt(0.0, highlightColor);
|
||||
|
||||
painter->translate(rect.topLeft());
|
||||
painter->fillRect(backgroundRect, backgroundColor);
|
||||
painter->fillPath(topPiecePath, topPieceGradient);
|
||||
painter->fillPath(bottomPiecePath, bottomPieceGradient);
|
||||
painter->translate(-rect.topLeft());
|
||||
}
|
||||
}
|
||||
|
||||
int PianoKeyboardView::numberOfKeys() const
|
||||
{
|
||||
return m_numberOfKeys;
|
||||
}
|
||||
|
||||
void PianoKeyboardView::setNumberOfKeys(int number)
|
||||
{
|
||||
if (m_numberOfKeys == number) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (number) {
|
||||
case 128:
|
||||
m_lowestKey = 0;
|
||||
m_numberOfKeys = 128;
|
||||
break;
|
||||
case 88:
|
||||
m_lowestKey = 21;
|
||||
m_numberOfKeys = 88;
|
||||
break;
|
||||
case 61:
|
||||
m_lowestKey = 36;
|
||||
m_numberOfKeys = 61;
|
||||
break;
|
||||
case 49:
|
||||
m_lowestKey = 36;
|
||||
m_numberOfKeys = 49;
|
||||
break;
|
||||
case 25:
|
||||
m_lowestKey = 48;
|
||||
m_numberOfKeys = 25;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
emit numberOfKeysChanged();
|
||||
calculateKeyRects();
|
||||
update();
|
||||
}
|
||||
|
||||
void PianoKeyboardView::moveCanvas(qreal dx)
|
||||
{
|
||||
setScrollOffset(m_scrollOffset + dx);
|
||||
}
|
||||
|
||||
void PianoKeyboardView::setScrollOffset(qreal offset)
|
||||
{
|
||||
qreal oldScrollOffset = m_scrollOffset;
|
||||
m_scrollOffset = offset;
|
||||
adjustKeysAreaPosition();
|
||||
|
||||
if (qFuzzyCompare(m_scrollOffset, oldScrollOffset)) {
|
||||
return;
|
||||
}
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
qreal PianoKeyboardView::keyWidthScaling() const
|
||||
{
|
||||
return m_keyWidthScaling;
|
||||
}
|
||||
|
||||
void PianoKeyboardView::scale(qreal factor, qreal x)
|
||||
{
|
||||
setScaling(m_keyWidthScaling * factor, x);
|
||||
}
|
||||
|
||||
void PianoKeyboardView::setScaling(qreal scaling, qreal x)
|
||||
{
|
||||
qreal newScaling = std::clamp(scaling, SMALL_KEY_WIDTH_SCALING, LARGE_KEY_WIDTH_SCALING);
|
||||
qreal correctedFactor = newScaling / m_keyWidthScaling;
|
||||
|
||||
if (qFuzzyCompare(correctedFactor, 1.0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_keyWidthScaling = newScaling;
|
||||
m_scrollOffset *= correctedFactor;
|
||||
m_scrollOffset += x * (1 - correctedFactor);
|
||||
|
||||
emit keyWidthScalingChanged();
|
||||
calculateKeyRects();
|
||||
update();
|
||||
}
|
||||
|
||||
qreal PianoKeyboardView::scrollBarPosition() const
|
||||
{
|
||||
return m_scrollBarPosition;
|
||||
}
|
||||
|
||||
qreal PianoKeyboardView::scrollBarSize() const
|
||||
{
|
||||
return m_scrollBarSize;
|
||||
}
|
||||
|
||||
void PianoKeyboardView::updateScrollBar()
|
||||
{
|
||||
qreal newPosition = -m_scrollOffset / m_keysAreaRect.width();
|
||||
qreal newSize = width() / m_keysAreaRect.width();
|
||||
|
||||
if (qFuzzyCompare(newPosition, m_scrollBarPosition)
|
||||
&& qFuzzyCompare(newSize, m_scrollBarSize)) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_scrollBarPosition = newPosition;
|
||||
m_scrollBarSize = newSize;
|
||||
emit scrollBarChanged();
|
||||
}
|
||||
|
||||
void PianoKeyboardView::setScrollBarPosition(qreal position)
|
||||
{
|
||||
setScrollOffset(-position * m_keysAreaRect.width());
|
||||
}
|
||||
|
||||
std::optional<piano_key_t> PianoKeyboardView::keyAt(const QPointF& position) const
|
||||
{
|
||||
QPointF correctedPosition = position - m_keysAreaRect.topLeft();
|
||||
|
||||
for (auto [key, rect] : m_blackKeyRects) {
|
||||
if (rect.contains(correctedPosition)) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto [key, rect] : m_whiteKeyRects) {
|
||||
if (rect.contains(correctedPosition)) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void PianoKeyboardView::wheelEvent(QWheelEvent* event)
|
||||
{
|
||||
QPoint delta = event->pixelDelta();
|
||||
|
||||
if (delta.isNull()) {
|
||||
delta = event->angleDelta();
|
||||
}
|
||||
|
||||
Qt::KeyboardModifiers keyState = event->modifiers();
|
||||
|
||||
// Windows touch pad pinches also execute this
|
||||
if (keyState & Qt::ControlModifier) {
|
||||
// For every pixel being scrolled, zoom by a factor or 1.002 (value is found empirically)
|
||||
constexpr qreal zoomSpeed = 1.002;
|
||||
qreal abs = abs2d(delta.x(), delta.y());
|
||||
qreal factor = pow(zoomSpeed, abs);
|
||||
scale(factor, event->position().x());
|
||||
return;
|
||||
}
|
||||
|
||||
if (delta.x() == 0) {
|
||||
// Make life easy for people who can only scroll vertically
|
||||
moveCanvas(delta.y());
|
||||
} else if (keyState & Qt::ShiftModifier) {
|
||||
qreal abs = abs2d(delta.x(), delta.y());
|
||||
moveCanvas(abs);
|
||||
} else {
|
||||
moveCanvas(delta.x());
|
||||
}
|
||||
}
|
||||
|
||||
void PianoKeyboardView::mousePressEvent(QMouseEvent* event)
|
||||
{
|
||||
m_controller->setPressedKey(keyAt(event->pos()));
|
||||
}
|
||||
|
||||
void PianoKeyboardView::mouseMoveEvent(QMouseEvent* event)
|
||||
{
|
||||
m_controller->setPressedKey(keyAt(event->pos()));
|
||||
}
|
||||
|
||||
void PianoKeyboardView::mouseReleaseEvent(QMouseEvent*)
|
||||
{
|
||||
m_controller->setPressedKey(std::nullopt);
|
||||
}
|
124
src/notation/view/pianokeyboard/pianokeyboardview.h
Normal file
124
src/notation/view/pianokeyboard/pianokeyboardview.h
Normal file
|
@ -0,0 +1,124 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
* MuseScore-CLA-applies
|
||||
*
|
||||
* MuseScore
|
||||
* Music Composition & Notation
|
||||
*
|
||||
* Copyright (C) 2022 MuseScore BVBA and others
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 3 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef MU_NOTATION_PIANOKEYBOARDVIEW_H
|
||||
#define MU_NOTATION_PIANOKEYBOARDVIEW_H
|
||||
|
||||
#include <QQuickPaintedItem>
|
||||
|
||||
#include "async/asyncable.h"
|
||||
|
||||
#include "modularity/ioc.h"
|
||||
#include "inotationconfiguration.h"
|
||||
#include "ui/iuiconfiguration.h"
|
||||
|
||||
#include "pianokeyboardtypes.h"
|
||||
|
||||
namespace mu::notation {
|
||||
class PianoKeyboardController;
|
||||
class PianoKeyboardView : public QQuickPaintedItem, public async::Asyncable
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
INJECT(notation, INotationConfiguration, configuration)
|
||||
INJECT(notation, ui::IUiConfiguration, uiConfiguration)
|
||||
|
||||
Q_PROPERTY(int numberOfKeys READ numberOfKeys WRITE setNumberOfKeys NOTIFY numberOfKeysChanged)
|
||||
Q_PROPERTY(qreal keyWidthScaling READ keyWidthScaling WRITE setScaling NOTIFY keyWidthScalingChanged)
|
||||
|
||||
Q_PROPERTY(qreal scrollBarPosition READ scrollBarPosition WRITE setScrollBarPosition NOTIFY scrollBarChanged)
|
||||
Q_PROPERTY(qreal scrollBarSize READ scrollBarSize NOTIFY scrollBarChanged)
|
||||
|
||||
public:
|
||||
explicit PianoKeyboardView(QQuickItem* parent = nullptr);
|
||||
~PianoKeyboardView() override;
|
||||
|
||||
void paint(QPainter* painter) override;
|
||||
|
||||
int numberOfKeys() const;
|
||||
void setNumberOfKeys(int number);
|
||||
|
||||
qreal keyWidthScaling() const;
|
||||
Q_INVOKABLE void scale(qreal factor, qreal x);
|
||||
void setScaling(qreal scaling, qreal x = 0.0);
|
||||
|
||||
qreal scrollBarPosition() const;
|
||||
qreal scrollBarSize() const;
|
||||
void setScrollBarPosition(qreal position);
|
||||
|
||||
signals:
|
||||
void numberOfKeysChanged();
|
||||
void keyWidthScalingChanged();
|
||||
|
||||
void scrollBarChanged();
|
||||
|
||||
private:
|
||||
void calculateKeyRects();
|
||||
void adjustKeysAreaPosition();
|
||||
void determineOctaveLabelsFont();
|
||||
void updateKeyStateColors();
|
||||
|
||||
void paintBackground(QPainter* painter);
|
||||
|
||||
void paintWhiteKeys(QPainter* painter, const QRectF& viewport);
|
||||
void paintBlackKeys(QPainter* painter, const QRectF& viewport);
|
||||
|
||||
void moveCanvas(qreal dx);
|
||||
void setScrollOffset(qreal offset);
|
||||
void updateScrollBar();
|
||||
|
||||
void wheelEvent(QWheelEvent* event) override;
|
||||
void mousePressEvent(QMouseEvent* event) override;
|
||||
void mouseMoveEvent(QMouseEvent* event) override;
|
||||
void mouseReleaseEvent(QMouseEvent* event) override;
|
||||
|
||||
std::optional<piano_key_t> keyAt(const QPointF& position) const;
|
||||
|
||||
static constexpr piano_key_t MIN_KEY = 0;
|
||||
static constexpr piano_key_t MAX_NUM_KEYS = 128;
|
||||
|
||||
piano_key_t m_lowestKey = MIN_KEY;
|
||||
piano_key_t m_numberOfKeys = MAX_NUM_KEYS;
|
||||
|
||||
PianoKeyboardController* m_controller = nullptr;
|
||||
|
||||
QRectF m_keysAreaRect;
|
||||
std::map<piano_key_t, QRectF> m_blackKeyRects;
|
||||
std::map<piano_key_t, QRectF> m_whiteKeyRects;
|
||||
|
||||
QFont m_octaveLabelsFont;
|
||||
|
||||
std::map<KeyState, QColor> m_whiteKeyStateColors;
|
||||
std::map<KeyState, QColor> m_blackKeyStateColors;
|
||||
|
||||
qreal m_keyWidthScaling = 1.0;
|
||||
qreal m_scrollOffset = 0.0;
|
||||
qreal m_spacing = 0.0;
|
||||
|
||||
qreal m_whiteKeyHeight = 0.0;
|
||||
qreal m_blackKeyHeight = 0.0;
|
||||
|
||||
qreal m_scrollBarPosition = 0.0;
|
||||
qreal m_scrollBarSize = 0.0;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // MU_NOTATION_PIANOKEYBOARDVIEW_H
|
|
@ -46,8 +46,6 @@ signals:
|
|||
void expandCollapseAllRequested(bool expand);
|
||||
|
||||
private:
|
||||
void buildMenu();
|
||||
|
||||
uicomponents::MenuItem* createIsSingleClickToOpenPaletteItem();
|
||||
uicomponents::MenuItem* createIsSinglePaletteItem();
|
||||
uicomponents::MenuItem* createExpandCollapseAllItem(bool expand);
|
||||
|
|
Loading…
Reference in a new issue