Skip to content

Commit c61c46d

Browse files
committed
Implement a custom list view renderer
- This is a significant performance and smoothness boost
1 parent 6099f8d commit c61c46d

9 files changed

Lines changed: 566 additions & 91 deletions

CHANGELOG

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ Bug fixes:
2929

3030
Other:
3131

32+
* Optimize UI performance during playback and pattern changes
33+
- Replaces heavy QML ListViews with custom C++ renderers (QQuickPaintedItem)
34+
3235
1.8.1
3336
=====
3437

src/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ set(HEADER_FILES
1212
application/command/automation_command.hpp
1313
application/command/composite_command.hpp
1414
application/command/undo_stack.hpp
15+
view/qml/Editor/line_number_renderer.hpp
1516
application/models/audio_settings_model.hpp
1617
application/models/column_settings_model.hpp
1718
application/models/event_selection_model.hpp
@@ -25,6 +26,7 @@ set(HEADER_FILES
2526
application/models/recent_files_model.hpp
2627
application/models/track_settings_model.hpp
2728
application/note_converter.hpp
29+
view/qml/Editor/note_column_renderer.hpp
2830
application/position.hpp
2931
application/service/application_service.hpp
3032
application/service/audio_service.hpp
@@ -107,6 +109,7 @@ set(SOURCE_FILES
107109
application/command/automation_command.cpp
108110
application/command/composite_command.cpp
109111
application/command/undo_stack.cpp
112+
view/qml/Editor/line_number_renderer.cpp
110113
application/models/audio_settings_model.cpp
111114
application/models/column_settings_model.cpp
112115
application/models/event_selection_model.cpp
@@ -120,6 +123,7 @@ set(SOURCE_FILES
120123
application/models/recent_files_model.cpp
121124
application/models/track_settings_model.cpp
122125
application/note_converter.cpp
126+
view/qml/Editor/note_column_renderer.cpp
123127
application/service/application_service.cpp
124128
application/service/audio_service.cpp
125129
application/service/audio_worker.cpp

src/application/application.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@
5050
#include "service/util_service.hpp"
5151
#include "state_machine.hpp"
5252
#include "ui_logger.hpp"
53+
#include "view/qml/Editor/line_number_renderer.hpp"
54+
#include "view/qml/Editor/note_column_renderer.hpp"
5355

5456
#include <QGuiApplication>
5557
#include <QQmlApplicationEngine>
@@ -134,12 +136,14 @@ void Application::registerTypes()
134136
qmlRegisterType<EditorService>("Noteahead", majorVersion, minorVersion, "EditorService");
135137
qmlRegisterType<EventSelectionModel>("Noteahead", majorVersion, minorVersion, "EventSelectionModel");
136138
qmlRegisterType<KeyboardService>("Noteahead", majorVersion, minorVersion, "KeyboardService");
139+
qmlRegisterType<LineNumberRenderer>("Noteahead", majorVersion, minorVersion, "LineNumberRenderer");
137140
qmlRegisterType<MidiCcAutomationsModel>("Noteahead", majorVersion, minorVersion, "MidiCcAutomationsModel");
138141
qmlRegisterType<MidiCcSelectionModel>("Noteahead", majorVersion, minorVersion, "MidiCcSelectionModel");
139142
qmlRegisterType<MidiService>("Noteahead", majorVersion, minorVersion, "MidiService");
140143
qmlRegisterType<MidiSettingsModel>("Noteahead", majorVersion, minorVersion, "MidiSettingsModel");
141144
qmlRegisterType<MixerService>("Noteahead", majorVersion, minorVersion, "MixerService");
142145
qmlRegisterType<NoteColumnLineContainerHelper>("Noteahead", majorVersion, minorVersion, "NoteColumnLineContainerHelper");
146+
qmlRegisterType<NoteColumnRenderer>("Noteahead", majorVersion, minorVersion, "NoteColumnRenderer");
143147
qmlRegisterType<PitchBendAutomationsModel>("Noteahead", majorVersion, minorVersion, "PitchBendAutomationsModel");
144148
qmlRegisterType<PropertyService>("Noteahead", majorVersion, minorVersion, "PropertyService");
145149
qmlRegisterType<RecentFilesModel>("Noteahead", majorVersion, minorVersion, "RecentFilesModel");
Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import QtQuick 2.15
2+
import Noteahead 1.0
23
import ".."
34

45
Rectangle {
@@ -13,27 +14,23 @@ Rectangle {
1314
function setPosition(position) {
1415
if (_currentLine !== position.line) {
1516
_currentLine = position.line;
16-
if (listView) {
17-
listView.positionViewAtIndex(position.line, ListView.Beginning);
17+
if (renderer) {
18+
renderer.scrollOffset = position.line;
1819
}
1920
}
2021
}
2122
function updateData() {
2223
}
23-
function _lineHeight() {
24-
const lineCount = settingsService.visibleLines;
25-
return rootItem.height / lineCount;
26-
}
27-
ListView {
28-
id: listView
24+
LineNumberRenderer {
25+
id: renderer
2926
anchors.fill: parent
30-
model: editorService.currentLineCount + settingsService.visibleLines
31-
delegate: LineNumberDelegate {
32-
width: rootItem.width
33-
height: _lineHeight()
34-
index: model.index
35-
}
36-
interactive: false
27+
visibleLines: settingsService.visibleLines
28+
currentLineCount: editorService.currentLineCount
29+
linesPerBeat: editorService.linesPerBeat
30+
positionBarLine: editorService.positionBarLine()
31+
scrollOffset: editorService.position.line
32+
backgroundColor: Constants.lineNumberColumnCellBackgroundColor
33+
textColor: Constants.lineNumberColumnTextColor
3734
}
3835
Rectangle {
3936
id: borderRectangle
@@ -43,4 +40,4 @@ Rectangle {
4340
anchors.fill: parent
4441
z: 2
4542
}
46-
}
43+
}
Lines changed: 64 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import QtQuick 2.15
22
import QtQuick.Controls.Universal 2.15
3+
import Noteahead 1.0
34
import ".."
45

56
Item {
@@ -18,78 +19,36 @@ Item {
1819
property double _scrollOffset: 0
1920
property int _lastTriggeredLine: -1
2021
readonly property string _tag: "NoteColumnLineContainer"
21-
property ListView _listView
22+
property NoteColumnRenderer _renderer
2223
function resize(width: int, height: int): void {
2324
rootItem.width = width;
2425
rootItem.height = height;
25-
if (_listView) {
26-
_listView.width = width;
27-
_listView.height = height;
28-
_scrollLines();
26+
if (_renderer) {
27+
_renderer.width = width;
28+
_renderer.height = height;
2929
}
3030
}
3131
function setLocation(patternIndex: int, trackIndex: int, columnIndex: int): void {
3232
_patternIndex = patternIndex;
3333
_trackIndex = trackIndex;
3434
_index = columnIndex;
35-
if (!_listView) {
36-
_listView = listViewComponent.createObject(rootItem);
35+
if (!_renderer) {
36+
_renderer = rendererComponent.createObject(rootItem);
3737
}
38-
_listView.model = noteColumnModelHandler.columnModel(_patternIndex, _trackIndex, _index);
38+
_renderer.model = noteColumnModelHandler.columnModel(_patternIndex, _trackIndex, _index);
3939
_scrollOffset = editorService.position.line;
4040
_lastTriggeredLine = -1;
41-
_scrollLines();
42-
delayedScrollTimer.start(); // Hack for Qt < 6.5
41+
_renderer.scrollOffset = _scrollOffset;
4342
}
4443
function _getEffectiveLine(lineIndex: int): int {
4544
return lineIndex - editorService.positionBarLine();
4645
}
47-
function _getGlobalX(delegate: var, mouse: var): int {
48-
return delegate.mapToItem(delegate.Window.contentItem, Qt.point(mouse.x, mouse.y)).x;
49-
}
50-
function _getGlobalY(delegate: var, mouse: var): int {
51-
return delegate.mapToItem(delegate.Window.contentItem, Qt.point(mouse.x, mouse.y)).y;
52-
}
53-
function handleClickOnDelegate(listItemIndex: int, delegate: var, mouse: var): void {
54-
const effectiveLineIndex = _getEffectiveLine(listItemIndex);
55-
if (mouse.button === Qt.LeftButton) {
56-
uiLogger.info(_tag, `Column ${rootItem._index} left clicked on line ${effectiveLineIndex}`);
57-
rootItem.leftClicked(effectiveLineIndex, _getGlobalX(delegate, mouse), _getGlobalY(delegate, mouse));
58-
} else {
59-
uiLogger.info(_tag, `Column ${rootItem._index} right clicked on line ${effectiveLineIndex}`);
60-
rootItem.rightClicked(effectiveLineIndex, _getGlobalX(delegate, mouse), _getGlobalY(delegate, mouse));
61-
}
62-
}
63-
function handlePressOnDelegate(listItemIndex: int, delegate: var, mouse: var): void {
64-
const effectiveLineIndex = _getEffectiveLine(listItemIndex);
65-
if (mouse.button === Qt.LeftButton) {
66-
uiLogger.info(_tag, `Column ${rootItem._index} left pressed on line ${effectiveLineIndex}`);
67-
rootItem.leftPressed(effectiveLineIndex, _getGlobalX(delegate, mouse), _getGlobalY(delegate, mouse));
68-
} else {
69-
uiLogger.info(_tag, `Column ${rootItem._index} right pressed on line ${effectiveLineIndex}`);
70-
rootItem.rightPressed(effectiveLineIndex, _getGlobalX(delegate, mouse), _getGlobalY(delegate, mouse));
71-
}
72-
}
73-
function handleReleaseOnDelegate(listItemIndex: int, delegate: var, mouse: var): void {
74-
const effectiveLineIndex = _getEffectiveLine(listItemIndex);
75-
if (mouse.button === Qt.LeftButton) {
76-
uiLogger.info(_tag, `Column ${rootItem._index} left released on line ${effectiveLineIndex}`);
77-
rootItem.leftReleased(effectiveLineIndex, _getGlobalX(delegate, mouse), _getGlobalY(delegate, mouse));
78-
} else {
79-
uiLogger.info(_tag, `Column ${rootItem._index} right released on line ${effectiveLineIndex}`);
80-
rootItem.rightReleased(effectiveLineIndex, _getGlobalX(delegate, mouse), _getGlobalY(delegate, mouse));
81-
}
82-
}
83-
function handleMouseMoveOnDelegate(listItemIndex: int, delegate: var, mouse: var): void {
84-
const offset = Math.floor(mouse.y / delegate.height);
85-
const effectiveLineIndex = _getEffectiveLine(listItemIndex) + offset;
86-
uiLogger.info(_tag, `Column ${rootItem._index} mouse moved on line ${effectiveLineIndex}`);
87-
rootItem.mouseMoved(effectiveLineIndex, _getGlobalX(delegate, mouse), _getGlobalY(delegate, mouse));
88-
}
8946
function setPosition(position: var): void {
9047
if (_scrollOffset !== position.line) {
9148
_scrollOffset = position.line;
92-
_scrollLines();
49+
if (_renderer) {
50+
_renderer.scrollOffset = _scrollOffset;
51+
}
9352
}
9453

9554
if (UiService.isPlaying()) {
@@ -101,11 +60,6 @@ Item {
10160
_lastTriggeredLine = -1;
10261
}
10362
}
104-
function _scrollLines(): void {
105-
if (_listView) {
106-
_listView.positionViewAtIndex(_scrollOffset, ListView.Beginning);
107-
}
108-
}
10963
function _triggerVolumeMeterAtPosition(position: var): void {
11064
if (UiService.isPlaying() && mixerService.shouldColumnPlay(_trackIndex, _index)) {
11165
const velocity = editorService.velocityAtPosition(position.pattern, _trackIndex, _index, position.line);
@@ -115,25 +69,12 @@ Item {
11569
}
11670
}
11771
Component {
118-
id: listViewComponent
119-
ListView {
120-
id: listView
72+
id: rendererComponent
73+
NoteColumnRenderer {
12174
anchors.fill: parent
122-
cacheBuffer: 2
123-
clip: true
124-
delegate: NoteColumn_LineDelegate {
125-
height: rootItem.height / settingsService.visibleLines
126-
width: listView.width
127-
}
128-
interactive: false
75+
visibleLines: settingsService.visibleLines
12976
}
13077
}
131-
Timer {
132-
id: delayedScrollTimer
133-
interval: 1
134-
repeat: false
135-
onTriggered: _scrollLines()
136-
}
13778
VolumeMeter {
13879
id: volumeMeter
13980
anchors.top: rootItem.top
@@ -143,8 +84,9 @@ Item {
14384
z: 5
14485
}
14586
MouseArea {
146-
id: wheelHandler
87+
id: inputHandler
14788
anchors.fill: parent
89+
acceptedButtons: Qt.LeftButton | Qt.RightButton
14890
onWheel: event => {
14991
if (!UiService.isPlaying()) {
15092
if (event.angleDelta.y > 0) {
@@ -156,5 +98,52 @@ Item {
15698
}
15799
}
158100
}
101+
onClicked: mouse => {
102+
const rowHeight = height / settingsService.visibleLines;
103+
const rowIndex = Math.floor(mouse.y / rowHeight) + Math.floor(_scrollOffset);
104+
const effectiveLineIndex = _getEffectiveLine(rowIndex);
105+
const globalPos = mapToItem(Window.contentItem, Qt.point(mouse.x, mouse.y));
106+
if (mouse.button === Qt.LeftButton) {
107+
uiLogger.info(_tag, `Column ${rootItem._index} left clicked on line ${effectiveLineIndex}`);
108+
rootItem.leftClicked(effectiveLineIndex, globalPos.x, globalPos.y);
109+
} else {
110+
uiLogger.info(_tag, `Column ${rootItem._index} right clicked on line ${effectiveLineIndex}`);
111+
rootItem.rightClicked(effectiveLineIndex, globalPos.x, globalPos.y);
112+
}
113+
}
114+
onPressed: mouse => {
115+
const rowHeight = height / settingsService.visibleLines;
116+
const rowIndex = Math.floor(mouse.y / rowHeight) + Math.floor(_scrollOffset);
117+
const effectiveLineIndex = _getEffectiveLine(rowIndex);
118+
const globalPos = mapToItem(Window.contentItem, Qt.point(mouse.x, mouse.y));
119+
if (mouse.button === Qt.LeftButton) {
120+
uiLogger.info(_tag, `Column ${rootItem._index} left pressed on line ${effectiveLineIndex}`);
121+
rootItem.leftPressed(effectiveLineIndex, globalPos.x, globalPos.y);
122+
} else {
123+
uiLogger.info(_tag, `Column ${rootItem._index} right pressed on line ${effectiveLineIndex}`);
124+
rootItem.rightPressed(effectiveLineIndex, globalPos.x, globalPos.y);
125+
}
126+
}
127+
onReleased: mouse => {
128+
const rowHeight = height / settingsService.visibleLines;
129+
const rowIndex = Math.floor(mouse.y / rowHeight) + Math.floor(_scrollOffset);
130+
const effectiveLineIndex = _getEffectiveLine(rowIndex);
131+
const globalPos = mapToItem(Window.contentItem, Qt.point(mouse.x, mouse.y));
132+
if (mouse.button === Qt.LeftButton) {
133+
uiLogger.info(_tag, `Column ${rootItem._index} left released on line ${effectiveLineIndex}`);
134+
rootItem.leftReleased(effectiveLineIndex, globalPos.x, globalPos.y);
135+
} else {
136+
uiLogger.info(_tag, `Column ${rootItem._index} right released on line ${effectiveLineIndex}`);
137+
rootItem.rightReleased(effectiveLineIndex, globalPos.x, globalPos.y);
138+
}
139+
}
140+
onPositionChanged: mouse => {
141+
const rowHeight = height / settingsService.visibleLines;
142+
const rowIndex = Math.floor(mouse.y / rowHeight) + Math.floor(_scrollOffset);
143+
const effectiveLineIndex = _getEffectiveLine(rowIndex);
144+
const globalPos = mapToItem(Window.contentItem, Qt.point(mouse.x, mouse.y));
145+
uiLogger.info(_tag, `Column ${rootItem._index} mouse moved on line ${effectiveLineIndex}`);
146+
rootItem.mouseMoved(effectiveLineIndex, globalPos.x, globalPos.y);
147+
}
159148
}
160149
}

0 commit comments

Comments
 (0)