concept: keyboard navigation by subsections

This commit is contained in:
Igor Korsukov 2021-04-06 16:40:32 +02:00
parent 66f33addca
commit b75e2f460e
14 changed files with 524 additions and 125 deletions

View file

@ -63,5 +63,6 @@
<file>qml/DevTools/Autobot/AutobotControl.qml</file>
<file>qml/DevTools/KeyNav/KeyNavExample.qml</file>
<file>qml/DevTools/KeyNav/KeyNavSection.qml</file>
<file>qml/DevTools/KeyNav/KeyNavSubSection.qml</file>
</qresource>
</RCC>

View file

@ -16,6 +16,7 @@ Rectangle {
sectionName: "sect 2"
sectionOrder: 2
subsecCount: 3
}
KeyNavSection {
@ -25,6 +26,7 @@ Rectangle {
sectionName: "sect 1"
sectionOrder: 1
subsecCount: 1
}
KeyNavSection {
@ -34,6 +36,7 @@ Rectangle {
sectionName: "sect 3"
sectionOrder: 3
subsecCount: 2
}
KeyNavSection {
@ -43,6 +46,7 @@ Rectangle {
sectionName: "sect 4"
sectionOrder: 4
subsecCount: 4
}
KeyNavSection {
@ -52,6 +56,7 @@ Rectangle {
sectionName: "sect 5"
sectionOrder: 5
subsecCount: 2
}
}
}

View file

@ -9,6 +9,8 @@ Rectangle {
property alias sectionName: keynavsec.name
property alias sectionOrder: keynavsec.order
property alias subsecCount: subsecrepeater.model
height: 48
border.color: "#75507b"
@ -19,15 +21,33 @@ Rectangle {
}
Text {
id: title
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: btn.left
anchors.leftMargin: 8
verticalAlignment: Text.AlignVCenter
width: 48
text: root.sectionName
}
Row {
id: subs
anchors.left: title.right
anchors.right: btn.left
anchors.verticalCenter: parent.verticalCenter
spacing: 8
Repeater {
id: subsecrepeater
KeyNavSubSection {
keynavSection: keynavsec
subsectionName: "subsec" + model.index
subsectionOrder: model.index
}
}
}
FlatButton {
id: btn
anchors.right: parent.right

View file

@ -0,0 +1,20 @@
import QtQuick 2.15
import MuseScore.Ui 1.0
Rectangle {
property alias keynavSection: keynavsec.section
property alias subsectionName: keynavsec.name
property alias subsectionOrder: keynavsec.order
height: 40
width: 40
opacity: 0.8
border.color: "#75507b"
border.width: keynavsec.active ? 4 : 0
KeyNavigationSubSection {
id: keynavsec
}
}

View file

@ -101,6 +101,8 @@ set(MODULE_SRC
${CMAKE_CURRENT_LIST_DIR}/view/abstractmenumodel.h
${CMAKE_CURRENT_LIST_DIR}/view/keynavigationsection.cpp
${CMAKE_CURRENT_LIST_DIR}/view/keynavigationsection.h
${CMAKE_CURRENT_LIST_DIR}/view/keynavigationsubsection.cpp
${CMAKE_CURRENT_LIST_DIR}/view/keynavigationsubsection.h
${CMAKE_CURRENT_LIST_DIR}/dev/interactivetestsmodel.cpp
${CMAKE_CURRENT_LIST_DIR}/dev/interactivetestsmodel.h

View file

@ -20,8 +20,21 @@
#define MU_UI_IKEYNAVIGATIONSECTION_H
#include <QString>
#include <QList>
namespace mu::ui {
class IKeyNavigationSubSection
{
public:
virtual ~IKeyNavigationSubSection() = default;
virtual QString name() const = 0;
virtual int order() const = 0;
virtual bool enabled() const = 0;
virtual bool active() const = 0;
virtual void setActive(bool arg) = 0;
};
class IKeyNavigationSection
{
public:
@ -32,6 +45,8 @@ public:
virtual bool enabled() const = 0;
virtual bool active() const = 0;
virtual void setActive(bool arg) = 0;
virtual const QList<IKeyNavigationSubSection*>& subsections() const = 0;
};
}

View file

@ -24,168 +24,301 @@
using namespace mu::ui;
void KeyNavigationController::init()
// algorithms
template<class T>
static T* findFirstEnabled(typename QList<T*>::const_iterator it, typename QList<T*>::const_iterator end)
{
dispatcher()->reg(this, "nav-next-section", this, &KeyNavigationController::nextSection);
dispatcher()->reg(this, "nav-prev-section", this, &KeyNavigationController::prevSection);
}
static void print(const std::string& title, const std::vector<IKeyNavigationSection*>& chain)
{
LOGI() << title;
for (size_t i = 0; i < chain.size(); ++i) {
LOGI() << " " << i << chain.at(i)->name() << " order: " << chain.at(i)->order();
for (; it != end; ++it) {
T* s = *it;
if (s->enabled()) {
return s;
}
}
void KeyNavigationController::reg(IKeyNavigationSection* s)
{
//! TODO add check on valid state
m_chain.push_back(s);
print("after push_back", m_chain);
std::sort(m_chain.begin(), m_chain.end(), [this](const IKeyNavigationSection* f, const IKeyNavigationSection* s) {
return f->order() < s->order();
});
print("after sort", m_chain);
}
void KeyNavigationController::unreg(IKeyNavigationSection* s)
{
m_chain.erase(std::remove(m_chain.begin(), m_chain.end(), s), m_chain.end());
}
IKeyNavigationSection* KeyNavigationController::firstSection() const
{
if (!m_chain.empty()) {
return findFirstEnabledSection(m_chain.cbegin(), m_chain.cend());
}
return nullptr;
}
IKeyNavigationSection* KeyNavigationController::lastSection() const
template<class T>
static T* findLastEnabled(typename QList<T*>::const_iterator it, typename QList<T*>::const_iterator begin)
{
if (!m_chain.empty()) {
auto last = --m_chain.cend();
return findLastEnabledSection(last, m_chain.cbegin());
for (; it != begin; --it) {
T* s = *it;
if (s->enabled()) {
return s;
}
}
T* s = *begin;
if (s->enabled()) {
return s;
}
return nullptr;
}
IKeyNavigationSection* KeyNavigationController::nextSection(const IKeyNavigationSection* s) const
template<class T>
static T* firstEnabled(const QList<T*>& list)
{
auto it = std::find(m_chain.begin(), m_chain.end(), s);
IF_ASSERT_FAILED(it != m_chain.end()) {
if (list.empty()) {
return nullptr;
}
return findFirstEnabled<T>(list.cbegin(), list.cend());
}
template<class T>
static T* lastEnabled(const QList<T*>& list)
{
if (list.empty()) {
return nullptr;
}
auto last = --list.cend();
return findLastEnabled<T>(last, list.cbegin());
}
template<class T>
static T* nextEnabled(const QList<T*>& list, const T* s)
{
auto it = std::find(list.begin(), list.end(), s);
IF_ASSERT_FAILED(it != list.end()) {
return nullptr;
}
++it;
if (it == m_chain.end()) {
if (it == list.end()) {
return nullptr;
}
return findFirstEnabledSection(it, m_chain.end());
return findFirstEnabled<T>(it, list.end());
}
IKeyNavigationSection* KeyNavigationController::prevSection(const IKeyNavigationSection* s) const
template<class T>
static T* prevEnabled(const QList<T*>& list, const T* s)
{
auto it = std::find(m_chain.begin(), m_chain.end(), s);
IF_ASSERT_FAILED(it != m_chain.end()) {
auto it = std::find(list.begin(), list.end(), s);
IF_ASSERT_FAILED(it != list.end()) {
return nullptr;
}
if (it == m_chain.begin()) {
if (it == list.begin()) {
return nullptr;
}
--it;
return findLastEnabledSection(it, m_chain.cbegin());
return findLastEnabled<T>(it, list.cbegin());
}
IKeyNavigationSection* KeyNavigationController::findFirstEnabledSection(Chain::const_iterator it, Chain::const_iterator end) const
template<class T>
static T* findActive(const QList<T*>& list)
{
for (; it != end; ++it) {
IKeyNavigationSection* s = *it;
if (s->enabled()) {
return s;
}
}
return nullptr;
}
IKeyNavigationSection* KeyNavigationController::findLastEnabledSection(Chain::const_iterator it, Chain::const_iterator begin) const
{
for (; it != begin; --it) {
IKeyNavigationSection* s = *it;
if (s->enabled()) {
return s;
}
}
IKeyNavigationSection* s = *begin;
if (s->enabled()) {
return s;
}
return nullptr;
}
IKeyNavigationSection* KeyNavigationController::activeSection() const
{
auto it = std::find_if(m_chain.cbegin(), m_chain.cend(), [this](const IKeyNavigationSection* s) {
auto it = std::find_if(list.cbegin(), list.cend(), [](const T* s) {
return s->active();
});
if (it != m_chain.cend()) {
if (it != list.cend()) {
return *it;
}
return nullptr;
}
void KeyNavigationController::init()
{
dispatcher()->reg(this, "nav-next-section", this, &KeyNavigationController::nextSection);
dispatcher()->reg(this, "nav-prev-section", this, &KeyNavigationController::prevSection);
dispatcher()->reg(this, "nav-next-subsection", this, &KeyNavigationController::nextSubSection);
dispatcher()->reg(this, "nav-prev-subsection", this, &KeyNavigationController::prevSubSection);
}
void KeyNavigationController::reg(IKeyNavigationSection* s)
{
//! TODO add check on valid state
m_sections.push_back(s);
std::sort(m_sections.begin(), m_sections.end(), [this](const IKeyNavigationSection* f, const IKeyNavigationSection* s) {
return f->order() < s->order();
});
}
void KeyNavigationController::unreg(IKeyNavigationSection* s)
{
m_sections.erase(std::remove(m_sections.begin(), m_sections.end(), s), m_sections.end());
}
void KeyNavigationController::activateSection(IKeyNavigationSection* s)
{
IF_ASSERT_FAILED(s) {
return;
}
for (IKeyNavigationSubSection* sub : s->subsections()) {
sub->setActive(false);
}
s->setActive(true);
IKeyNavigationSubSection* firstSub = firstEnabled(s->subsections());
if (firstSub) {
firstSub->setActive(true);
}
}
void KeyNavigationController::deactivateSection(IKeyNavigationSection* s)
{
IF_ASSERT_FAILED(s) {
return;
}
for (IKeyNavigationSubSection* sub : s->subsections()) {
sub->setActive(false);
}
s->setActive(false);
}
void KeyNavigationController::nextSection()
{
LOGI() << "====";
if (m_chain.empty()) {
if (m_sections.empty()) {
return;
}
IKeyNavigationSection* activeSec = activeSection();
IKeyNavigationSection* activeSec = findActive(m_sections);
if (!activeSec) { // no any active
firstSection()->setActive(true);
IKeyNavigationSection* first = firstEnabled(m_sections);
if (first) {
activateSection(first);
}
return;
}
activeSec->setActive(false);
deactivateSection(activeSec);
IKeyNavigationSection* nextSec = nextSection(activeSec);
IKeyNavigationSection* nextSec = nextEnabled(m_sections, activeSec);
if (!nextSec) { // active is last
firstSection()->setActive(true);
IKeyNavigationSection* first = firstEnabled(m_sections);
if (first) {
activateSection(first);
}
return;
}
nextSec->setActive(true);
activateSection(nextSec);
}
void KeyNavigationController::prevSection()
{
LOGI() << "====";
if (m_chain.empty()) {
if (m_sections.empty()) {
return;
}
IKeyNavigationSection* activeSec = activeSection();
IKeyNavigationSection* activeSec = findActive(m_sections);
if (!activeSec) { // no any active
lastSection()->setActive(true);
IKeyNavigationSection* last = lastEnabled(m_sections);
if (last) {
activateSection(last);
}
return;
}
activeSec->setActive(false);
deactivateSection(activeSec);
IKeyNavigationSection* prevSec = prevSection(activeSec);
IKeyNavigationSection* prevSec = prevEnabled(m_sections, activeSec);
if (!prevSec) { // active is first
lastSection()->setActive(true);
IKeyNavigationSection* last = lastEnabled(m_sections);
if (last) {
activateSection(last);
}
return;
}
prevSec->setActive(true);
activateSection(prevSec);
}
const QList<IKeyNavigationSubSection*>& KeyNavigationController::subsectionsOfActiveSection(bool doActiveIfNoAnyActive) const
{
static const QList<IKeyNavigationSubSection*> null;
if (m_sections.empty()) {
return null;
}
IKeyNavigationSection* activeSec = findActive(m_sections);
if (!activeSec) { // no any active
if (!doActiveIfNoAnyActive) {
return null;
}
activeSec = firstEnabled(m_sections);
if (!activeSec) {
return null;
}
activeSec->setActive(true);
}
return activeSec->subsections();
}
void KeyNavigationController::nextSubSection()
{
LOGI() << "====";
const QList<IKeyNavigationSubSection*>& subsections = subsectionsOfActiveSection();
if (subsections.isEmpty()) {
return;
}
IKeyNavigationSubSection* activeSubSec = findActive(subsections);
if (!activeSubSec) { // no any active
IKeyNavigationSubSection* firstSub = firstEnabled(subsections);
if (firstSub) {
firstSub->setActive(true);
}
return;
}
activeSubSec->setActive(false);
IKeyNavigationSubSection* nextSubSec = nextEnabled(subsections, activeSubSec);
if (!nextSubSec) { // active is last
IKeyNavigationSubSection* firstSub = firstEnabled(subsections);
if (firstSub) {
firstSub->setActive(true);
}
return;
}
nextSubSec->setActive(true);
}
void KeyNavigationController::prevSubSection()
{
LOGI() << "====";
const QList<IKeyNavigationSubSection*>& subsections = subsectionsOfActiveSection();
if (subsections.isEmpty()) {
return;
}
IKeyNavigationSubSection* activeSubSec = findActive(subsections);
if (!activeSubSec) { // no any active
IKeyNavigationSubSection* lastSub = lastEnabled(subsections);
if (lastSub) {
lastSub->setActive(true);
}
return;
}
activeSubSec->setActive(false);
IKeyNavigationSubSection* prevSubSec = prevEnabled(subsections, activeSubSec);
if (!prevSubSec) { // active is first
IKeyNavigationSubSection* lastSub = lastEnabled(subsections);
if (lastSub) {
lastSub->setActive(true);
}
return;
}
prevSubSec->setActive(true);
}

View file

@ -19,7 +19,7 @@
#ifndef MU_UI_KEYNAVIGATIONCONTROLLER_H
#define MU_UI_KEYNAVIGATIONCONTROLLER_H
#include <vector>
#include <QList>
#include "../ikeynavigationcontroller.h"
#include "modularity/ioc.h"
@ -41,18 +41,15 @@ public:
private:
void nextSection();
void prevSection();
void nextSubSection();
void prevSubSection();
using Chain = std::vector<IKeyNavigationSection*>;
void activateSection(IKeyNavigationSection* s);
void deactivateSection(IKeyNavigationSection* s);
IKeyNavigationSection* firstSection() const;
IKeyNavigationSection* lastSection() const;
IKeyNavigationSection* nextSection(const IKeyNavigationSection* s) const;
IKeyNavigationSection* prevSection(const IKeyNavigationSection* s) const;
IKeyNavigationSection* findFirstEnabledSection(Chain::const_iterator from, Chain::const_iterator end) const;
IKeyNavigationSection* findLastEnabledSection(Chain::const_iterator from, Chain::const_iterator begin) const;
IKeyNavigationSection* activeSection() const;
const QList<IKeyNavigationSubSection*>& subsectionsOfActiveSection(bool doActiveIfNoAnyActive = true) const;
Chain m_chain;
QList<IKeyNavigationSection*> m_sections;
};
}

View file

@ -30,6 +30,12 @@ const UiActionList KeyNavigationUiActions::m_actions = {
UiAction("nav-prev-section",
mu::context::UiCtxAny
),
UiAction("nav-next-subsection",
mu::context::UiCtxAny
),
UiAction("nav-prev-subsection",
mu::context::UiCtxAny
)
};
const UiActionList& KeyNavigationUiActions::actionsList() const

View file

@ -24,6 +24,7 @@
#include "view/musicalsymbolcodes.h"
#include "view/qmldialog.h"
#include "view/keynavigationsection.h"
#include "view/keynavigationsubsection.h"
#include "dev/interactivetestsmodel.h"
#include "dev/testdialog.h"
@ -99,6 +100,7 @@ void UiModule::registerUiTypes()
qmlRegisterType<QmlDialog>("MuseScore.Ui", 1, 0, "QmlDialog");
qmlRegisterType<KeyNavigationSection>("MuseScore.Ui", 1, 0, "KeyNavigationSection");
qmlRegisterType<KeyNavigationSubSection>("MuseScore.Ui", 1, 0, "KeyNavigationSubSection");
qmlRegisterType<InteractiveTestsModel>("MuseScore.Ui", 1, 0, "InteractiveTestsModel");
qRegisterMetaType<TestDialog>("TestDialog");

View file

@ -34,12 +34,7 @@ KeyNavigationSection::~KeyNavigationSection()
void KeyNavigationSection::setName(QString name)
{
if (m_name == name) {
return;
}
m_name = name;
emit nameChanged(m_name);
}
QString KeyNavigationSection::name() const
@ -49,12 +44,7 @@ QString KeyNavigationSection::name() const
void KeyNavigationSection::setOrder(int order)
{
if (m_order == order) {
return;
}
m_order = order;
emit orderChanged(m_order);
}
int KeyNavigationSection::order() const
@ -100,5 +90,24 @@ void KeyNavigationSection::componentComplete()
{
//! NOTE Reg after set properties.
LOGD() << "Completed: " << m_name << ", order: " << m_order;
IF_ASSERT_FAILED(!m_name.isEmpty()) {
return;
}
IF_ASSERT_FAILED(m_order > -1) {
return;
}
keyNavigationController()->reg(this);
}
void KeyNavigationSection::addSubSection(IKeyNavigationSubSection* s)
{
m_subsections.append(s);
}
const QList<IKeyNavigationSubSection*>& KeyNavigationSection::subsections() const
{
return m_subsections;
}

View file

@ -21,18 +21,21 @@
#include <QObject>
#include <QQmlParserStatus>
#include <QList>
#include "../ikeynavigationsection.h"
#include "modularity/ioc.h"
#include "../ikeynavigationcontroller.h"
namespace mu::ui {
class KeyNavigationSubSection;
class KeyNavigationSection : public QObject, public IKeyNavigationSection, public QQmlParserStatus
{
Q_OBJECT
Q_INTERFACES(QQmlParserStatus)
Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
Q_PROPERTY(int order READ order WRITE setOrder NOTIFY orderChanged)
Q_PROPERTY(QString name READ name WRITE setName)
Q_PROPERTY(int order READ order WRITE setOrder)
Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged)
Q_PROPERTY(bool active READ active NOTIFY activeChanged)
@ -48,27 +51,30 @@ public:
bool enabled() const override;
bool active() const override;
void setActive(bool active) override;
const QList<IKeyNavigationSubSection*>& subsections() const override;
// QQmlParserStatus
void classBegin() override;
void componentComplete() override;
void addSubSection(IKeyNavigationSubSection* s);
public slots:
void setName(QString name);
void setOrder(int order);
void setEnabled(bool enabled);
signals:
void nameChanged(QString name);
void activeChanged(bool active);
void orderChanged(int order);
void enabledChanged(bool enabled);
void activeChanged(bool active);
private:
QString m_name;
int m_order = -1;
bool m_active = false;
bool m_enabled = true;
QList<IKeyNavigationSubSection*> m_subsections;
};
}

View file

@ -0,0 +1,109 @@
//=============================================================================
// MuseScore
// Music Composition & Notation
//
// Copyright (C) 2021 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 2.
//
// 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, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//=============================================================================
#include "keynavigationsubsection.h"
#include "log.h"
using namespace mu::ui;
KeyNavigationSubSection::KeyNavigationSubSection(QObject* parent)
: QObject(parent)
{
}
KeyNavigationSection* KeyNavigationSubSection::section() const
{
return m_section;
}
void KeyNavigationSubSection::setSection(KeyNavigationSection* section)
{
m_section = section;
if (m_section) {
m_section->addSubSection(this);
}
}
void KeyNavigationSubSection::setName(QString name)
{
m_name = name;
}
QString KeyNavigationSubSection::name() const
{
return m_name;
}
void KeyNavigationSubSection::setOrder(int order)
{
m_order = order;
}
int KeyNavigationSubSection::order() const
{
return m_order;
}
void KeyNavigationSubSection::setEnabled(bool enabled)
{
if (m_enabled == enabled) {
return;
}
m_enabled = enabled;
emit enabledChanged(m_enabled);
}
bool KeyNavigationSubSection::enabled() const
{
return m_enabled;
}
void KeyNavigationSubSection::setActive(bool active)
{
if (m_active == active) {
return;
}
m_active = active;
emit activeChanged(m_active);
}
bool KeyNavigationSubSection::active() const
{
return m_active;
}
void KeyNavigationSubSection::classBegin()
{
}
void KeyNavigationSubSection::componentComplete()
{
//! NOTE Reg after set properties.
LOGD() << "Completed: " << m_name << ", order: " << m_order;
IF_ASSERT_FAILED(!m_name.isEmpty()) {
return;
}
IF_ASSERT_FAILED(m_order > -1) {
return;
}
}

View file

@ -0,0 +1,74 @@
//=============================================================================
// MuseScore
// Music Composition & Notation
//
// Copyright (C) 2021 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 2.
//
// 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, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//=============================================================================
#ifndef MU_UI_KEYNAVIGATIONSUBSECTION_H
#define MU_UI_KEYNAVIGATIONSUBSECTION_H
#include <QObject>
#include <QQmlParserStatus>
#include "../ikeynavigationsection.h"
#include "keynavigationsection.h"
namespace mu::ui {
class KeyNavigationSubSection : public QObject, public IKeyNavigationSubSection, public QQmlParserStatus
{
Q_OBJECT
Q_INTERFACES(QQmlParserStatus)
Q_PROPERTY(KeyNavigationSection * section READ section WRITE setSection)
Q_PROPERTY(QString name READ name WRITE setName)
Q_PROPERTY(int order READ order WRITE setOrder)
Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged)
Q_PROPERTY(bool active READ active NOTIFY activeChanged)
public:
explicit KeyNavigationSubSection(QObject* parent = nullptr);
KeyNavigationSection* section() const;
QString name() const override;
int order() const override;
bool enabled() const override;
bool active() const override;
void setActive(bool active) override;
// QQmlParserStatus
void classBegin() override;
void componentComplete() override;
public slots:
void setSection(KeyNavigationSection* section);
void setName(QString name);
void setOrder(int order);
void setEnabled(bool enabled);
signals:
void enabledChanged(bool enabled);
void activeChanged(bool active);
private:
KeyNavigationSection* m_section = nullptr;
QString m_name;
int m_order = -1;
bool m_active = false;
bool m_enabled = true;
};
}
#endif // MU_UI_KEYNAVIGATIONSUBSECTION_H