-
Notifications
You must be signed in to change notification settings - Fork 2.7k
UI Actions
UiActions
are actions that the user performs.
The main difference between Actions
and UiActions
is that Actions
are like a command or method call, which may will call in the any time, of technically possible, while UiActions
are a description of an action for the user (title, icon, type, etc.) and user interaction logic ( the availability of the action for the user at the moment, the check state of the action, etc.)
Each module declares its own UiActions
, the typical class name with declarations is ModuleUiActions
(for example NotationsUiActions
).
Example:
const UiActionList NotationUiActions::m_actions = {
UiAction("next-element",
mu::context::UiCtxNotationOpened,
mu::context::CTX_NOTATION_FOCUSED,
TranslatableString("action", "Next element"),
TranslatableString("action", "Select next element in score")
),
UiAction("prev-element",
mu::context::UiCtxNotationOpened,
mu::context::CTX_NOTATION_FOCUSED,
TranslatableString("action", "Previous element"),
TranslatableString("action", "Select previous element in score")
),
...
UiAction("add-braces",
mu::context::UiCtxNotationOpened,
mu::context::CTX_NOTATION_OPENED,
TranslatableString("action", "Add braces to element"),
TranslatableString("action", "Add braces to element"),
IconCode::Code::BRACE
),
UiAction("add-parentheses",
mu::context::UiCtxNotationOpened,
mu::context::CTX_ANY,
TranslatableString("action", "Add parentheses to element"),
TranslatableString("action", "Add parentheses to element"),
IconCode::Code::BRACKET_PARENTHESES
),
...
UiAction("note-input-steptime",
mu::context::UiCtxNotationOpened,
mu::context::CTX_NOTATION_OPENED,
TranslatableString("action", "Default (step time)"),
TranslatableString("action", "Note input: toggle ‘default (step-time)’ mode"),
IconCode::Code::EDIT
),
When declaring UiActions
can be specified:
- action code - action which will be dispatched on activation
- uiCtx- UI Context for which action is available (see UI Context)
- scCtx- Shortcut Context for the shortcut which is assigned to this action
- title - usually used for tooltips title
- description - usually used for tooltips and the text that is displayed in the preferences listing
- icon (code) - usually used for menu items and toolbutton icon
- checkable - usually used for menu items
All UiActions
must be registered in the UiActions Register
. Through this register, clients getting access to UiActions
and their state, so UiActions Register
- this is global register for all UiActions
.
Registration takes place in the module setup
...
static std::shared_ptr<NotationActionController> s_actionController = std::make_shared<NotationActionController>();
static std::shared_ptr<NotationUiActions> s_notationUiActions = std::make_shared<NotationUiActions>(s_actionController);
...
void NotationModule::resolveImports()
{
auto ar = ioc()->resolve<IUiActionsRegister>(moduleName());
if (ar) {
ar->reg(s_notationUiActions);
}
...
}
void NotationModule::onInit(const IApplication::RunMode&)
{
...
s_actionController->init();
s_notationUiActions->init();
...
}
To be able to register in a register, the class must inherit and implement the ui::IUiActionsModule
interface.
class NotationUiActions : public ui::IUiActionsModule
{
public:
const ui::UiActionList& actionsList() const override;
bool actionEnabled(const ui::UiAction& act) const override;
async::Channel<actions::ActionCodeList> actionEnabledChanged() const override;
bool actionChecked(const ui::UiAction& act) const override;
async::Channel<actions::ActionCodeList> actionCheckedChanged() const override;
}
Clients (for example, menu or toolbar models) can get UiAction
from the register
const UiAction& action = uiactionsRegister()->action("select-workspace");
It is also possible to get the state of the UiAction
and subscibe on states changes
const UiActionState& state = uiactionsRegister()->actionState("select-workspace");
uiactionsRegister()->actionStateChanged().onReceive(this, [this](const ActionCodeList& codes) {
...
});
Each UiAction
action has a state:
- enabled - the action is available to the user
- checked - action checked (only for checkable actions)
The availability of an action for the user is determined by the technical ability to perform the action and the logical one.
For example, there is an action "insert measure", technically, the ability to perform the action depends on whether the notation element is currently selected (to know where to insert the measure).
And logically for users, it also depends on where the users are now and what they are doing (for example, on which screen). For example, if we are on the notation screen (and the current element is selected), then the action "insert measure" is available for the user (for example, from a menu item). If we switch to the Home screen, then technically it is still possible to insert measure (the notation is loaded, the element is selected, it is just not visible), but it is illogical for the user to insert measures while on the Home screen, so the action should not be available to the user (for example, from the menu item).
When the state of the UiAction
changes, there must be a notification about the change so that the user controls can be updated (for example, menu items, buttons on the toolbar, etc.)
Since conditions of so many UiActions
depend on the UI Context
, the condition of the current UI Context
is checked centrally in the UiActionsRegister
, for this it uses UiContextResolver
(see UI Context).
...
if (!ctxResolver->match(currentCtx, action.context)) {
state.enabled = false;
}
...
Other logical conditions (if they present) are checked in ModuleUiActions
, for this UiActionsRegister
calling the actionEnabled
method. In this method also checks the technical conditions, usually with the ActionController
(see Actions)
bool NotationUiActions::actionEnabled(const UiAction& act) const
{
if (!m_controller->canReceiveAction(act.code)) { // check technical conditions
return false;
}
// can be check some logic conditions, if they present
return true;
}
For notification of a change in the state of UiActions
, the register listens for a change in the current UI Context
, as well as a notification from ModuleUiActions
...
uicontextResolver()->currentUiContextChanged().onNotify(this, [this]() {
updateEnabledAll();
});
...
module->actionEnabledChanged().onReceive(this, [this](const ActionCodeList& codes) {
updateEnabled(codes);
m_actionStateChanged.send(codes);
});
module->actionCheckedChanged().onReceive(this, [this](const ActionCodeList& codes) {
updateChecked(codes);
m_actionStateChanged.send(codes);
});
...
To understand where the users is now and what they are doing, we introduced the concept of UI Context
.
The UI Context
is mainly used to determine what UiActions
are currently available, and it is also of primary importance for determining what action should be performed when shortcuts are activated, if several actions are assigned to one shortcut for different contexts.
The list of current contexts is in the context/uicontext.h
file
namespace mu::context {
//! NOTE Determines where to be, what the user is doing
// common ui (re declared for convenience)
static constexpr ui::UiContext UiCtxUnknown = ui::UiCtxUnknown;
static constexpr ui::UiContext UiCtxAny = ui::UiCtxAny;
// screens
static constexpr ui::UiContext UiCtxNotationOpened = "UiCtxNotationOpened";
static constexpr ui::UiContext UiCtxHomeOpened = "UiCtxHomeOpened";
// common
static constexpr ui::UiContext UiCtxPlaying= "UiCtxPlaying";
// notation detail
static constexpr ui::UiContext UiCtxNotationHasSelection = "UiCtxNotationHasSelection";
static constexpr ui::UiContext UiCtxNotationTextEditing = "UiCtxNotationTextEditing";
}
This list can be expanded as needed.
The logic for determining the current UI Context
is in the context::UiContextResolver
class (located in the context module), which implements the ui::IUiContextResolver
interface (located in the ui module).
With this interface, we can get the current UI Context
ui::UiContext uicontext = uicontextResolver()->currentUiContext();
And also context resolver contains the logic for matching contexts (it is more complex than just comparison)
if (!ctxResolver->match(currentCtx, action.context)) {
state.enabled = false;
}
And notifies about the change in the ui context
uicontextResolver()->currentUiContextChanged().onNotify(this, [this]() {
...
});
For each UiAction
can be set a UI Context
in which the action is logically available. At the moment, we can set only one value, perhaps in the future, if necessary, we can modify and set several values with the conditions or
and and
.
Testing
- Manual testing
- Automatic testing
Translation
Compilation
- Set up developer environment
- Install Qt and Qt Creator
- Get MuseScore's source code
- Install dependencies
- Compile on the command line
- Compile in Qt Creator
Beyond compiling
Misc. development
Architecture general
- Architecture overview
- AppShell
- Modularity
- Interact workflow
- Channels and Notifications
- Settings and Configuration
- Error handling
- Launcher and Interactive
- Keyboard Navigation
Audio
Engraving
- Style settings
- Working with style files
- Style parameter changes for 4.0
- Style parameter changes for 4.1
- Style parameter changes for 4.2
- Style parameter changes for 4.3
- Style parameter changes for 4.4
Extensions
- Extensions overview
- Manifest
- Forms
- Macros
- Api
- Legacy plugin API
Google Summer of Code
References