Render and handle speaker, radio call, and phone buttons in desktop member rows
This commit is contained in:
1 parent
dad819282a
commit
a8c2ce8401
2 files changed
+75
-47
No files matched your search
@@ -15,12 +15,27 @@ extern "C" {
|
||||
#include "chat/chat_member.h"
|
||||
}
|
||||
|
||||
static QRect inviteButtonRect(const QRect& row) {
|
||||
return QRect(row.right() - 31, row.center().y() - 14, 28, 28);
|
||||
QRect AccountDelegate::actionRect(const QRect& row, Action action) {
|
||||
const int offset = action == Action::RadioMute ? 95 : action == Action::RadioCall ? 63 : 31;
|
||||
return QRect(row.right() - offset, row.center().y() - 14, 28, 28);
|
||||
}
|
||||
|
||||
static QRect radioMuteButtonRect(const QRect& row) {
|
||||
return QRect(row.right() - 63, row.center().y() - 14, 28, 28);
|
||||
/* Трубка в той же системе координат 24×24, что и портативная рация. */
|
||||
static void drawCallButton(QPainter* painter, const QRect& button, const QColor& color) {
|
||||
painter->save();
|
||||
painter->setRenderHint(QPainter::Antialiasing);
|
||||
painter->translate(button.center().x() - 12, button.center().y() - 12);
|
||||
QPainterPath phone;
|
||||
phone.moveTo(6.6, 10.8); phone.cubicTo(8.1, 13.6, 10.4, 15.9, 13.2, 17.4);
|
||||
phone.lineTo(15.4, 15.2); phone.quadTo(15.8, 14.8, 16.3, 15);
|
||||
phone.quadTo(18.1, 15.6, 20, 15.6); phone.quadTo(21, 15.6, 21, 16.6);
|
||||
phone.lineTo(21, 20); phone.quadTo(21, 21, 20, 21);
|
||||
phone.cubicTo(10.6, 21, 3, 13.4, 3, 4); phone.quadTo(3, 3, 4, 3);
|
||||
phone.lineTo(7.4, 3); phone.quadTo(8.4, 3, 8.4, 4);
|
||||
phone.quadTo(8.4, 5.9, 9, 7.7); phone.quadTo(9.2, 8.2, 8.8, 8.6);
|
||||
phone.closeSubpath();
|
||||
painter->fillPath(phone, color);
|
||||
painter->restore();
|
||||
}
|
||||
|
||||
// Волны показывают передачу независимо от локального mute; перечёркивание сохраняется при речи.
|
||||
@@ -47,6 +62,13 @@ static QRect avatarRect(const QRect& row) {
|
||||
return QRect(row.left() + 4, row.top() + (row.height() - size) / 2, size, size);
|
||||
}
|
||||
|
||||
AccountDelegate::Action AccountDelegate::actionAt(const QPoint& position, const QRect& row, bool self) {
|
||||
if (avatarRect(row).contains(position)) return Action::Avatar;
|
||||
if (!self) for (Action action : {Action::RadioMute, Action::RadioCall, Action::Call})
|
||||
if (actionRect(row, action).contains(position)) return action;
|
||||
return Action::None;
|
||||
}
|
||||
|
||||
AccountDelegate::AccountDelegate(QObject *parent)
|
||||
: QStyledItemDelegate(parent) {
|
||||
GUI_DEBUG("AccountDelegate: row height=%dpx avatar=%dpx gap=%dpx", MemberListModel::kRowHeight,
|
||||
@@ -72,8 +94,8 @@ void AccountDelegate::paint(QPainter *painter, const QStyleOptionViewItem &optio
|
||||
icon.paint(painter, avatarRect(option.rect));
|
||||
|
||||
const int x = option.rect.left() + iconSz + margin * 3;
|
||||
const bool canInvite = !index.data(Qt::UserRole + 2).toBool();
|
||||
const int w = option.rect.right() - x - margin - 64;
|
||||
const bool self = index.data(Qt::UserRole + 2).toBool();
|
||||
const int w = qMax(0, option.rect.right() - x - margin - (self ? 32 : 96));
|
||||
const int yMid = option.rect.top() + option.rect.height() / 2;
|
||||
|
||||
uint8_t flags = (uint8_t)(index.data(Qt::UserRole + 3).toInt() & 0xFF);
|
||||
@@ -89,6 +111,8 @@ void AccountDelegate::paint(QPainter *painter, const QStyleOptionViewItem &optio
|
||||
if (index.data(Qt::UserRole + 2).toBool()) textColor = QColor("#4CAF50");
|
||||
painter->setPen(textColor);
|
||||
|
||||
painter->save();
|
||||
painter->setClipRect(QRect(x, option.rect.top(), w, option.rect.height()), Qt::IntersectClip);
|
||||
const uint8_t visualFlags = flags & (MEMBER_FLAG_SUPERNODE | MEMBER_FLAG_ADMIN | MEMBER_FLAG_MODER);
|
||||
int flagX = x;
|
||||
if (visualFlags) {
|
||||
@@ -121,13 +145,15 @@ void AccountDelegate::paint(QPainter *painter, const QStyleOptionViewItem &optio
|
||||
painter->drawText(QRect(flagX, yMid + 2, w - (flagX - x), option.rect.bottom() - yMid - 2),
|
||||
Qt::AlignLeft | Qt::AlignTop, sub);
|
||||
|
||||
drawRadioMuteButton(painter, radioMuteButtonRect(option.rect), flags & CHAT_MEMBER_FLAG_RADIO_MUTED,
|
||||
painter->restore();
|
||||
drawRadioMuteButton(painter, actionRect(option.rect, self ? Action::Call : Action::RadioMute), flags & CHAT_MEMBER_FLAG_RADIO_MUTED,
|
||||
index.data(MemberListModel::RadioSpeakingRole).toBool());
|
||||
if (canInvite) {
|
||||
QFont plusFont = nameFont; plusFont.setPixelSize(22); plusFont.setStrikeOut(false);
|
||||
painter->setFont(plusFont);
|
||||
painter->setPen(option.palette.color(QPalette::Link));
|
||||
painter->drawText(inviteButtonRect(option.rect), Qt::AlignCenter, QStringLiteral("+"));
|
||||
if (!self) {
|
||||
const QColor color = index.data(MemberListModel::OnlineRole).toBool() ? textColor :
|
||||
option.palette.color(QPalette::Disabled, QPalette::Text);
|
||||
const QRect radio = actionRect(option.rect, Action::RadioCall);
|
||||
FlagPainter::drawRadio(painter, radio.center().x() - 12, radio.center().y() - 12, 24, color);
|
||||
drawCallButton(painter, actionRect(option.rect, Action::Call), color);
|
||||
}
|
||||
painter->restore();
|
||||
}
|
||||
@@ -139,41 +165,31 @@ bool AccountDelegate::editorEvent(QEvent* event, QAbstractItemModel* model, cons
|
||||
event->type() != QEvent::MouseButtonDblClick) return false;
|
||||
auto* mouse = static_cast<QMouseEvent*>(event);
|
||||
if (mouse->button() != Qt::LeftButton) return false;
|
||||
const bool onMute = !index.data(Qt::UserRole + 2).toBool() && radioMuteButtonRect(option.rect).contains(mouse->pos());
|
||||
const Action action = actionAt(mouse->pos(), option.rect, index.data(Qt::UserRole + 2).toBool());
|
||||
const bool enabled = action == Action::Avatar || action == Action::RadioMute || index.data(MemberListModel::OnlineRole).toBool();
|
||||
if (event->type() == QEvent::MouseButtonPress) {
|
||||
m_radioMutePress = onMute ? index : QModelIndex(); m_radioMutePosition = mouse->pos();
|
||||
m_pressedIndex = action != Action::None && enabled ? index : QModelIndex();
|
||||
m_pressedAction = action; m_pressedPosition = mouse->pos();
|
||||
return action != Action::None;
|
||||
}
|
||||
if (onMute || m_radioMutePress.isValid()) {
|
||||
if (event->type() == QEvent::MouseButtonRelease) {
|
||||
const bool activate = onMute && m_radioMutePress == index && (mouse->pos() - m_radioMutePosition).manhattanLength() < 5;
|
||||
m_radioMutePress = QPersistentModelIndex();
|
||||
if (activate) emit radioMuteRequested(index);
|
||||
}
|
||||
return true;
|
||||
if (event->type() == QEvent::MouseButtonDblClick) return action != Action::None;
|
||||
const bool handled = action != Action::None || m_pressedIndex.isValid();
|
||||
const bool activate = enabled && action != Action::None && action == m_pressedAction && m_pressedIndex == index &&
|
||||
(mouse->pos() - m_pressedPosition).manhattanLength() < 5;
|
||||
m_pressedIndex = QPersistentModelIndex(); m_pressedAction = Action::None;
|
||||
if (activate) {
|
||||
if (action == Action::Avatar) emit avatarRequested(index);
|
||||
else if (action == Action::RadioMute) emit radioMuteRequested(index);
|
||||
else if (action == Action::RadioCall) emit radioCallRequested(index);
|
||||
else if (action == Action::Call) emit callRequested(index);
|
||||
}
|
||||
const bool onAvatar = avatarRect(option.rect).contains(mouse->pos());
|
||||
if (event->type()==QEvent::MouseButtonPress) {
|
||||
m_avatarPress=onAvatar ? index : QModelIndex(); m_avatarPosition=mouse->pos();
|
||||
}
|
||||
if (onAvatar) {
|
||||
if (event->type()==QEvent::MouseButtonRelease && m_avatarPress==index && (mouse->pos()-m_avatarPosition).manhattanLength()<5)
|
||||
emit avatarRequested(index);
|
||||
if (event->type()==QEvent::MouseButtonRelease) m_avatarPress=QPersistentModelIndex();
|
||||
return true;
|
||||
}
|
||||
if (index.data(Qt::UserRole + 2).toBool()) return false;
|
||||
const bool inside = inviteButtonRect(option.rect).contains(mouse->pos());
|
||||
if (event->type() == QEvent::MouseButtonPress) { m_invitePress = inside ? index : QModelIndex(); return inside; }
|
||||
if (event->type() == QEvent::MouseButtonDblClick) return inside;
|
||||
const bool activate = inside && m_invitePress == index;
|
||||
m_invitePress = QPersistentModelIndex();
|
||||
if (activate) emit inviteRequested(index);
|
||||
return activate;
|
||||
return handled;
|
||||
}
|
||||
|
||||
bool AccountDelegate::helpEvent(QHelpEvent* event, QAbstractItemView* view, const QStyleOptionViewItem& option,
|
||||
const QModelIndex& index) {
|
||||
if (event && radioMuteButtonRect(option.rect).contains(event->pos())) {
|
||||
const bool self = index.data(Qt::UserRole + 2).toBool();
|
||||
if (event && actionRect(option.rect, self ? Action::Call : Action::RadioMute).contains(event->pos())) {
|
||||
const bool muted = index.data(Qt::UserRole + 3).toInt() & CHAT_MEMBER_FLAG_RADIO_MUTED;
|
||||
const bool speaking = index.data(MemberListModel::RadioSpeakingRole).toBool();
|
||||
QString text = QCoreApplication::translate("ChatGui", speaking ? "Radio: speaking" : "Radio: silent");
|
||||
@@ -183,6 +199,15 @@ bool AccountDelegate::helpEvent(QHelpEvent* event, QAbstractItemView* view, cons
|
||||
QToolTip::showText(event->globalPos(), text, view);
|
||||
return true;
|
||||
}
|
||||
if (event && !self) {
|
||||
const Action action = actionAt(event->pos(), option.rect, false);
|
||||
if (action == Action::RadioCall || action == Action::Call) {
|
||||
QString text = QCoreApplication::translate("ChatGui", action == Action::RadioCall ? "Call by radio" : "Call peer");
|
||||
if (!index.data(MemberListModel::OnlineRole).toBool()) text += " · " + QCoreApplication::translate("ChatGui", "offline");
|
||||
QToolTip::showText(event->globalPos(), text, view);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return QStyledItemDelegate::helpEvent(event, view, option, index);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/* Отрисовка строки участника AccountList: аватар, имя, online и служебные значки. */
|
||||
/* Строка участника: аватар, имя и три действия — звук рации, вызов рации, обычный звонок.
|
||||
* Вызывает signals только после клика без перетаскивания; вызовы доступны online-пирам. */
|
||||
#pragma once
|
||||
|
||||
#include <QStyledItemDelegate>
|
||||
@@ -18,13 +19,15 @@ public:
|
||||
bool helpEvent(QHelpEvent* event, QAbstractItemView* view, const QStyleOptionViewItem& option,
|
||||
const QModelIndex& index) override;
|
||||
signals:
|
||||
void inviteRequested(const QModelIndex& index);
|
||||
void radioCallRequested(const QModelIndex& index);
|
||||
void callRequested(const QModelIndex& index);
|
||||
void avatarRequested(const QModelIndex& index);
|
||||
void radioMuteRequested(const QModelIndex& index);
|
||||
private:
|
||||
QPersistentModelIndex m_invitePress;
|
||||
QPersistentModelIndex m_avatarPress;
|
||||
QPoint m_avatarPosition;
|
||||
QPersistentModelIndex m_radioMutePress;
|
||||
QPoint m_radioMutePosition;
|
||||
enum class Action { None, Avatar, RadioMute, RadioCall, Call };
|
||||
static QRect actionRect(const QRect& row, Action action);
|
||||
static Action actionAt(const QPoint& position, const QRect& row, bool self);
|
||||
QPersistentModelIndex m_pressedIndex; /* строка, на которой начался клик */
|
||||
QPoint m_pressedPosition;
|
||||
Action m_pressedAction = Action::None;
|
||||
};
|
||||
Reference in new issue
Block a user