Activate control navigation when the user interacts with the component
This commit is contained in:
parent
c97f0707c6
commit
d7084d5b03
24 changed files with 116 additions and 19 deletions
|
@ -165,6 +165,8 @@ Dial {
|
|||
}
|
||||
|
||||
onMoved: {
|
||||
navigation.requestActiveByInteraction()
|
||||
|
||||
newValueRequested(value)
|
||||
}
|
||||
|
||||
|
|
|
@ -322,6 +322,8 @@ Slider {
|
|||
}
|
||||
|
||||
onMoved: {
|
||||
navigation.requestActiveByInteraction()
|
||||
|
||||
var newLevel = convertor.volumeLevelFromLocal(value)
|
||||
root.volumeLevelMoved(newLevel)
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -121,6 +121,8 @@ FocusScope {
|
|||
hoverEnabled: true
|
||||
|
||||
onClicked: {
|
||||
navigation.requestActiveByInteraction()
|
||||
|
||||
root.clicked()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -70,6 +70,8 @@ Rectangle {
|
|||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked: {
|
||||
navigation.requestActiveByInteraction()
|
||||
|
||||
colorDialog.open()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -104,6 +104,8 @@ FocusScope {
|
|||
hoverEnabled: true
|
||||
|
||||
onClicked: {
|
||||
navigation.requestActiveByInteraction()
|
||||
|
||||
root.isExpanded = !root.isExpanded
|
||||
}
|
||||
}
|
||||
|
|
|
@ -278,6 +278,8 @@ FocusScope {
|
|||
hoverEnabled: true
|
||||
|
||||
onClicked: function(mouse) {
|
||||
navigation.requestActiveByInteraction()
|
||||
|
||||
root.clicked(mouse)
|
||||
}
|
||||
|
||||
|
|
|
@ -59,7 +59,11 @@ RadioDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
onClicked: root.ensureActiveFocus()
|
||||
onClicked: {
|
||||
navigation.requestActiveByInteraction()
|
||||
|
||||
root.ensureActiveFocus()
|
||||
}
|
||||
|
||||
NavigationControl {
|
||||
id: navCtrl
|
||||
|
|
|
@ -86,6 +86,8 @@ FocusScope {
|
|||
|
||||
hoverEnabled: true
|
||||
onClicked: {
|
||||
navigation.requestActiveByInteraction()
|
||||
|
||||
root.ensureActiveFocus()
|
||||
root.toggled()
|
||||
}
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -164,6 +164,8 @@ FocusScope {
|
|||
enabled: !textField.readOnly
|
||||
|
||||
onPressed: {
|
||||
navigation.requestActiveByInteraction()
|
||||
|
||||
root.ensureActiveFocus()
|
||||
textField.cursorPosition = textField.text.length
|
||||
}
|
||||
|
|
|
@ -45,6 +45,10 @@ RadioDelegate {
|
|||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
|
||||
onToggled: {
|
||||
navigation.requestActiveByInteraction()
|
||||
}
|
||||
|
||||
NavigationControl {
|
||||
id: navCtrl
|
||||
name: root.objectName
|
||||
|
|
|
@ -48,6 +48,10 @@ RadioDelegate {
|
|||
|
||||
hoverEnabled: true
|
||||
|
||||
onToggled: {
|
||||
navigation.requestActiveByInteraction()
|
||||
}
|
||||
|
||||
NavigationControl {
|
||||
id: keynavCtrl
|
||||
name: root.objectName != "" ? root.objectName : "RoundedRadioButton"
|
||||
|
|
|
@ -53,6 +53,8 @@ TabButton {
|
|||
font: isCurrent ? ui.theme.largeBodyBoldFont : ui.theme.largeBodyFont
|
||||
|
||||
onPressed: {
|
||||
navigation.requestActiveByInteraction()
|
||||
|
||||
root.ensureActiveFocus()
|
||||
}
|
||||
|
||||
|
|
|
@ -80,6 +80,8 @@ FocusScope {
|
|||
onActiveFocusChanged: {
|
||||
if (activeFocus) {
|
||||
valueInput.forceActiveFocus()
|
||||
|
||||
navigation.requestActiveByInteraction()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue