diff --git a/ui/command_buffer_model.cpp b/ui/command_buffer_model.cpp index 89c3dc07b..64c6197bc 100644 --- a/ui/command_buffer_model.cpp +++ b/ui/command_buffer_model.cpp @@ -20,6 +20,7 @@ #include #include +#include "color_utils.h" #include "dive_core/command_hierarchy.h" #include "dive_tree_view.h" @@ -92,7 +93,7 @@ QVariant CommandBufferModel::data(const QModelIndex &index, int role) const uint64_t node_index = index.internalId(); if (role == Qt::ForegroundRole && IsSelected(node_index)) - return QColor(255, 128, 128); + return GetTextAccentColor(); if (role != Qt::DisplayRole) return QVariant(); @@ -457,4 +458,4 @@ bool CommandBufferModel::IsSelected(uint64_t node_index) const uint32_t bit_element = node_index % 8; uint8_t mask = 0x1 << bit_element; return (m_node_is_selected_bit_list[array_index] & mask) != 0; -} \ No newline at end of file +} diff --git a/ui/command_model.cpp b/ui/command_model.cpp index c488990d8..08791692f 100644 --- a/ui/command_model.cpp +++ b/ui/command_model.cpp @@ -18,6 +18,7 @@ #include #include +#include "color_utils.h" #include "dive_core/command_hierarchy.h" static_assert(sizeof(void *) == sizeof(uint64_t), @@ -86,7 +87,7 @@ QVariant CommandModel::data(const QModelIndex &index, int role) const if (marker_type == Dive::CommandHierarchy::MarkerType::kInsert || marker_type == Dive::CommandHierarchy::MarkerType::kBeginEnd) { - return QVariant(QBrush(QColor(85, 139, 47))); // Shade of green + return QVariant(QBrush(GetTextAccentColor())); } } } diff --git a/ui/dive_application.cpp b/ui/dive_application.cpp new file mode 100644 index 000000000..ed2ab4fc0 --- /dev/null +++ b/ui/dive_application.cpp @@ -0,0 +1,30 @@ +/* + Copyright 2025 Google LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include "dive_application.h" + +bool DiveApplication::event(QEvent* e) +{ + if (e->type() == QEvent::ApplicationPaletteChange) + { + if (m_style_sheet) + { + // Re-apply style sheet, so palette change is propagated. + setStyleSheet(*m_style_sheet); + } + } + return QApplication::event(e); +} diff --git a/ui/dive_application.h b/ui/dive_application.h new file mode 100644 index 000000000..742a8f50d --- /dev/null +++ b/ui/dive_application.h @@ -0,0 +1,41 @@ +/* + Copyright 2025 Google LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#pragma once + +#include + +#include +#include +#include + +class DiveApplication : public QApplication +{ + Q_OBJECT +public: + using QApplication::QApplication; + void SetStyleSheet(const QString& style_sheet) + { + m_style_sheet = style_sheet; + setStyleSheet(style_sheet); + } + +protected: + virtual bool event(QEvent* e) Q_DECL_OVERRIDE; + +private: + std::optional m_style_sheet; +}; diff --git a/ui/draw_dispatch_stats_tab_view.cpp b/ui/draw_dispatch_stats_tab_view.cpp index 23f169553..18e8a5c34 100644 --- a/ui/draw_dispatch_stats_tab_view.cpp +++ b/ui/draw_dispatch_stats_tab_view.cpp @@ -15,6 +15,7 @@ #include "draw_dispatch_stats_model.h" #include "search_bar.h" #include +#include #include #include #include @@ -29,6 +30,7 @@ DrawDispatchStatsTabView::DrawDispatchStatsTabView(const Dive::CaptureStats &sta { m_draw_dispatch_stats_model = new DrawDispatchStatsModel(); m_draw_dispatch_stats_view = new QTableView(); + m_draw_dispatch_stats_view->verticalHeader()->hide(); m_draw_dispatch_stats_view->setModel(m_draw_dispatch_stats_model); ResizeColumns(m_draw_dispatch_stats_model, m_draw_dispatch_stats_view); @@ -60,4 +62,4 @@ void DrawDispatchStatsTabView::LoadStatistics() m_draw_dispatch_stats_model->LoadData(m_stats.m_stats_list); ResizeColumns(m_draw_dispatch_stats_model, m_draw_dispatch_stats_view); -} \ No newline at end of file +} diff --git a/ui/event_state_view.cpp b/ui/event_state_view.cpp index 789e7ac2b..cad8b8ca0 100644 --- a/ui/event_state_view.cpp +++ b/ui/event_state_view.cpp @@ -20,6 +20,7 @@ #include #include #include +#include "color_utils.h" #include "dive_core/command_hierarchy.h" #include "dive_core/data_core.h" #include "dive_core/dive_strings.h" @@ -41,7 +42,7 @@ _item->setText(1, _string); \ if (!prev_event_state_it->IsValid() || !_prev_field_set || \ QString::compare(_prev_string, _string) != 0) \ - _item->setForeground(1, QBrush(QColor(Qt::cyan))); \ + _item->setForeground(1, QBrush(m_accent_color)); \ _items.append(_item); \ } @@ -51,7 +52,7 @@ _item->setText(0, QString(_field)); \ _item->setText(1, QString::number(_num)); \ if (!prev_event_state_it->IsValid() || !_prev_field_set || _prev_num != _num) \ - _item->setForeground(1, QBrush(QColor(Qt::cyan))); \ + _item->setForeground(1, QBrush(m_accent_color)); \ _items.append(_item); \ } @@ -61,7 +62,7 @@ _item->setText(0, QString(_field)); \ _item->setText(1, "0x" + QString::number(static_cast(_num), 16)); \ if (!prev_event_state_it->IsValid() || !_prev_field_set || _prev_num != _num) \ - _item->setForeground(1, QBrush(QColor(Qt::cyan))); \ + _item->setForeground(1, QBrush(m_accent_color)); \ _items.append(_item); \ } @@ -81,7 +82,7 @@ _item->setText(0, QString(_field)); \ _item->setText(1, (_bool ? "true" : "false")); \ if (!prev_event_state_it->IsValid() || !_prev_field_set || _prev_bool != _bool) \ - _item->setForeground(1, QBrush(QColor(Qt::cyan))); \ + _item->setForeground(1, QBrush(m_accent_color)); \ _items.append(_item); \ } @@ -164,6 +165,8 @@ void EventStateView::OnEventSelected(uint64_t node_index) if (node_index == UINT64_MAX) return; + m_accent_color = GetTextAccentColor(); + auto &metadata = m_data_core.GetCaptureMetadata(); auto &command_hierarchy = m_data_core.GetCommandHierarchy(); auto &event_state = metadata.m_event_state; @@ -901,66 +904,84 @@ void EventStateView::DisplayColorBlendState(Dive::EventStateInfo::ConstIterator { if (event_state_it->IsAttachmentSet(i)) { + QList attachment_items; // LogicOpEnabled if (event_state_it->IsLogicOpEnabledSet(i)) - ADD_FIELD_TYPE_BOOL(event_state_it->GetLogicOpEnabledName() + - QString::number(i), + ADD_FIELD_TYPE_BOOL(event_state_it->GetLogicOpEnabledName(), event_state_it->LogicOpEnabled(i), prev_event_state_it->IsLogicOpEnabledSet(i), prev_event_state_it->LogicOpEnabled(i), - child_items) + attachment_items) else - ADD_FIELD_NOT_SET(event_state_it->GetLogicOpEnabledName() + QString::number(i), - child_items) + ADD_FIELD_NOT_SET(event_state_it->GetLogicOpEnabledName(), attachment_items) // LogicOp if (event_state_it->IsLogicOpSet(i)) { - ADD_FIELD_TYPE_STRING(event_state_it->GetLogicOpName() + QString::number(i), + ADD_FIELD_TYPE_STRING(event_state_it->GetLogicOpName(), GetVkLogicOp(event_state_it->LogicOp(i)), prev_event_state_it->IsLogicOpSet(i), GetVkLogicOp(prev_event_state_it->LogicOp(i)), - child_items) + attachment_items) } else { - ADD_FIELD_NOT_SET(event_state_it->GetLogicOpName() + QString::number(i), - child_items) + ADD_FIELD_NOT_SET(event_state_it->GetLogicOpName(), attachment_items) } - auto GetBlendString = [](VkPipelineColorBlendAttachmentState attach) { - return "blendEnabled: " + QString::number(attach.blendEnable) + - ", srcColorBlendFactor: " + - QString(GetVkBlendFactor(attach.srcColorBlendFactor)) + - ", dstColorBlendFactor: " + - QString(GetVkBlendFactor(attach.dstColorBlendFactor)) + - ", colorBlendOp: " + QString(GetVkBlendOp(attach.colorBlendOp)) + - ", srcAlphaBlendFactor: " + - QString(GetVkBlendFactor(attach.srcAlphaBlendFactor)) + - ", dstAlphaBlendFactor: " + - QString(GetVkBlendFactor(attach.dstAlphaBlendFactor)) + - ", alphaBlendOp: " + QString(GetVkBlendOp(attach.alphaBlendOp)) + - ", colorWriteMask: 0x" + QString::number(attach.colorWriteMask, 16); - }; - - QString value; - VkPipelineColorBlendAttachmentState attach = event_state_it->Attachment(i); - - value = GetBlendString(attach); - - QString prev_value; - VkPipelineColorBlendAttachmentState prev_attach; + const VkPipelineColorBlendAttachmentState curr_attach = event_state_it->Attachment(i); + const bool prev_set = prev_event_state_it->IsAttachmentSet(i); + VkPipelineColorBlendAttachmentState prev_attach = {}; if (prev_event_state_it->IsValid()) { prev_attach = prev_event_state_it->Attachment(i); - prev_value = GetBlendString(prev_attach); } - - ADD_FIELD_TYPE_STRING(QString::number(i), - value, - prev_event_state_it->IsAttachmentSet(i), - prev_value, - child_items); + ADD_FIELD_TYPE_NUMBER("BlendEnabled", + curr_attach.blendEnable, + prev_set, + prev_attach.blendEnable, + attachment_items); + ADD_FIELD_TYPE_NUMBER("SrcColorBlendFactor", + curr_attach.srcColorBlendFactor, + prev_set, + prev_attach.srcColorBlendFactor, + attachment_items); + ADD_FIELD_TYPE_NUMBER("DstColorBlendFactor", + curr_attach.dstColorBlendFactor, + prev_set, + prev_attach.dstColorBlendFactor, + attachment_items); + ADD_FIELD_TYPE_NUMBER("ColorBlendOp", + curr_attach.colorBlendOp, + prev_set, + prev_attach.colorBlendOp, + attachment_items); + ADD_FIELD_TYPE_NUMBER("SrcAlphaBlendFactor", + curr_attach.srcAlphaBlendFactor, + prev_set, + prev_attach.srcAlphaBlendFactor, + attachment_items); + ADD_FIELD_TYPE_NUMBER("DstAlphaBlendFactor", + curr_attach.dstAlphaBlendFactor, + prev_set, + prev_attach.dstAlphaBlendFactor, + attachment_items); + ADD_FIELD_TYPE_NUMBER("AlphaBlendOp", + curr_attach.alphaBlendOp, + prev_set, + prev_attach.alphaBlendOp, + attachment_items); + ADD_FIELD_TYPE_NUMBER_HEX("ColorWriteMask", + curr_attach.colorWriteMask, + prev_set, + prev_attach.colorWriteMask, + attachment_items); + + QTreeWidgetItem *attachment_item = new QTreeWidgetItem((QTreeWidget *)0, + QStringList( + QString::number(i))); + attachment_item->insertChildren(0, attachment_items); + child_items.append(attachment_item); } else ADD_FIELD_NOT_SET(QString::number(i), child_items); diff --git a/ui/event_state_view.h b/ui/event_state_view.h index 758565adb..0b63a9610 100644 --- a/ui/event_state_view.h +++ b/ui/event_state_view.h @@ -46,6 +46,7 @@ private slots: std::map m_field_desc; const Dive::DataCore &m_data_core; QTreeWidget *m_event_state_tree; + QColor m_accent_color; Dive::EventStateInfo::ConstIterator GetStateInfoForEvent(const Dive::EventStateInfo &state, uint32_t event_id); @@ -83,4 +84,4 @@ private slots: void BuildResolveSysmemDescriptionMap(Dive::EventStateInfo::ConstIterator event_state_it); void DisplayResolveSysmemInfo(Dive::EventStateInfo::ConstIterator event_state_it, Dive::EventStateInfo::ConstIterator prev_event_state_it); -}; \ No newline at end of file +}; diff --git a/ui/gpu_timing_tab_view.cpp b/ui/gpu_timing_tab_view.cpp index c2bd24bfd..d99c0f169 100644 --- a/ui/gpu_timing_tab_view.cpp +++ b/ui/gpu_timing_tab_view.cpp @@ -15,6 +15,7 @@ #include #include +#include #include #include "ui/gpu_timing_model.h" @@ -27,6 +28,7 @@ GpuTimingTabView::GpuTimingTabView(GpuTimingModel &gpu_timing_mode m_command_hierarchy(command_hierarchy) { m_table_view = new QTableView(this); + m_table_view->verticalHeader()->hide(); m_table_view->setModel(&m_model); // Used otherwise the table does not expand to fit available space @@ -190,4 +192,4 @@ void GpuTimingTabView::ClearSelection() { selection_model->clear(); } -} \ No newline at end of file +} diff --git a/ui/main.cpp b/ui/main.cpp index bcc48a34d..b5156a3f0 100644 --- a/ui/main.cpp +++ b/ui/main.cpp @@ -20,12 +20,14 @@ #include #include #include +#include #include #include #include #include #include #include +#include "dive_application.h" #include "dive_core/common.h" #include "dive_core/pm4_info.h" #include "application_controller.h" @@ -247,13 +249,25 @@ int main(int argc, char *argv[]) // Try setting "Fusion" style. If not found, set "Windows". // And if that's not found, default to whatever style the factory provides. - if (!SetApplicationStyle("Fusion")) + bool use_default_style = false; + + QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); + QString desktopEnvironment = env.value("XDG_CURRENT_DESKTOP"); + if (desktopEnvironment.contains("KDE", Qt::CaseInsensitive)) + { + use_default_style = true; + } + + if (!use_default_style) { - if (!SetApplicationStyle("Windows")) + if (!SetApplicationStyle("Fusion")) { - if (!QStyleFactory::keys().empty()) + if (!SetApplicationStyle("Windows")) { - SetApplicationStyle(QStyleFactory::keys()[0]); + if (!QStyleFactory::keys().empty()) + { + SetApplicationStyle(QStyleFactory::keys()[0]); + } } } } @@ -261,15 +275,26 @@ int main(int argc, char *argv[]) Dive::RegisterCustomMetaType(); QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); - QApplication app(argc, argv); + DiveApplication app(argc, argv); app.setWindowIcon(QIcon(":/images/dive.ico")); - SetDarkMode(app); - // Load and apply the style sheet - QFile style_sheet(":/stylesheet.qss"); - style_sheet.open(QFile::ReadOnly); - QString style(style_sheet.readAll()); - app.setStyleSheet(style); + if (!use_default_style) + { + SetDarkMode(app); + // Load and apply the style sheet + QFile style_sheet(":/stylesheet.qss"); + style_sheet.open(QFile::ReadOnly); + QString style(style_sheet.readAll()); + app.SetStyleSheet(style); + } + else + { + // Load and apply the style sheet + QFile style_sheet(":/stylesheet_adaptive.qss"); + style_sheet.open(QFile::ReadOnly); + QString style(style_sheet.readAll()); + app.SetStyleSheet(style); + } // Display splash screen QSplashScreen *splash_screen = new QSplashScreen(); @@ -285,6 +310,7 @@ int main(int argc, char *argv[]) { QObject::connect(main_window, &MainWindow::FileLoaded, main_window, &MainWindow::close); } + main_window->SetUseDefaultStyle(use_default_style); if (!controller.InitializePlugins()) { diff --git a/ui/main_window.cpp b/ui/main_window.cpp index 1f7b53b62..4c0cc6e57 100644 --- a/ui/main_window.cpp +++ b/ui/main_window.cpp @@ -1637,7 +1637,7 @@ void MainWindow::CreateActions() { // Open file action m_open_action = new QAction(tr("&Open"), this); - m_open_action->setIcon(QIcon(":/images/open.png")); + m_open_action->setIcon(QApplication::style()->standardIcon(QStyle::SP_DialogOpenButton)); m_open_action->setShortcuts(QKeySequence::Open); m_open_action->setStatusTip(tr("Open an existing capture")); connect(m_open_action, &QAction::triggered, this, &MainWindow::OnOpenFile); @@ -1652,7 +1652,7 @@ void MainWindow::CreateActions() // Save file action m_save_action = new QAction(tr("&Save"), this); m_save_action->setStatusTip(tr("Save the current capture")); - m_save_action->setIcon(QIcon(":/images/save.png")); + m_save_action->setIcon(QApplication::style()->standardIcon(QStyle::SP_DialogSaveButton)); m_save_action->setShortcut(QKeySequence::Save); m_save_action->setEnabled(false); connect(m_save_action, &QAction::triggered, this, &MainWindow::OnSaveCapture); @@ -1670,6 +1670,7 @@ void MainWindow::CreateActions() for (auto &action : m_recent_file_actions) { action = new QAction(this); + action->setIcon(QApplication::style()->standardIcon(QStyle::SP_FileIcon)); action->setVisible(false); connect(action, SIGNAL(triggered()), this, SLOT(OpenRecentFile())); } @@ -1819,7 +1820,10 @@ void MainWindow::CreateStatusBar() { // Create status bar on the main window. m_status_bar = new QStatusBar(this); - m_status_bar->setStyleSheet("background:#D0D0D0; color:#282828"); + if (!m_use_default_style) + { + m_status_bar->setStyleSheet("background:#D0D0D0; color:#282828"); + } setStatusBar(m_status_bar); } diff --git a/ui/main_window.h b/ui/main_window.h index 24634a118..044458bed 100644 --- a/ui/main_window.h +++ b/ui/main_window.h @@ -125,6 +125,8 @@ class MainWindow : public QMainWindow ~MainWindow(); bool LoadFile(const std::string &file_name, bool is_temp_file = false, bool async = true); + void SetUseDefaultStyle(bool use_default_style) { m_use_default_style = use_default_style; } + protected: virtual void closeEvent(QCloseEvent *closeEvent) Q_DECL_OVERRIDE; @@ -238,6 +240,8 @@ private slots: CorrelationTarget target); ApplicationController &m_controller; + + bool m_use_default_style = false; QMenu *m_file_menu; QMenu *m_recent_captures_menu; diff --git a/ui/misc_stats_tab_view.cpp b/ui/misc_stats_tab_view.cpp index 41e2426c4..7bdedd381 100644 --- a/ui/misc_stats_tab_view.cpp +++ b/ui/misc_stats_tab_view.cpp @@ -15,6 +15,7 @@ #include "misc_stats_model.h" #include "search_bar.h" #include +#include #include #include #include @@ -28,6 +29,7 @@ MiscStatsTabView::MiscStatsTabView(const Dive::CaptureStats &stats, QWidget *par { m_misc_stats_model = new MiscStatsModel(); m_misc_stats_view = new QTableView(); + m_misc_stats_view->verticalHeader()->hide(); m_misc_stats_view->setModel(m_misc_stats_model); ResizeColumns(m_misc_stats_model, m_misc_stats_view); diff --git a/ui/perf_counter_tab_view.cpp b/ui/perf_counter_tab_view.cpp index 6ca1decc5..10eeaf525 100644 --- a/ui/perf_counter_tab_view.cpp +++ b/ui/perf_counter_tab_view.cpp @@ -31,6 +31,7 @@ PerfCounterTabView::PerfCounterTabView(PerfCounterModel &perf_counter_model, QWi m_proxy_model->setSourceModel(&m_perf_counter_model); m_perf_counter_view = new QTableView(); + m_perf_counter_view->verticalHeader()->hide(); m_perf_counter_view->setSortingEnabled(true); m_perf_counter_view->setModel(m_proxy_model); diff --git a/ui/resources.qrc b/ui/resources.qrc index cfffd6932..d05ac7038 100644 --- a/ui/resources.qrc +++ b/ui/resources.qrc @@ -17,8 +17,9 @@ images/dive.png images/dive.ico stylesheet.qss + stylesheet_adaptive.qss images/filter.png images/analyze.png images/copy.png - \ No newline at end of file + diff --git a/ui/stylesheet_adaptive.qss b/ui/stylesheet_adaptive.qss new file mode 100644 index 000000000..8ca49f547 --- /dev/null +++ b/ui/stylesheet_adaptive.qss @@ -0,0 +1,41 @@ +/* QTreeView style */ + +QTreeView { + show-decoration-selected: 1; +} + +QTreeView::branch:has-siblings:!adjoins-item { + border-image: url(\":/images/vline.png\") 0; +} + +QTreeView::branch:has-siblings:adjoins-item { + border-image: url(\":/images/branch_more.png\") 0; +} + +QTreeView::branch:!has-children:!has-siblings:adjoins-item { + border-image: url(\":/images/branch_end.png\") 0; +} + +QTreeView::branch:has-children:!has-siblings:closed, +QTreeView::branch:closed:has-children:has-siblings { + border-image: none; + image: url(\":/images/branch_closed.png\"); +} + +QTreeView::branch:open:has-children:!has-siblings, +QTreeView::branch:open:has-children:has-siblings { + border-image: none; + image: url(\":/images/branch_open.png\"); +} + +/* Style for the hover help label */ +QLabel#hoverHelpLabel { + background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #e7effd, stop: 1 #cbdaf1); + border-radius: 5px; + color: #000; + padding: 3px; +} + +QLabel#propertyPanelLabel { + font - family : Arial, Helvetica, sans - serif; +} diff --git a/ui/tile_stats_tab_view.cpp b/ui/tile_stats_tab_view.cpp index b94df68ed..4fa5915a1 100644 --- a/ui/tile_stats_tab_view.cpp +++ b/ui/tile_stats_tab_view.cpp @@ -16,9 +16,10 @@ #include "window_scissors_stats_model.h" #include "search_bar.h" #include -#include +#include #include #include +#include #include #include "object_names.h" #include "trace_stats/trace_stats.h" @@ -29,11 +30,13 @@ TileStatsTabView::TileStatsTabView(const Dive::CaptureStats &stats, QWidget *par { m_viewport_stats_model = new ViewportStatsModel(); m_viewport_stats_view = new QTableView(); + m_viewport_stats_view->verticalHeader()->hide(); m_viewport_stats_view->setModel(m_viewport_stats_model); ResizeColumns(m_viewport_stats_model, m_viewport_stats_view); m_window_scissors_stats_model = new WindowScissorsStatsModel(); m_window_scissors_stats_view = new QTableView(); + m_window_scissors_stats_view->verticalHeader()->hide(); m_window_scissors_stats_view->setModel(m_window_scissors_stats_model); ResizeColumns(m_viewport_stats_model, m_window_scissors_stats_view);