Activate control navigation when the user interacts with the component

This commit is contained in:
Eism 2022-04-12 10:14:38 +03:00
parent c97f0707c6
commit d7084d5b03
24 changed files with 116 additions and 19 deletions

View file

@ -165,6 +165,8 @@ Dial {
}
onMoved: {
navigation.requestActiveByInteraction()
newValueRequested(value)
}

View file

@ -322,6 +322,8 @@ Slider {
}
onMoved: {
navigation.requestActiveByInteraction()
var newLevel = convertor.volumeLevelFromLocal(value)
root.volumeLevelMoved(newLevel)
}

View file

@ -39,7 +39,6 @@ class INavigationSection;
class INavigationPanel;
class INavigationControl;
using OnActiveRequested = std::function<void (INavigationSection* sec, INavigationPanel* panel, INavigationControl* ctrl)>;
class INavigation
{
public:
@ -85,6 +84,12 @@ public:
std::string to_string() const { return std::string("[") + std::to_string(row) + "," + std::to_string(column) + "]"; }
};
enum class ActivationType {
None,
ByKeyboard,
ByMouse
};
virtual QString name() const = 0;
virtual const Index& index() const = 0;
@ -110,6 +115,8 @@ public:
virtual void trigger() = 0;
virtual void requestActive() = 0;
virtual QWindow* window() const = 0;
};
class INavigationSection;
@ -129,9 +136,13 @@ public:
virtual Direction direction() const = 0;
virtual const std::set<INavigationControl*>& controls() const = 0;
virtual async::Notification controlsListChanged() const = 0;
virtual void requestActive(INavigationControl* control = nullptr) = 0;
virtual void requestActive(INavigationControl* control = nullptr,
INavigation::ActivationType activationType = INavigation::ActivationType::None) = 0;
};
using OnActiveRequested = std::function<void (INavigationSection* sec, INavigationPanel* panel, INavigationControl* ctrl,
INavigation::ActivationType activationType)>;
class INavigationSection : public INavigation
{
public:
@ -150,7 +161,8 @@ public:
virtual async::Notification panelsListChanged() const = 0;
virtual void setOnActiveRequested(const OnActiveRequested& func) = 0;
virtual void requestActive(INavigationPanel* panel = nullptr, INavigationControl* control = nullptr) = 0;
virtual void requestActive(INavigationPanel* panel = nullptr, INavigationControl* control = nullptr,
INavigation::ActivationType activationType = INavigation::ActivationType::None) = 0;
};
}

View file

@ -49,6 +49,7 @@ static const mu::UriQuery DEV_SHOW_CONTROLS_URI("musescore://devtools/keynav/con
using MoveDirection = NavigationController::MoveDirection;
using Event = INavigation::Event;
using ActivationType = INavigation::ActivationType;
// algorithms
template<class T>
@ -300,7 +301,15 @@ void NavigationController::reg(INavigationSection* section)
//! TODO add check on valid state
TRACEFUNC;
m_sections.insert(section);
section->setOnActiveRequested([this](INavigationSection* section, INavigationPanel* panel, INavigationControl* control) {
section->setOnActiveRequested([this](INavigationSection* section, INavigationPanel* panel, INavigationControl* control, ActivationType activationType) {
if (control && activationType == ActivationType::ByMouse) {
if (mainWindow()->qWindow() == control->window()) {
return;
}
m_isNavigatedByMouse = true;
}
onActiveRequested(section, panel, control);
});
}
@ -319,7 +328,7 @@ const std::set<INavigationSection*>& NavigationController::sections() const
bool NavigationController::isHighlight() const
{
return m_isNavigatedByKeyboard;
return m_isHighlight;
}
void NavigationController::setIsHighlight(bool isHighlight)
@ -354,6 +363,9 @@ void NavigationController::resetIfNeed(QObject* watched)
}
#endif
m_isNavigatedByKeyboard = false;
m_isNavigatedByMouse = false;
setIsHighlight(false);
}
@ -370,7 +382,7 @@ void NavigationController::navigateTo(NavigationController::NavigationType type)
{
//! NOTE We will assume that if a action was sent, then it was sent using the keyboard
//! (instead of an explicit request to activate the by clicking the mouse)
if (!m_isNavigatedByKeyboard) {
if (!m_isNavigatedByKeyboard && !m_isNavigatedByMouse) {
m_isNavigatedByKeyboard = true;
INavigationSection* activeSec = findActive(m_sections);
if (activeSec) {
@ -378,6 +390,8 @@ void NavigationController::navigateTo(NavigationController::NavigationType type)
}
}
m_isHighlight = true;
switch (type) {
case NavigationType::NextSection:
goToNextSection();

View file

@ -25,18 +25,21 @@
#include <QObject>
#include <QList>
#include "../inavigationcontroller.h"
#include "modularity/ioc.h"
#include "global/iinteractive.h"
#include "async/asyncable.h"
#include "ui/imainwindow.h"
#include "actions/iactionsdispatcher.h"
#include "actions/actionable.h"
#include "async/asyncable.h"
#include "global/iinteractive.h"
#include "../inavigationcontroller.h"
namespace mu::ui {
class NavigationController : public QObject, public INavigationController, public actions::Actionable, public async::Asyncable
{
INJECT(ui, actions::IActionsDispatcher, dispatcher)
INJECT(ui, framework::IInteractive, interactive)
INJECT(ui, IMainWindow, mainWindow)
public:
NavigationController() = default;
@ -133,7 +136,11 @@ private:
std::set<INavigationSection*> m_sections;
async::Notification m_navigationChanged;
async::Notification m_highlightChanged;
bool m_isHighlight = false;
bool m_isNavigatedByKeyboard = false;
bool m_isNavigatedByMouse = false;
bool m_isResetOnMousePress = true;
};
}

View file

@ -50,7 +50,7 @@ public:
MOCK_METHOD(async::Notification, panelsListChanged, (), (const, override));
MOCK_METHOD(void, setOnActiveRequested, (const OnActiveRequested& func), (override));
MOCK_METHOD(void, requestActive, (INavigationPanel*, INavigationControl*), (override));
MOCK_METHOD(void, requestActive, (INavigationPanel*, INavigationControl*, INavigation::ActivationType), (override));
};
class NavigationPanelMock : public INavigationPanel
@ -76,7 +76,7 @@ public:
MOCK_METHOD(const std::set<INavigationControl*>&, controls, (), (const, override));
MOCK_METHOD(async::Notification, controlsListChanged, (), (const, override));
MOCK_METHOD(void, requestActive, (INavigationControl*), (override));
MOCK_METHOD(void, requestActive, (INavigationControl*, INavigation::ActivationType), (override));
};
class NavigationControlMock : public INavigationControl
@ -101,6 +101,8 @@ public:
MOCK_METHOD(void, trigger, (), (override));
MOCK_METHOD(void, requestActive, (), (override));
MOCK_METHOD(QWindow*, window, (), (const, override));
};
}

View file

@ -94,6 +94,21 @@ void NavigationControl::trigger()
emit triggered();
}
QWindow* NavigationControl::window() const
{
QObject* prn = parent();
while (prn) {
QQuickItem* vitem = qobject_cast<QQuickItem*>(prn);
if (vitem) {
return vitem->window();
}
prn = prn->parent();
}
return nullptr;
}
void NavigationControl::requestActive()
{
if (m_panel) {
@ -101,6 +116,13 @@ void NavigationControl::requestActive()
}
}
void NavigationControl::requestActiveByInteraction()
{
if (m_panel) {
m_panel->requestActive(this, INavigation::ActivationType::ByMouse);
}
}
void NavigationControl::setPanel(NavigationPanel* panel)
{
TRACEFUNC;

View file

@ -57,7 +57,10 @@ public:
void trigger() override;
QWindow* window() const override;
Q_INVOKABLE void requestActive() override;
Q_INVOKABLE void requestActiveByInteraction();
public slots:
void setPanel(NavigationPanel* panel);

View file

@ -213,9 +213,9 @@ void NavigationPanel::removeControl(NavigationControl* control)
}
}
void NavigationPanel::requestActive(INavigationControl* control)
void NavigationPanel::requestActive(INavigationControl* control, INavigation::ActivationType activationType)
{
if (m_section) {
m_section->requestActive(this, control);
m_section->requestActive(this, control, activationType);
}
}

View file

@ -77,7 +77,7 @@ public:
void removeControl(NavigationControl* control);
//! NOTE Can be called from QML without args
Q_INVOKABLE void requestActive(INavigationControl* control = nullptr) override;
Q_INVOKABLE void requestActive(INavigationControl* control = nullptr, ActivationType activationType = ActivationType::None) override;
public slots:
void setSection_property(NavigationSection* section);

View file

@ -142,10 +142,10 @@ void NavigationSection::setOnActiveRequested(const OnActiveRequested& func)
m_onActiveRequested = func;
}
void NavigationSection::requestActive(INavigationPanel* panel, INavigationControl* control)
void NavigationSection::requestActive(INavigationPanel* panel, INavigationControl* control, INavigation::ActivationType activationType)
{
if (m_onActiveRequested) {
m_onActiveRequested(this, panel, control);
m_onActiveRequested(this, panel, control, activationType);
}
}

View file

@ -79,7 +79,8 @@ public:
void setOnActiveRequested(const OnActiveRequested& func) override;
//! NOTE Can be called from QML without args
Q_INVOKABLE void requestActive(INavigationPanel* panel = nullptr, INavigationControl* control = nullptr) override;
Q_INVOKABLE void requestActive(INavigationPanel* panel = nullptr, INavigationControl* control = nullptr,
ActivationType activationType = ActivationType::None) override;
public slots:
void setType(QmlType type);

View file

@ -121,6 +121,8 @@ FocusScope {
hoverEnabled: true
onClicked: {
navigation.requestActiveByInteraction()
root.clicked()
}
}

View file

@ -70,6 +70,8 @@ Rectangle {
anchors.fill: parent
hoverEnabled: true
onClicked: {
navigation.requestActiveByInteraction()
colorDialog.open()
}
}

View file

@ -104,6 +104,8 @@ FocusScope {
hoverEnabled: true
onClicked: {
navigation.requestActiveByInteraction()
root.isExpanded = !root.isExpanded
}
}

View file

@ -278,6 +278,8 @@ FocusScope {
hoverEnabled: true
onClicked: function(mouse) {
navigation.requestActiveByInteraction()
root.clicked(mouse)
}

View file

@ -59,7 +59,11 @@ RadioDelegate {
}
}
onClicked: root.ensureActiveFocus()
onClicked: {
navigation.requestActiveByInteraction()
root.ensureActiveFocus()
}
NavigationControl {
id: navCtrl

View file

@ -86,6 +86,8 @@ FocusScope {
hoverEnabled: true
onClicked: {
navigation.requestActiveByInteraction()
root.ensureActiveFocus()
root.toggled()
}

View file

@ -54,7 +54,11 @@ FocusableControl {
mouseArea.hoverEnabled: root.visible
mouseArea.onHoveredChanged: root.hovered(mouseArea.containsMouse, mouseArea.mouseX, mouseArea.mouseY)
mouseArea.onClicked: function(mouse) { root.clicked(mouse) }
mouseArea.onClicked: function(mouse) {
navigation.requestActiveByInteraction()
root.clicked(mouse)
}
mouseArea.onDoubleClicked: function(mouse) { root.doubleClicked(mouse) }
mouseArea.onContainsMouseChanged: {

View file

@ -164,6 +164,8 @@ FocusScope {
enabled: !textField.readOnly
onPressed: {
navigation.requestActiveByInteraction()
root.ensureActiveFocus()
textField.cursorPosition = textField.text.length
}

View file

@ -45,6 +45,10 @@ RadioDelegate {
leftPadding: 0
rightPadding: 0
onToggled: {
navigation.requestActiveByInteraction()
}
NavigationControl {
id: navCtrl
name: root.objectName

View file

@ -48,6 +48,10 @@ RadioDelegate {
hoverEnabled: true
onToggled: {
navigation.requestActiveByInteraction()
}
NavigationControl {
id: keynavCtrl
name: root.objectName != "" ? root.objectName : "RoundedRadioButton"

View file

@ -53,6 +53,8 @@ TabButton {
font: isCurrent ? ui.theme.largeBodyBoldFont : ui.theme.largeBodyFont
onPressed: {
navigation.requestActiveByInteraction()
root.ensureActiveFocus()
}

View file

@ -80,6 +80,8 @@ FocusScope {
onActiveFocusChanged: {
if (activeFocus) {
valueInput.forceActiveFocus()
navigation.requestActiveByInteraction()
}
}