Radial: dual drag/click interaction, open at cursor without moving caret
The radial now supports both intended interactions from one widget: - Hold-and-drag: opened with a button held, drag toward a slice and release to trigger the slice under the cursor (far flicks allowed). - Release-and-click: a quick press/release that never leaves the centre dead zone switches to click mode and keeps the menu open; then hover and click a slice to trigger. A click outside the ring cancels instead of snapping. Armed state is detected from QApplication::mouseButtons() at popup time, so key-triggered radials start directly in click mode. mousePressEvent is accepted to stop a Qt::Popup from self-dismissing on an in-widget press. The menu opens centred on the given point; the RMB press is consumed upstream so the caret does not move. All 5 ctest suites green.
This commit is contained in:
parent
4be3ead30d
commit
9ecfd379c2
|
|
@ -88,8 +88,12 @@ Therefore the palette is replaced, not extended, and the replacement is built on
|
|||
dead zone, far-flick snaps by angle, descend/ascend navigation. 14 tests.
|
||||
- `src/radial/radialmenu.{h,cpp}`: frameless translucent `QWidget`; paints the
|
||||
current ring (wedges, labels, branch ▸, central "back" hub), highlights the
|
||||
slice under the pointer, release/click activates a leaf, descends a branch, or
|
||||
ascends/cancels from the dead zone; Esc ascends/cancels.
|
||||
slice under the pointer. Two interaction modes from one widget: a **drag
|
||||
gesture** (opened with a button held — drag out and release to select; a
|
||||
release that never left the dead zone switches to click mode and keeps the
|
||||
menu open) and a **discrete click mode** (click a slice to select, click
|
||||
outside the ring to cancel). Esc ascends/cancels. Opens centred on the given
|
||||
point without moving the caret.
|
||||
- `src/radial/radialconfig.{h,cpp}`: JSON → radial tree parser (+ built-in
|
||||
default) so multiple radials can be bound to different triggers. 8 tests.
|
||||
- Wired into the plugin: `OllieView::setupRadials()` registers a key trigger
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
#include <QApplication>
|
||||
#include <QKeyEvent>
|
||||
#include <QLineF>
|
||||
#include <QMouseEvent>
|
||||
#include <QPainter>
|
||||
#include <QPainterPath>
|
||||
|
|
@ -38,6 +39,11 @@ void RadialMenu::popup(const RadialNode &root, const QPoint &globalCenter)
|
|||
{
|
||||
m_model = std::make_unique<RadialModel>(root);
|
||||
m_hoverSlice = RadialModel::InvalidSlice;
|
||||
// If a mouse button is held as we open (the RMB gesture), this is a
|
||||
// drag-gesture candidate: a release that drags out selects, a release in
|
||||
// place switches to discrete click mode. Opened from a key (no button) we
|
||||
// start directly in click mode.
|
||||
m_armed = (QApplication::mouseButtons() != Qt::NoButton);
|
||||
|
||||
// Size the widget to a square large enough for the outer ring plus labels,
|
||||
// positioned so the ring centre lands on globalCenter — clamped to the
|
||||
|
|
@ -149,17 +155,54 @@ void RadialMenu::mouseMoveEvent(QMouseEvent *event)
|
|||
}
|
||||
}
|
||||
|
||||
void RadialMenu::mouseReleaseEvent(QMouseEvent *event)
|
||||
void RadialMenu::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
handleActivationAt(event->position());
|
||||
// Accept presses so a Qt::Popup does not treat an in-widget press as an
|
||||
// outside click and dismiss itself. Selection happens on release.
|
||||
event->accept();
|
||||
}
|
||||
|
||||
void RadialMenu::handleActivationAt(const QPointF &localPos)
|
||||
void RadialMenu::mouseReleaseEvent(QMouseEvent *event)
|
||||
{
|
||||
if (!m_model) {
|
||||
return;
|
||||
}
|
||||
const int slice = m_model->sliceAt(localPos, currentLayout());
|
||||
const QPointF pos = event->position();
|
||||
|
||||
if (m_armed) {
|
||||
// End of the initial hold from the opening gesture.
|
||||
m_armed = false;
|
||||
const int slice = m_model->sliceAt(pos, currentLayout());
|
||||
if (slice == RadialModel::InvalidSlice) {
|
||||
// Released in place without dragging out: treat the gesture as a
|
||||
// click-to-open and stay open for discrete clicks.
|
||||
m_hoverSlice = RadialModel::InvalidSlice;
|
||||
update();
|
||||
return;
|
||||
}
|
||||
// Dragged out to a slice: select immediately (far flicks allowed).
|
||||
handleActivationAt(pos, /*allowFarFlick=*/true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Discrete click mode: require the click to land on the ring; a click
|
||||
// beyond the outer edge dismisses rather than snapping by angle.
|
||||
handleActivationAt(pos, /*allowFarFlick=*/false);
|
||||
}
|
||||
|
||||
void RadialMenu::handleActivationAt(const QPointF &localPos, bool allowFarFlick)
|
||||
{
|
||||
if (!m_model) {
|
||||
return;
|
||||
}
|
||||
int slice = m_model->sliceAt(localPos, currentLayout());
|
||||
if (!allowFarFlick && slice != RadialModel::InvalidSlice) {
|
||||
const double dist = QLineF(m_center, localPos).length();
|
||||
if (dist > m_outerRadius) {
|
||||
slice = RadialModel::InvalidSlice; // outside the ring => cancel/ascend
|
||||
}
|
||||
}
|
||||
|
||||
QString id;
|
||||
switch (m_model->resolve(slice, id)) {
|
||||
case RadialModel::Hit::Activate:
|
||||
|
|
|
|||
|
|
@ -3,11 +3,14 @@
|
|||
*
|
||||
* RadialMenu — a frameless, multi-level radial (pie) menu widget.
|
||||
*
|
||||
* Shows the current ring of a RadialModel centred on a screen point. The slice
|
||||
* under the pointer is highlighted; releasing over it activates a leaf (emits
|
||||
* activatedId) or descends into a branch. Releasing in the central dead zone
|
||||
* ascends one level, or cancels at the root. Works both as a gesture
|
||||
* (press-drag-release) and as discrete clicks.
|
||||
* Shows the current ring of a RadialModel centred on a screen point. Two
|
||||
* interaction modes are supported from a single widget:
|
||||
* - Drag gesture: opened with a mouse button held (the RMB gesture), the user
|
||||
* drags toward a slice and releases to select; a release that never left the
|
||||
* central dead zone instead switches to click mode (the menu stays open).
|
||||
* - Click mode: discrete hover + click. A click on a slice selects, a click
|
||||
* outside the ring cancels, the central hub goes back one level or cancels.
|
||||
* Esc ascends one level, or cancels at the root.
|
||||
*/
|
||||
#ifndef KATECUSTOM_RADIALMENU_H
|
||||
#define KATECUSTOM_RADIALMENU_H
|
||||
|
|
@ -41,19 +44,24 @@ Q_SIGNALS:
|
|||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
void mouseMoveEvent(QMouseEvent *event) override;
|
||||
void mouseReleaseEvent(QMouseEvent *event) override;
|
||||
void keyPressEvent(QKeyEvent *event) override;
|
||||
|
||||
private:
|
||||
RadialLayout currentLayout() const;
|
||||
void handleActivationAt(const QPointF &localPos);
|
||||
// allowFarFlick: when true, a point beyond the outer radius still selects
|
||||
// the slice in that direction (confident drag-flick). When false, such a
|
||||
// point is treated as "outside" and cancels/ascends (discrete click mode).
|
||||
void handleActivationAt(const QPointF &localPos, bool allowFarFlick);
|
||||
void finishActivated(const QString &id);
|
||||
void finishCancelled();
|
||||
|
||||
std::unique_ptr<RadialModel> m_model;
|
||||
QPointF m_center; // local widget coordinates of the ring centre
|
||||
int m_hoverSlice = RadialModel::InvalidSlice;
|
||||
bool m_armed = false; // opened with a button held => drag-gesture candidate
|
||||
|
||||
double m_innerRadius = 36.0;
|
||||
double m_outerRadius = 130.0;
|
||||
|
|
|
|||
Loading…
Reference in New Issue