diff --git a/tools/chatgui/src/capture_controls.h b/tools/chatgui/src/capture_controls.h new file mode 100644 index 00000000..cd625775 --- /dev/null +++ b/tools/chatgui/src/capture_controls.h @@ -0,0 +1,129 @@ +/* Общие кнопки настроек захвата для звонка и трансляции: 48x48, значок 40x36, зазор 4. + * MenuStyle назначается QMenuBar, родитель стиля должен пережить bar и его меню. + * Меню вариантов должно содержать один QActionGroup; data хранит значение, checked — текущий выбор. + * select меняет выбор без triggered; addOption отмечает первый вариант, clearOptions сохраняет группу и добавляет заголовок. + * controlIcon рисует выбранное значение внутри силуэта, detail — дополнительную короткую подпись, rotation — угол маркера. */ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include + +namespace CaptureControls { +class MenuStyle : public QProxyStyle { +public: + int pixelMetric(PixelMetric metric, const QStyleOption* option, const QWidget* widget) const override { + if (qobject_cast(widget)) { + if (metric == PM_SmallIconSize) return 40; + if (metric == PM_MenuBarItemSpacing) return 4; + } + return QProxyStyle::pixelMetric(metric, option, widget); + } + QSize sizeFromContents(ContentsType type, const QStyleOption* option, const QSize& size, const QWidget* widget) const override { + if (type == CT_MenuBarItem && qobject_cast(widget)) return QSize(48, 48); + return QProxyStyle::sizeFromContents(type, option, size, widget); + } + void drawControl(ControlElement element, const QStyleOption* option, QPainter* painter, const QWidget* widget) const override { + auto* item = qstyleoption_cast(option); + if (element != CE_MenuBarItem || !item || !qobject_cast(widget)) { + QProxyStyle::drawControl(element, option, painter, widget); return; + } + painter->save(); painter->setRenderHint(QPainter::Antialiasing); painter->setPen(Qt::NoPen); + QColor background = item->palette.button().color(); + if (item->state & (State_Selected | State_Sunken)) background = item->palette.highlight().color(); + background.setAlphaF(item->state & State_Enabled ? 0.3 : 0.12); painter->setBrush(background); + painter->drawRoundedRect(item->rect, 10, 10); + QRect iconRect = alignedRect(item->direction, Qt::AlignCenter, QSize(40, 36), item->rect); + item->icon.paint(painter, iconRect, Qt::AlignCenter, item->state & State_Enabled ? QIcon::Normal : QIcon::Disabled); + painter->restore(); + } +}; + +inline QAction* selected(QMenu* menu) { return menu->findChild()->checkedAction(); } +inline QVariant value(QMenu* menu) { auto* action = selected(menu); return action ? action->data() : QVariant(); } +inline QAction* option(QMenu* menu, const QVariant& data) { + for (auto* action : menu->actions()) if (action->isCheckable() && action->data() == data) return action; + return nullptr; +} +inline bool select(QMenu* menu, const QVariant& data) { + auto* action = option(menu, data); + if (action) action->setChecked(true); + return action; +} +inline QAction* addOption(QMenu* menu, const QString& label, const QVariant& data) { + auto* group = menu->findChild(); + auto* action = menu->addAction(label); action->setCheckable(true); action->setData(data); group->addAction(action); + if (!group->checkedAction()) action->setChecked(true); + return action; +} +inline void clearOptions(QMenu* menu, const char* heading) { menu->clear(); menu->addSection(QCoreApplication::translate("ChatGui", heading)); } + +/* Компактные векторные значки с текущим значением, читаемые в обеих темах. */ +inline QIcon controlIcon(const QPalette& palette, const QString& shape, const QString& label = {}, const QString& detail = {}, int rotation = 0) { + QPixmap pixmap(80, 72); pixmap.fill(Qt::transparent); QPainter painter(&pixmap); painter.scale(2, 2); + painter.setRenderHint(QPainter::Antialiasing); painter.setPen(QPen(palette.windowText(), 1.6)); + QRectF labelRect(3, 5, 34, 26); int labelSize = 11; + if (shape == "camera") { + QPainterPath body; body.moveTo(4, 9); body.lineTo(11, 9); body.lineTo(14, 3); body.lineTo(26, 3); + body.lineTo(29, 9); body.lineTo(36, 9); body.quadTo(38, 9, 38, 12); body.lineTo(38, 31); + body.quadTo(38, 34, 35, 34); body.lineTo(5, 34); body.quadTo(2, 34, 2, 31); body.lineTo(2, 12); body.quadTo(2, 9, 4, 9); + painter.drawPath(body); labelRect.moveTop(10); labelRect.setHeight(22); + } else if (shape == "screen") { + painter.drawRoundedRect(QRectF(1.5, 2, 37, 27), 3, 3); painter.drawLine(20, 29, 20, 34); painter.drawLine(12, 34, 28, 34); + labelRect = QRectF(3, 3, 34, 24); + } else if (shape == "chip") { + painter.drawRoundedRect(QRectF(5.5, 5, 29, 26), 3, 3); + painter.setPen(QPen(palette.highlight(), 1.5)); + for (double position : {0.28, 0.5, 0.72}) { + painter.drawLine(QPointF(40 * position, 1), QPointF(40 * position, 5)); + painter.drawLine(QPointF(40 * position, 31), QPointF(40 * position, 35)); + painter.drawLine(QPointF(1, 36 * position), QPointF(5.5, 36 * position)); + painter.drawLine(QPointF(34.5, 36 * position), QPointF(39, 36 * position)); + } + labelRect = QRectF(6, 5, 28, 26); + } else if (shape == "gauge") { + painter.setPen(QPen(palette.highlight(), 1.5)); painter.drawArc(QRectF(2, 2, 36, 32), 0, 180 * 16); + painter.drawLine(2, 18, 6, 18); painter.drawLine(20, 2, 20, 6); painter.drawLine(34, 18, 38, 18); + painter.drawLine(6, 34, 34, 34); labelRect = QRectF(3, 17, 34, 15); labelSize = 12; + } else if (shape == "rotation") { + painter.save(); painter.translate(20, 18); painter.rotate(rotation); painter.translate(-20, -18); + QColor frame = palette.windowText().color(); frame.setAlphaF(0.6); painter.setPen(QPen(frame, 1.5)); + painter.drawRoundedRect(QRectF(8, 1.5, 24, 33), 4, 4); + painter.setPen(Qt::NoPen); painter.setBrush(palette.highlight()); painter.drawRoundedRect(QRectF(8, 1.5, 24, 4), 2, 2); + QPolygonF triangle; triangle << QPointF(15, 4.5) << QPointF(25, 4.5) << QPointF(20, 8.5); + painter.drawPolygon(triangle); painter.restore(); labelSize = 10; + labelRect.translate(rotation == 90 ? -2 : rotation == 270 ? 2 : 0, rotation == 0 ? 2 : 0); + } else if (shape == "settings") { + painter.translate(20, 18); + for (int i = 0; i < 8; i++) { painter.drawLine(0, -10, 0, -15); painter.rotate(45); } + painter.drawEllipse(QPointF(), 10, 10); painter.drawEllipse(QPointF(), 4, 4); + } else if (shape == "microphone") { + painter.save(); painter.translate(7, 0); painter.scale(0.65, 0.65); + painter.drawRoundedRect(QRectF(15, 3, 10, 19), 5, 5); painter.drawArc(QRectF(10, 12, 20, 16), 180 * 16, 180 * 16); + painter.drawLine(20, 28, 20, 33); painter.drawLine(14, 33, 26, 33); + painter.restore(); labelRect = QRectF(1, 23, 38, 13); labelSize = 9; + } else if (shape == "sound" || shape == "silent") { + QPolygon speaker; speaker << QPoint(6, 14) << QPoint(13, 14) << QPoint(22, 7) << QPoint(22, 29) + << QPoint(13, 22) << QPoint(6, 22); painter.drawPolygon(speaker); + if (shape == "sound") painter.drawArc(QRectF(18, 6, 17, 24), -70 * 16, 140 * 16); + else { painter.drawLine(27, 13, 35, 23); painter.drawLine(27, 23, 35, 13); } + } else { + painter.drawRoundedRect(QRectF(3, 5, 34, 26), 4, 4); + if (!detail.isEmpty()) labelRect = QRectF(3, 16, 34, 15); + } + painter.setPen(palette.windowText().color()); + if (!label.isEmpty()) { + QFont font = painter.font(); font.setPixelSize(labelSize); font.setBold(true); painter.setFont(font); + painter.drawText(labelRect, Qt::AlignCenter, painter.fontMetrics().elidedText(label, Qt::ElideRight, int(labelRect.width()))); + } + if (!detail.isEmpty()) { + QFont font = painter.font(); font.setPixelSize(8); font.setBold(false); painter.setFont(font); + painter.drawText(QRectF(3, 7, 34, 10), Qt::AlignCenter, detail); + } + return QIcon(pixmap); +} +} diff --git a/tools/chatgui/src/stream_publish_window.cpp b/tools/chatgui/src/stream_publish_window.cpp index 333333b9..dbab0222 100644 --- a/tools/chatgui/src/stream_publish_window.cpp +++ b/tools/chatgui/src/stream_publish_window.cpp @@ -1,4 +1,5 @@ #include "stream_publish_window.h" +#include "capture_controls.h" #include "sound_manager.h" #include "debug_config.h" #include @@ -32,120 +33,10 @@ namespace { constexpr int MaximumManualBitrate = 12000000; -class StreamMenuStyle : public QProxyStyle { -public: - int pixelMetric(PixelMetric metric, const QStyleOption* option, const QWidget* widget) const override { - if (qobject_cast(widget)) { - if (metric == PM_SmallIconSize) return 40; - if (metric == PM_MenuBarItemSpacing) return 4; - } - return QProxyStyle::pixelMetric(metric, option, widget); - } - QSize sizeFromContents(ContentsType type, const QStyleOption* option, const QSize& size, const QWidget* widget) const override { - if (type == CT_MenuBarItem && qobject_cast(widget)) return QSize(48, 48); - return QProxyStyle::sizeFromContents(type, option, size, widget); - } - void drawControl(ControlElement element, const QStyleOption* option, QPainter* painter, const QWidget* widget) const override { - auto* item = qstyleoption_cast(option); - if (element != CE_MenuBarItem || !item || !qobject_cast(widget)) { - QProxyStyle::drawControl(element, option, painter, widget); return; - } - painter->save(); painter->setRenderHint(QPainter::Antialiasing); painter->setPen(Qt::NoPen); - QColor background = item->palette.button().color(); - if (item->state & (State_Selected | State_Sunken)) background = item->palette.highlight().color(); - background.setAlphaF(item->state & State_Enabled ? 0.3 : 0.12); painter->setBrush(background); - painter->drawRoundedRect(item->rect, 10, 10); - QRect iconRect = alignedRect(item->direction, Qt::AlignCenter, QSize(40, 36), item->rect); - item->icon.paint(painter, iconRect, Qt::AlignCenter, item->state & State_Enabled ? QIcon::Normal : QIcon::Disabled); - painter->restore(); - } -}; QString text(const char* value) { return QCoreApplication::translate("ChatGui", value); } QString bytes(uint64_t value) { return QString::number(value / 1024.0, 'f', 1) + " KiB"; } -QAction* selected(QMenu* menu) { return menu->findChild()->checkedAction(); } -QVariant value(QMenu* menu) { auto* action = selected(menu); return action ? action->data() : QVariant(); } -QAction* option(QMenu* menu, const QVariant& data) { - for (auto* action : menu->actions()) if (action->isCheckable() && action->data() == data) return action; - return nullptr; -} -bool select(QMenu* menu, const QVariant& data) { - auto* action = option(menu, data); - if (action) action->setChecked(true); - return action; -} -QAction* addOption(QMenu* menu, const QString& label, const QVariant& data) { - auto* group = menu->findChild(); - auto* action = menu->addAction(label); action->setCheckable(true); action->setData(data); group->addAction(action); - if (!group->checkedAction()) action->setChecked(true); - return action; -} -void clearOptions(QMenu* menu, const char* heading) { menu->clear(); menu->addSection(text(heading)); } - -/* Компактные векторные значки с текущим значением, читаемые в обеих темах. */ -QIcon controlIcon(const QPalette& palette, const QString& shape, const QString& label = {}, const QString& detail = {}, int rotation = 0) { - QPixmap pixmap(80, 72); pixmap.fill(Qt::transparent); QPainter painter(&pixmap); painter.scale(2, 2); - painter.setRenderHint(QPainter::Antialiasing); painter.setPen(QPen(palette.windowText(), 1.6)); - QRectF labelRect(3, 5, 34, 26); int labelSize = 11; - if (shape == "camera") { - QPainterPath body; body.moveTo(4, 9); body.lineTo(11, 9); body.lineTo(14, 3); body.lineTo(26, 3); - body.lineTo(29, 9); body.lineTo(36, 9); body.quadTo(38, 9, 38, 12); body.lineTo(38, 31); - body.quadTo(38, 34, 35, 34); body.lineTo(5, 34); body.quadTo(2, 34, 2, 31); body.lineTo(2, 12); body.quadTo(2, 9, 4, 9); - painter.drawPath(body); labelRect.moveTop(10); labelRect.setHeight(22); - } else if (shape == "screen") { - painter.drawRoundedRect(QRectF(1.5, 2, 37, 27), 3, 3); painter.drawLine(20, 29, 20, 34); painter.drawLine(12, 34, 28, 34); - labelRect = QRectF(3, 3, 34, 24); - } else if (shape == "chip") { - painter.drawRoundedRect(QRectF(5.5, 5, 29, 26), 3, 3); - painter.setPen(QPen(palette.highlight(), 1.5)); - for (double position : {0.28, 0.5, 0.72}) { - painter.drawLine(QPointF(40 * position, 1), QPointF(40 * position, 5)); - painter.drawLine(QPointF(40 * position, 31), QPointF(40 * position, 35)); - painter.drawLine(QPointF(1, 36 * position), QPointF(5.5, 36 * position)); - painter.drawLine(QPointF(34.5, 36 * position), QPointF(39, 36 * position)); - } - labelRect = QRectF(6, 5, 28, 26); - } else if (shape == "gauge") { - painter.setPen(QPen(palette.highlight(), 1.5)); painter.drawArc(QRectF(2, 2, 36, 32), 0, 180 * 16); - painter.drawLine(2, 18, 6, 18); painter.drawLine(20, 2, 20, 6); painter.drawLine(34, 18, 38, 18); - painter.drawLine(6, 34, 34, 34); labelRect = QRectF(3, 17, 34, 15); labelSize = 12; - } else if (shape == "rotation") { - painter.save(); painter.translate(20, 18); painter.rotate(rotation); painter.translate(-20, -18); - QColor frame = palette.windowText().color(); frame.setAlphaF(0.6); painter.setPen(QPen(frame, 1.5)); - painter.drawRoundedRect(QRectF(8, 1.5, 24, 33), 4, 4); - painter.setPen(Qt::NoPen); painter.setBrush(palette.highlight()); painter.drawRoundedRect(QRectF(8, 1.5, 24, 4), 2, 2); - QPolygonF triangle; triangle << QPointF(15, 4.5) << QPointF(25, 4.5) << QPointF(20, 8.5); - painter.drawPolygon(triangle); painter.restore(); labelSize = 10; - labelRect.translate(rotation == 90 ? -2 : rotation == 270 ? 2 : 0, rotation == 0 ? 2 : 0); - } else if (shape == "settings") { - painter.translate(20, 18); - for (int i = 0; i < 8; i++) { painter.drawLine(0, -10, 0, -15); painter.rotate(45); } - painter.drawEllipse(QPointF(), 10, 10); painter.drawEllipse(QPointF(), 4, 4); - } else if (shape == "microphone") { - painter.save(); painter.translate(7, 0); painter.scale(0.65, 0.65); - painter.drawRoundedRect(QRectF(15, 3, 10, 19), 5, 5); painter.drawArc(QRectF(10, 12, 20, 16), 180 * 16, 180 * 16); - painter.drawLine(20, 28, 20, 33); painter.drawLine(14, 33, 26, 33); - painter.restore(); labelRect = QRectF(1, 23, 38, 13); labelSize = 9; - } else if (shape == "sound" || shape == "silent") { - QPolygon speaker; speaker << QPoint(6, 14) << QPoint(13, 14) << QPoint(22, 7) << QPoint(22, 29) - << QPoint(13, 22) << QPoint(6, 22); painter.drawPolygon(speaker); - if (shape == "sound") painter.drawArc(QRectF(18, 6, 17, 24), -70 * 16, 140 * 16); - else { painter.drawLine(27, 13, 35, 23); painter.drawLine(27, 23, 35, 13); } - } else { - painter.drawRoundedRect(QRectF(3, 5, 34, 26), 4, 4); - if (!detail.isEmpty()) labelRect = QRectF(3, 16, 34, 15); - } - painter.setPen(palette.windowText().color()); - if (!label.isEmpty()) { - QFont font = painter.font(); font.setPixelSize(labelSize); font.setBold(true); painter.setFont(font); - painter.drawText(labelRect, Qt::AlignCenter, painter.fontMetrics().elidedText(label, Qt::ElideRight, int(labelRect.width()))); - } - if (!detail.isEmpty()) { - QFont font = painter.font(); font.setPixelSize(8); font.setBold(false); painter.setFont(font); - painter.drawText(QRectF(3, 7, 34, 10), Qt::AlignCenter, detail); - } - return QIcon(pixmap); -} +using namespace CaptureControls; } /* Горизонталь — сохранённое окно источника, одна линия объединяет субпотоки получателя. */ @@ -216,7 +107,7 @@ StreamPublishWindow::StreamPublishWindow(StreamPublishEngine* engine, uint64_t g auto* layout = new QVBoxLayout(this); auto* controls = new QHBoxLayout; controls->setSpacing(4); m_menuBar = new QMenuBar(this); m_menuBar->setNativeMenuBar(false); m_menuBar->setMouseTracking(true); - auto* menuStyle = new StreamMenuStyle; menuStyle->setParent(this); m_menuBar->setStyle(menuStyle); + auto* menuStyle = new MenuStyle; menuStyle->setParent(this); m_menuBar->setStyle(menuStyle); m_menuBar->setObjectName(QStringLiteral("streamControls")); auto makeMenu = [this](const char* heading, const char* name) { auto* menu = m_menuBar->addMenu(QString()); menu->setObjectName(QString::fromLatin1(name));