Further refactoring of hex viewer item painting for additional performance gains

This commit is contained in:
Nav
2023-05-06 21:02:30 +01:00
parent eca86fcb1a
commit b80f6aad6c
22 changed files with 1193 additions and 1110 deletions

View File

@@ -99,6 +99,8 @@ target_sources(
${CMAKE_CURRENT_SOURCE_DIR}/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/StackMemoryGroupItem.cpp
${CMAKE_CURRENT_SOURCE_DIR}/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ByteAddressContainer.cpp
${CMAKE_CURRENT_SOURCE_DIR}/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ByteAddressItem.cpp
${CMAKE_CURRENT_SOURCE_DIR}/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/HexViewerItemIndex.cpp
${CMAKE_CURRENT_SOURCE_DIR}/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/HexViewerItemRenderer.cpp
${CMAKE_CURRENT_SOURCE_DIR}/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ContextMenuAction.cpp
${CMAKE_CURRENT_SOURCE_DIR}/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/MemoryRegion.cpp
${CMAKE_CURRENT_SOURCE_DIR}/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/FocusedMemoryRegion.cpp

View File

@@ -1,350 +1,8 @@
#include "ByteItem.hpp"
#include <QFont>
#include <QColor>
namespace Bloom::Widgets
{
ByteItem::ByteItem(Targets::TargetMemoryAddress address)
: HexViewerItem(address)
{
if (!ByteItem::pixmapCachesGenerated) {
ByteItem::generatePixmapCaches();
}
}
void ByteItem::paint(
QPainter* painter,
const HexViewerSharedState* hexViewerState,
const QGraphicsItem* graphicsItem
) const {
const auto boundingRect = QRect(0, 0, ByteItem::WIDTH, ByteItem::HEIGHT);
if (!graphicsItem->isEnabled() || (this->excluded && !this->selected)) {
painter->setOpacity(0.6);
}
if (this->excluded || !hexViewerState->data.has_value()) {
if (this->selected) {
painter->drawPixmap(boundingRect, ByteItem::selectedMissingDataPixmap.value());
return;
}
painter->drawPixmap(boundingRect, ByteItem::missingDataPixmap.value());
return;
}
const auto byteIndex = this->startAddress - hexViewerState->memoryDescriptor.addressRange.startAddress;
const auto value = (*hexViewerState->data)[byteIndex];
const auto hoveredPrimary = hexViewerState->hoveredByteItem == this;
if (hexViewerState->settings.displayAsciiValues) {
if (this->selected) {
painter->drawPixmap(
boundingRect,
ByteItem::selectedAsciiPixmapsByValue[value]
);
return;
}
if (this->changed) {
painter->drawPixmap(
boundingRect,
ByteItem::changedMemoryAsciiPixmapsByValue[value]
);
return;
}
if (this->stackMemory && hexViewerState->settings.groupStackMemory) {
painter->drawPixmap(
boundingRect,
ByteItem::stackMemoryAsciiPixmapsByValue[value]
);
return;
}
if (this->grouped && hexViewerState->settings.highlightFocusedMemory) {
painter->drawPixmap(
boundingRect,
ByteItem::groupedAsciiPixmapsByValue[value]
);
return;
}
if (hoveredPrimary) {
painter->drawPixmap(
boundingRect,
ByteItem::hoveredPrimaryAsciiPixmapsByValue[value]
);
return;
}
painter->drawPixmap(boundingRect, this->standardAsciiPixmapsByValue[value]);
return;
}
if (this->selected) {
painter->drawPixmap(
boundingRect,
ByteItem::selectedPixmapsByValue[value]
);
return;
}
if (this->changed) {
painter->drawPixmap(
boundingRect,
ByteItem::changedMemoryPixmapsByValue[value]
);
return;
}
if (this->stackMemory && hexViewerState->settings.groupStackMemory) {
painter->drawPixmap(
boundingRect,
ByteItem::stackMemoryPixmapsByValue[value]
);
return;
}
if (this->grouped && hexViewerState->settings.highlightFocusedMemory) {
painter->drawPixmap(
boundingRect,
ByteItem::groupedPixmapsByValue[value]
);
return;
}
if (hoveredPrimary) {
painter->drawPixmap(
boundingRect,
ByteItem::hoveredPrimaryPixmapsByValue[value]
);
return;
}
painter->drawPixmap(
boundingRect,
this->standardPixmapsByValue[value]
);
}
void ByteItem::generatePixmapCaches() {
const auto lock = std::unique_lock(ByteItem::pixmapCacheMutex);
if (ByteItem::pixmapCachesGenerated) {
return;
}
static constexpr auto standardBackgroundColor = QColor(0x32, 0x33, 0x30, 255);
static constexpr auto highlightedBackgroundColor = QColor(0x3C, 0x59, 0x5C, 255);
static constexpr auto selectedBackgroundColor = QColor(0x3C, 0x59, 0x5C, 255);
static constexpr auto groupedBackgroundColor = QColor(0x44, 0x44, 0x41, 255);
static constexpr auto stackMemoryBackgroundColor = QColor(0x44, 0x44, 0x41, 200);
static constexpr auto stackMemoryBarColor = QColor(0x67, 0x57, 0x20, 255);
static constexpr auto changedMemoryBackgroundColor = QColor(0x5C, 0x49, 0x5D, 200);
static constexpr auto changedMemoryFadedBackgroundColor = QColor(0x5C, 0x49, 0x5D, 125);
static const auto hoveredStackMemoryBackgroundColor = QColor(
stackMemoryBackgroundColor.red(),
stackMemoryBackgroundColor.green(),
stackMemoryBackgroundColor.blue(),
255
);
static constexpr auto hoveredBackgroundColor = QColor(0x8E, 0x8B, 0x83, 70);
static constexpr auto standardFontColor = QColor(0xAF, 0xB1, 0xB3);
static constexpr auto fadedFontColor = QColor(0xAF, 0xB1, 0xB3, 100);
static constexpr auto asciiFontColor = QColor(0xA7, 0x77, 0x26);
static constexpr auto changedMemoryAsciiFontColor = QColor(0xB7, 0x7F, 0x21);
const auto byteItemRect = QRect(0, 0, ByteItem::WIDTH, ByteItem::HEIGHT);
const auto byteItemSize = byteItemRect.size();
auto standardTemplatePixmap = QPixmap(byteItemSize);
standardTemplatePixmap.fill(standardBackgroundColor);
auto highlightedTemplatePixmap = QPixmap(byteItemSize);
highlightedTemplatePixmap.fill(highlightedBackgroundColor);
auto selectedTemplatePixmap = QPixmap(byteItemSize);
selectedTemplatePixmap.fill(selectedBackgroundColor);
auto groupedTemplatePixmap = QPixmap(byteItemSize);
groupedTemplatePixmap.fill(groupedBackgroundColor);
auto stackMemoryTemplatePixmap = QPixmap(byteItemSize);
stackMemoryTemplatePixmap.fill(stackMemoryBackgroundColor);
{
auto painter = QPainter(&stackMemoryTemplatePixmap);
painter.setBrush(stackMemoryBarColor);
painter.setPen(Qt::PenStyle::NoPen);
painter.drawRect(0, byteItemSize.height() - 3, byteItemSize.width(), 3);
}
auto changedMemoryTemplatePixmap = QPixmap(byteItemSize);
changedMemoryTemplatePixmap.fill(changedMemoryBackgroundColor);
auto changedMemoryFadedTemplatePixmap = QPixmap(byteItemSize);
changedMemoryFadedTemplatePixmap.fill(changedMemoryFadedBackgroundColor);
auto hoveredStackMemoryTemplatePixmap = QPixmap(byteItemSize);
hoveredStackMemoryTemplatePixmap.fill(hoveredStackMemoryBackgroundColor);
auto hoveredPrimaryTemplatePixmap = QPixmap(byteItemSize);
hoveredPrimaryTemplatePixmap.fill(hoveredBackgroundColor);
static auto font = QFont("'Ubuntu', sans-serif", 8);
for (std::uint16_t value = 0x00; value <= 0xFF; ++value) {
const auto hexValue = QString::number(value, 16).rightJustified(2, '0').toUpper();
const auto asciiValue = value >= 32 && value <= 126
? std::optional("'" + QString(QChar(value)) + "'")
: std::nullopt;
{
auto standardPixmap = standardTemplatePixmap;
auto painter = QPainter(&standardPixmap);
painter.setFont(font);
painter.setPen(standardFontColor);
painter.drawText(byteItemRect, Qt::AlignCenter, hexValue);
ByteItem::standardPixmapsByValue.emplace_back(std::move(standardPixmap));
}
{
auto selectedPixmap = selectedTemplatePixmap;
auto painter = QPainter(&selectedPixmap);
painter.setFont(font);
painter.setPen(standardFontColor);
painter.drawText(byteItemRect, Qt::AlignCenter, hexValue);
ByteItem::selectedPixmapsByValue.emplace_back(std::move(selectedPixmap));
}
{
auto groupedPixmap = groupedTemplatePixmap;
auto painter = QPainter(&groupedPixmap);
painter.setFont(font);
painter.setPen(standardFontColor);
painter.drawText(byteItemRect, Qt::AlignCenter, hexValue);
ByteItem::groupedPixmapsByValue.emplace_back(std::move(groupedPixmap));
}
{
auto stackMemoryPixmap = stackMemoryTemplatePixmap;
auto painter = QPainter(&stackMemoryPixmap);
painter.setFont(font);
painter.setPen(standardFontColor);
painter.drawText(byteItemRect, Qt::AlignCenter, hexValue);
ByteItem::stackMemoryPixmapsByValue.emplace_back(std::move(stackMemoryPixmap));
}
{
auto changedMemoryPixmap = changedMemoryTemplatePixmap;
auto painter = QPainter(&changedMemoryPixmap);
painter.setFont(font);
painter.setPen(standardFontColor);
painter.drawText(byteItemRect, Qt::AlignCenter, hexValue);
ByteItem::changedMemoryPixmapsByValue.emplace_back(std::move(changedMemoryPixmap));
}
{
auto hoveredPrimaryPixmap = hoveredPrimaryTemplatePixmap;
auto painter = QPainter(&hoveredPrimaryPixmap);
painter.setFont(font);
painter.setPen(standardFontColor);
painter.drawText(byteItemRect, Qt::AlignCenter, hexValue);
ByteItem::hoveredPrimaryPixmapsByValue.emplace_back(std::move(hoveredPrimaryPixmap));
}
{
auto standardAsciiPixmap = standardTemplatePixmap;
auto painter = QPainter(&standardAsciiPixmap);
painter.setFont(font);
painter.setPen(asciiValue.has_value() ? asciiFontColor : fadedFontColor);
painter.drawText(byteItemRect, Qt::AlignCenter, asciiValue.value_or(hexValue));
ByteItem::standardAsciiPixmapsByValue.emplace_back(std::move(standardAsciiPixmap));
}
{
auto selectedAsciiPixmap = selectedTemplatePixmap;
auto painter = QPainter(&selectedAsciiPixmap);
painter.setFont(font);
painter.setPen(asciiValue.has_value() ? asciiFontColor : fadedFontColor);
painter.drawText(byteItemRect, Qt::AlignCenter, asciiValue.value_or(hexValue));
ByteItem::selectedAsciiPixmapsByValue.emplace_back(std::move(selectedAsciiPixmap));
}
{
auto groupedAsciiPixmap = groupedTemplatePixmap;
auto painter = QPainter(&groupedAsciiPixmap);
painter.setFont(font);
painter.setPen(asciiValue.has_value() ? asciiFontColor : fadedFontColor);
painter.drawText(byteItemRect, Qt::AlignCenter, asciiValue.value_or(hexValue));
ByteItem::groupedAsciiPixmapsByValue.emplace_back(std::move(groupedAsciiPixmap));
}
{
auto stackMemoryAsciiPixmap = stackMemoryTemplatePixmap;
auto painter = QPainter(&stackMemoryAsciiPixmap);
painter.setFont(font);
painter.setPen(asciiValue.has_value() ? asciiFontColor : fadedFontColor);
painter.drawText(byteItemRect, Qt::AlignCenter, asciiValue.value_or(hexValue));
ByteItem::stackMemoryAsciiPixmapsByValue.emplace_back(std::move(stackMemoryAsciiPixmap));
}
{
auto changedMemoryAsciiPixmap = asciiValue.has_value()
? changedMemoryTemplatePixmap
: changedMemoryFadedTemplatePixmap;
auto painter = QPainter(&changedMemoryAsciiPixmap);
painter.setFont(font);
painter.setPen(asciiValue.has_value() ? changedMemoryAsciiFontColor : fadedFontColor);
painter.drawText(byteItemRect, Qt::AlignCenter, asciiValue.value_or(hexValue));
ByteItem::changedMemoryAsciiPixmapsByValue.emplace_back(std::move(changedMemoryAsciiPixmap));
}
{
auto hoveredPrimaryAsciiPixmap = hoveredPrimaryTemplatePixmap;
auto painter = QPainter(&hoveredPrimaryAsciiPixmap);
painter.setFont(font);
painter.setPen(asciiValue.has_value() ? asciiFontColor : fadedFontColor);
painter.drawText(byteItemRect, Qt::AlignCenter, asciiValue.value_or(hexValue));
ByteItem::hoveredPrimaryAsciiPixmapsByValue.emplace_back(std::move(hoveredPrimaryAsciiPixmap));
}
}
{
ByteItem::missingDataPixmap = standardTemplatePixmap;
auto painter = QPainter(&ByteItem::missingDataPixmap.value());
painter.setFont(font);
painter.setPen(standardFontColor);
painter.drawText(byteItemRect, Qt::AlignCenter, "??");
}
{
ByteItem::selectedMissingDataPixmap = selectedTemplatePixmap;
auto painter = QPainter(&ByteItem::selectedMissingDataPixmap.value());
painter.setFont(font);
painter.setPen(standardFontColor);
painter.drawText(byteItemRect, Qt::AlignCenter, "??");
}
ByteItem::pixmapCachesGenerated = true;
}
{}
}

View File

@@ -14,50 +14,23 @@ namespace Bloom::Widgets
class ByteItem: public HexViewerItem
{
public:
static constexpr int WIDTH = 27;
static constexpr int HEIGHT = 21;
static constexpr int WIDTH = 28;
static constexpr int HEIGHT = 22;
static constexpr int RIGHT_MARGIN = 5;
static constexpr int BOTTOM_MARGIN = 5;
static constexpr int RIGHT_MARGIN = 6;
static constexpr int BOTTOM_MARGIN = 6;
bool selected = false;
bool excluded = false;
bool grouped = false;
bool stackMemory = false;
bool changed = false;
bool selected:1 = false;
bool excluded:1 = false;
bool grouped:1 = false;
bool stackMemory:1 = false;
bool changed:1 = false;
explicit ByteItem(Targets::TargetMemoryAddress address);
QSize size() const override {
return QSize(ByteItem::WIDTH, ByteItem::HEIGHT);
}
void paint(
QPainter* painter,
const HexViewerSharedState* hexViewerState,
const QGraphicsItem* graphicsItem
) const override;
private:
static inline std::atomic<bool> pixmapCachesGenerated = false;
static inline std::mutex pixmapCacheMutex;
static inline std::vector<QPixmap> standardPixmapsByValue = {};
static inline std::vector<QPixmap> selectedPixmapsByValue = {};
static inline std::vector<QPixmap> groupedPixmapsByValue = {};
static inline std::vector<QPixmap> stackMemoryPixmapsByValue = {};
static inline std::vector<QPixmap> changedMemoryPixmapsByValue = {};
static inline std::vector<QPixmap> standardAsciiPixmapsByValue = {};
static inline std::vector<QPixmap> selectedAsciiPixmapsByValue = {};
static inline std::vector<QPixmap> groupedAsciiPixmapsByValue = {};
static inline std::vector<QPixmap> stackMemoryAsciiPixmapsByValue = {};
static inline std::vector<QPixmap> changedMemoryAsciiPixmapsByValue = {};
static inline std::vector<QPixmap> hoveredPrimaryPixmapsByValue = {};
static inline std::vector<QPixmap> hoveredPrimaryAsciiPixmapsByValue = {};
static inline std::optional<QPixmap> missingDataPixmap = {};
static inline std::optional<QPixmap> selectedMissingDataPixmap = {};
static void generatePixmapCaches();
};
#pragma pack(pop)
}

View File

@@ -1,7 +1,6 @@
#include "FocusedRegionGroupItem.hpp"
#include <cassert>
#include <QColor>
namespace Bloom::Widgets
{
@@ -137,29 +136,6 @@ namespace Bloom::Widgets
}
}
void FocusedRegionGroupItem::paint(
QPainter* painter,
const HexViewerSharedState* hexViewerState,
const QGraphicsItem* graphicsItem
) const {
if (!hexViewerState->settings.displayAnnotations) {
return;
}
painter->setRenderHints(QPainter::RenderHint::Antialiasing, false);
if (!graphicsItem->isEnabled()) {
painter->setOpacity(0.5);
}
this->paintRegionNameAnnotation(painter, hexViewerState, graphicsItem);
if (this->focusedMemoryRegion.dataType != MemoryRegionDataType::UNKNOWN) {
// Paint the value annotation
this->paintValueAnnotation(painter, hexViewerState, graphicsItem);
}
}
QMargins FocusedRegionGroupItem::groupMargins(
const HexViewerSharedState* hexViewerState,
const int maximumWidth
@@ -198,185 +174,4 @@ namespace Bloom::Widgets
return QMargins(0, 0, 0, 0);
}
void FocusedRegionGroupItem::paintRegionNameAnnotation(
QPainter* painter,
const HexViewerSharedState* hexViewerState,
const QGraphicsItem* graphicsItem
) const {
auto labelText = this->focusedMemoryRegion.name;
static constexpr auto lineColor = QColor(0x4F, 0x4F, 0x4F);
static constexpr auto labelFontColor = QColor(0x68, 0x68, 0x68);
static auto labelFont = QFont("'Ubuntu', sans-serif");
labelFont.setPixelSize(12);
painter->setFont(labelFont);
const auto groupWidth = this->groupSize.width();
const auto fontMetrics = painter->fontMetrics();
auto labelSize = fontMetrics.size(Qt::TextSingleLine, labelText);
if (labelSize.width() > groupWidth) {
labelSize.setWidth(groupWidth);
labelText = fontMetrics.elidedText(
labelText,
Qt::TextElideMode::ElideRight,
groupWidth
);
}
const auto heightOffset = this->groupSize.height() - FocusedRegionGroupItem::ANNOTATION_HEIGHT + 4;
const auto verticalLineYStart = static_cast<int>(heightOffset);
const auto verticalLineYEnd = static_cast<int>(heightOffset + 5);
const auto labelRect = QRect(
(groupWidth - labelSize.width()) / 2,
verticalLineYEnd + 10,
labelSize.width(),
labelSize.height()
);
painter->setPen(lineColor);
if (this->focusedMemoryRegion.addressRange.startAddress != this->focusedMemoryRegion.addressRange.endAddress) {
const auto lineStartX = this->items.front()->relativePosition.x() + (ByteItem::WIDTH / 2);
const auto lineEndX = this->multiLine
? groupWidth - + (ByteItem::WIDTH / 2)
: this->items.back()->relativePosition.x() + (ByteItem::WIDTH / 2);
painter->drawLine(QLine(
lineStartX,
verticalLineYStart,
lineStartX,
verticalLineYEnd
));
painter->drawLine(QLine(
lineEndX,
verticalLineYStart,
lineEndX,
verticalLineYEnd
));
painter->drawLine(QLine(
lineStartX,
verticalLineYEnd,
lineEndX,
verticalLineYEnd
));
}
painter->drawLine(QLine(
groupWidth / 2,
verticalLineYEnd,
groupWidth / 2,
verticalLineYEnd + 4
));
painter->setPen(labelFontColor);
painter->drawText(labelRect, Qt::AlignCenter, labelText);
}
void FocusedRegionGroupItem::paintValueAnnotation(
QPainter* painter,
const HexViewerSharedState* hexViewerState,
const QGraphicsItem* graphicsItem
) const {
using Targets::TargetMemoryEndianness;
auto labelText = this->valueLabel.value_or("??");
static const auto lineColor = QColor(0x4F, 0x4F, 0x4F);
static const auto labelFontColor = QColor(0x94, 0x6F, 0x30);
static auto labelFont = QFont("'Ubuntu', sans-serif");
labelFont.setPixelSize(12);
labelFont.setItalic(true);
painter->setFont(labelFont);
const auto groupWidth = this->groupSize.width();
const auto fontMetrics = painter->fontMetrics();
auto labelSize = fontMetrics.size(Qt::TextSingleLine, labelText);
if (labelSize.width() > groupWidth) {
labelSize.setWidth(groupWidth);
labelText = fontMetrics.elidedText(
labelText,
Qt::TextElideMode::ElideRight,
groupWidth
);
}
const auto heightOffset = FocusedRegionGroupItem::ANNOTATION_HEIGHT - 4;
const auto verticalLineYStart = static_cast<int>(heightOffset - 5);
const auto verticalLineYEnd = static_cast<int>(heightOffset);
const auto labelRect = QRect(
(groupWidth - labelSize.width()) / 2,
verticalLineYStart - 10 - labelSize.height(),
labelSize.width(),
labelSize.height()
);
painter->setPen(lineColor);
if (this->focusedMemoryRegion.addressRange.startAddress != this->focusedMemoryRegion.addressRange.endAddress) {
const auto lineStartX = this->items.front()->relativePosition.x() + (ByteItem::WIDTH / 2);
const auto lineEndX = this->multiLine
? groupWidth - + (ByteItem::WIDTH / 2)
: this->items.back()->relativePosition.x() + (ByteItem::WIDTH / 2);
painter->drawLine(QLine(
lineStartX,
verticalLineYStart,
lineStartX,
verticalLineYEnd
));
painter->drawLine(QLine(
lineEndX,
verticalLineYStart,
lineEndX,
verticalLineYEnd
));
painter->drawLine(QLine(
lineStartX,
verticalLineYStart,
lineEndX,
verticalLineYStart
));
/*
* Draw a circle just above the first byte item of the region, to indicate the first byte used to generate
* the value (which will depend on the configured endianness of the region).
*/
painter->setBrush(lineColor);
constexpr auto radius = 2;
painter->drawEllipse(
QPoint(
this->focusedMemoryRegion.endianness == TargetMemoryEndianness::BIG ? lineStartX : lineEndX,
!this->multiLine || this->focusedMemoryRegion.endianness == TargetMemoryEndianness::BIG
? verticalLineYStart
: this->groupSize.height() - FocusedRegionGroupItem::ANNOTATION_HEIGHT + 4 + 5
),
radius,
radius
);
}
painter->drawLine(QLine(
groupWidth / 2,
verticalLineYStart - 4,
groupWidth / 2,
verticalLineYStart
));
painter->setPen(labelFontColor);
painter->drawText(labelRect, Qt::AlignCenter, labelText);
}
}

View File

@@ -14,6 +14,11 @@ namespace Bloom::Widgets
class FocusedRegionGroupItem: public GroupItem
{
public:
static constexpr int ANNOTATION_HEIGHT = 35;
const FocusedMemoryRegion& focusedMemoryRegion;
std::optional<QString> valueLabel;
FocusedRegionGroupItem(
const FocusedMemoryRegion& focusedRegion,
std::unordered_map<Targets::TargetMemoryAddress, ByteItem>& byteItemsByAddress,
@@ -24,31 +29,7 @@ namespace Bloom::Widgets
void refreshValue(const HexViewerSharedState& hexViewerState);
void paint(
QPainter* painter,
const HexViewerSharedState* hexViewerState,
const QGraphicsItem* graphicsItem
) const override;
protected:
QMargins groupMargins(const HexViewerSharedState* hexViewerState, const int maximumWidth) const override;
private:
static constexpr int ANNOTATION_HEIGHT = 35;
const FocusedMemoryRegion& focusedMemoryRegion;
std::optional<QString> valueLabel;
__attribute__((always_inline)) inline void paintRegionNameAnnotation(
QPainter* painter,
const HexViewerSharedState* hexViewerState,
const QGraphicsItem* graphicsItem
) const;
__attribute__((always_inline)) inline void paintValueAnnotation(
QPainter* painter,
const HexViewerSharedState* hexViewerState,
const QGraphicsItem* graphicsItem
) const;
};
}

View File

@@ -1,69 +0,0 @@
#pragma once
#include <QGraphicsItem>
#include <QPainter>
#include "HexViewerItem.hpp"
#include "HexViewerSharedState.hpp"
namespace Bloom::Widgets
{
class GraphicsItem: public QGraphicsItem
{
public:
HexViewerItem* hexViewerItem = nullptr;
GraphicsItem(const HexViewerSharedState* hexViewerState)
: hexViewerState(hexViewerState)
{
this->setAcceptHoverEvents(true);
this->setCacheMode(QGraphicsItem::CacheMode::NoCache);
}
~GraphicsItem() {
if (this->hexViewerItem != nullptr) {
this->hexViewerItem->allocatedGraphicsItem = nullptr;
}
}
void setHexViewerItem(HexViewerItem* item) {
if (this->hexViewerItem != nullptr) {
this->hexViewerItem->allocatedGraphicsItem = nullptr;
}
this->hexViewerItem = item;
if (this->hexViewerItem == nullptr) {
this->setVisible(false);
return;
}
if (this->hexViewerItem->allocatedGraphicsItem != nullptr) {
this->hexViewerItem->allocatedGraphicsItem->setHexViewerItem(nullptr);
}
this->hexViewerItem->allocatedGraphicsItem = this;
this->setPos(this->hexViewerItem->position());
this->setVisible(true);
this->update();
}
[[nodiscard]] QRectF boundingRect() const override {
if (this->hexViewerItem != nullptr) {
const auto itemSize = this->hexViewerItem->size();
return QRectF(0, 0, itemSize.width(), itemSize.height());
}
return QRectF();
}
void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override {
if (this->hexViewerItem != nullptr) {
return this->hexViewerItem->paint(painter, this->hexViewerState, this);
}
}
private:
const HexViewerSharedState* hexViewerState;
};
}

View File

@@ -17,6 +17,8 @@ namespace Bloom::Widgets
{
public:
std::vector<HexViewerItem*> items;
QSize groupSize = {};
bool multiLine = false;
~GroupItem();
@@ -28,18 +30,7 @@ namespace Bloom::Widgets
[[nodiscard]] std::vector<HexViewerItem*> flattenedItems() const;
void paint(
QPainter* painter,
const HexViewerSharedState* hexViewerState,
const QGraphicsItem* graphicsItem
) const override {
return;
}
protected:
QSize groupSize = {};
bool multiLine = false;
GroupItem(
Targets::TargetMemoryAddress startAddress,
HexViewerItem* parent = nullptr

View File

@@ -1,7 +1,5 @@
#include "HexViewerItem.hpp"
#include "GraphicsItem.hpp"
namespace Bloom::Widgets
{
HexViewerItem::HexViewerItem(Targets::TargetMemoryAddress startAddress, HexViewerItem* parent)
@@ -9,12 +7,6 @@ namespace Bloom::Widgets
, parent(parent)
{}
HexViewerItem::~HexViewerItem() {
if (this->allocatedGraphicsItem != nullptr) {
this->allocatedGraphicsItem->setHexViewerItem(nullptr);
}
}
QPoint HexViewerItem::position() const {
if (this->parent != nullptr) {
return this->parent->position() + this->relativePosition;

View File

@@ -18,26 +18,15 @@ namespace Bloom::Widgets
static constexpr int RIGHT_MARGIN = 5;
static constexpr int BOTTOM_MARGIN = 5;
HexViewerItem* parent = nullptr;
const Targets::TargetMemoryAddress startAddress = 0;
QPoint relativePosition = {};
HexViewerItem* parent = nullptr;
GraphicsItem* allocatedGraphicsItem = nullptr;
HexViewerItem(Targets::TargetMemoryAddress startAddress, HexViewerItem* parent = nullptr);
virtual ~HexViewerItem();
QPoint position() const;
virtual QSize size() const = 0;
virtual void paint(
QPainter* painter,
const HexViewerSharedState* hexViewerState,
const QGraphicsItem* graphicsItem
) const = 0;
};
#pragma pack(pop)
}

View File

@@ -0,0 +1,184 @@
#include "HexViewerItemIndex.hpp"
#include <cassert>
#include <cmath>
#include "src/Logger/Logger.hpp"
namespace Bloom::Widgets
{
HexViewerItemIndex::HexViewerItemIndex(
const TopLevelGroupItem* topLevelGroupItem,
const QGraphicsScene* hexViewerScene
)
: topLevelGroupItem(topLevelGroupItem)
, hexViewerScene(hexViewerScene)
{
this->refreshFlattenedItems();
}
HexViewerItemIndex::ItemRangeType HexViewerItemIndex::items(int yStart, int yEnd) const {
assert(!this->byteItemGrid.empty());
const auto gridPointCount = this->byteItemGrid.size();
const auto startGridPointIndex = static_cast<decltype(this->byteItemGrid)::size_type>(
std::floor(
static_cast<float>(yStart) / static_cast<float>(HexViewerItemIndex::GRID_SIZE)
)
);
if (startGridPointIndex >= gridPointCount) {
return HexViewerItemIndex::ItemRangeType();
}
const auto endGridPointIndex = static_cast<decltype(this->byteItemGrid)::size_type>(std::min(
static_cast<decltype(this->byteItemGrid)::size_type>(
std::ceil(
static_cast<float>(yEnd) / static_cast<float>(HexViewerItemIndex::GRID_SIZE)
)
),
gridPointCount - 1
));
return HexViewerItemIndex::ItemRangeType(
this->byteItemGrid[startGridPointIndex],
endGridPointIndex == gridPointCount - 1
? this->flattenedItems.end()
: this->byteItemGrid[endGridPointIndex]
);
}
ByteItem* HexViewerItemIndex::byteItemAt(const QPointF& position) const {
const auto gridPointCount = this->byteItemGrid.size();
const auto startGridPointIndex = static_cast<decltype(this->byteItemGrid)::size_type>(
std::floor(
static_cast<float>(std::max(position.y(), static_cast<qreal>(0)))
/ static_cast<float>(HexViewerItemIndex::GRID_SIZE)
)
);
if (startGridPointIndex >= gridPointCount) {
return nullptr;
}
const auto startItemIt = this->byteItemGrid[startGridPointIndex];
for (auto itemIt = startItemIt; itemIt < this->flattenedItems.end(); ++itemIt) {
auto* item = *itemIt;
const auto itemPosition = item->position();
if (itemPosition.y() > position.y()) {
break;
}
const auto itemSize = item->size();
if (
(itemPosition.y() + itemSize.height()) < position.y()
|| itemPosition.x() > position.x()
|| (itemPosition.x() + itemSize.width()) < position.x()
) {
continue;
}
auto* byteItem = dynamic_cast<ByteItem*>(item);
if (byteItem != nullptr) {
return byteItem;
}
}
return nullptr;
}
ByteItem* HexViewerItemIndex::closestByteItem(int yPosition) const {
assert(!this->byteItemGrid.empty());
const auto gridPointCount = this->byteItemGrid.size();
const auto gridPointIndex = static_cast<decltype(this->byteItemGrid)::size_type>(
std::round(
static_cast<float>(yPosition) / static_cast<float>(HexViewerItemIndex::GRID_SIZE)
)
);
// Sanity check
assert(gridPointCount > gridPointIndex);
return static_cast<ByteItem*>(*(this->byteItemGrid[gridPointIndex]));
}
std::vector<ByteItem*> HexViewerItemIndex::intersectingByteItems(const QRectF& rect) const {
auto output = std::vector<ByteItem*>();
const auto yStart = static_cast<int>(std::max(rect.y(), static_cast<qreal>(0)));
const auto yEnd = static_cast<int>(std::max(rect.y() + rect.height(), static_cast<qreal>(0)));
const auto items = this->items(yStart, yEnd);
for (auto& item : items) {
const auto itemRect = QRectF(item->position(), item->size());
if (itemRect.y() > yEnd) {
break;
}
if (!itemRect.intersects(rect)) {
continue;
}
auto* byteItem = dynamic_cast<ByteItem*>(item);
if (byteItem != nullptr) {
output.push_back(byteItem);
}
}
return output;
}
void HexViewerItemIndex::refreshFlattenedItems() {
this->flattenedItems = this->topLevelGroupItem->flattenedItems();
}
void HexViewerItemIndex::refreshIndex() {
const auto pointsRequired = static_cast<std::uint32_t>(
this->hexViewerScene->sceneRect().height() / HexViewerItemIndex::GRID_SIZE
);
this->byteItemGrid.clear();
this->byteItemGrid.reserve(pointsRequired);
this->byteItemLines.clear();
this->byteItemYStartPositionsByAddress.clear();
auto currentByteItemGridPoint = 0;
auto currentLineYPosition = 0;
for (auto itemIt = this->flattenedItems.begin(); itemIt != this->flattenedItems.end(); ++itemIt) {
auto& item = *itemIt;
const auto byteItem = dynamic_cast<const ByteItem*>(item);
if (byteItem == nullptr) {
continue;
}
const auto itemYStartPosition = byteItem->position().y();
const auto itemYEndPosition = itemYStartPosition + byteItem->size().height();
this->byteItemYStartPositionsByAddress.emplace(byteItem->startAddress, itemYStartPosition);
if (itemYStartPosition > currentLineYPosition) {
this->byteItemLines.push_back(byteItem);
currentLineYPosition = itemYStartPosition;
}
if (itemYEndPosition >= currentByteItemGridPoint) {
// This byte item is the first to exceed or intersect with the currentByteItemGridPoint
this->byteItemGrid.push_back(itemIt);
currentByteItemGridPoint += HexViewerItemIndex::GRID_SIZE;
}
}
}
}

View File

@@ -0,0 +1,108 @@
#pragma once
#include <vector>
#include <ranges>
#include <QGraphicsScene>
#include <QPointF>
#include <unordered_map>
#include "HexViewerItem.hpp"
#include "TopLevelGroupItem.hpp"
#include "ByteItem.hpp"
namespace Bloom::Widgets
{
/**
* This class maintains indices of hex viewer item positions and provides fast lookups for items within certain
* positions.
*/
class HexViewerItemIndex
{
public:
using FlattenedItemType = std::vector<HexViewerItem*>;
using FlattenedItemItType = FlattenedItemType::const_iterator;
using ItemRangeType = std::ranges::subrange<FlattenedItemItType>;
std::vector<const ByteItem*> byteItemLines;
std::unordered_map<Targets::TargetMemoryAddress, int> byteItemYStartPositionsByAddress;
explicit HexViewerItemIndex(
const TopLevelGroupItem* topLevelGroupItem,
const QGraphicsScene* hexViewerScene
);
/**
* Identifies the items between two points on the Y axis, and returns them in the form of a subrange, in
* constant-time.
*
* This member function can return items that are outside of the range, by HexViewerItemIndex::GRID_SIZE.
* The caller should tolerate this.
*
* CAUTION: The returned range can be invalidated! This member function should only be used immediately before
* you intend to do work on the returned range. Do **NOT** keep hold of the returned range. You should consider
* any data returned by this function to be invalid as soon as program control has returned to the main event
* loop.
*
* @param yStart
* @param yEnd
*
* @return
*/
ItemRangeType items(int yStart, int yEnd) const;
/**
* Returns the byte item at the given position. Byte items do not overlap.
*
* @param position
* @return
*/
ByteItem* byteItemAt(const QPointF& position) const;
/**
* Returns the closest byte item from the given position on the Y-axis.
* *
* @param yPosition
* @return
*/
ByteItem* closestByteItem(int yPosition) const;
/**
* Returns all byte items that intersect with the given rectangle.
*
* @param rect
* @return
*/
std::vector<ByteItem*> intersectingByteItems(const QRectF& rect) const;
void refreshFlattenedItems();
void refreshIndex();
protected:
static constexpr auto GRID_SIZE = 100;
const TopLevelGroupItem* topLevelGroupItem;
const QGraphicsScene* hexViewerScene;
/**
* An std::vector of all HexViewerItems along with their parents and children, sorted by position.
*
* Some of the lookup member functions return subranges from this container.
*/
FlattenedItemType flattenedItems;
/**
* Byte item Y-axis grid (one-dimensional index)
*
* Each element in this std::vector represents a point on the Y-axis grid. The distance between each point is
* equal to HexViewerItemIndex::GRID_SIZE.
*
* The value of each element is an iterator addressing the HexViewerItem* at that point on the grid.
*
* Although the iterators address hex viewer items, all elements can be safely cast to ByteItem*, as we only
* consider byte items when populating this grid. See HexViewerItemIndex::refreshIndex() for more.
*
* We use an std::vector here because it provides constant-time access to any element.
*/
std::vector<FlattenedItemItType> byteItemGrid;
};
}

View File

@@ -0,0 +1,723 @@
#include "HexViewerItemRenderer.hpp"
#include <QScrollBar>
#include <QColor>
namespace Bloom::Widgets
{
HexViewerItemRenderer::HexViewerItemRenderer(
const HexViewerSharedState& hexViewerState,
const HexViewerItemIndex& itemIndex,
const QGraphicsView* view
)
: hexViewerState(hexViewerState)
, itemIndex(itemIndex)
, view(view)
, viewport(view->viewport())
{
this->setAcceptHoverEvents(true);
this->setCacheMode(QGraphicsItem::CacheMode::NoCache);
if (!HexViewerItemRenderer::pixmapCachesGenerated) {
HexViewerItemRenderer::generatePixmapCaches();
}
}
void HexViewerItemRenderer::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) {
const auto vScrollBarValue = this->view->verticalScrollBar()->value();
const auto visibleItems = this->itemIndex.items(
vScrollBarValue,
vScrollBarValue + this->viewport->size().height()
);
painter->setRenderHints(QPainter::RenderHint::Antialiasing, false);
// Paint the ancestors of the first visible item
const auto& firstItem = *(visibleItems.begin());
auto* parentItem = firstItem->parent;
while (parentItem != nullptr) {
painter->setOpacity(1);
this->paintItem(parentItem, painter);
parentItem = parentItem->parent;
}
for (auto& item : visibleItems) {
painter->setOpacity(1);
this->paintItem(item, painter);
}
}
void HexViewerItemRenderer::paintItem(const HexViewerItem* item, QPainter* painter) {
const auto* byteItem = dynamic_cast<const ByteItem*>(item);
if (byteItem != nullptr) {
return this->paintByteItem(byteItem, painter);
}
const auto* focusedRegionItem = dynamic_cast<const FocusedRegionGroupItem*>(item);
if (focusedRegionItem != nullptr) {
return this->paintFocusedRegionGroupItem(focusedRegionItem, painter);
}
const auto* stackMemoryItem = dynamic_cast<const StackMemoryGroupItem*>(item);
if (stackMemoryItem != nullptr) {
return this->paintStackMemoryGroupItem(stackMemoryItem, painter);
}
}
void HexViewerItemRenderer::paintByteItem(const ByteItem* item, QPainter* painter) {
const auto position = item->position();
const auto boundingRect = QRect(position.x(), position.y(), ByteItem::WIDTH, ByteItem::HEIGHT);
painter->setOpacity(!this->isEnabled() || (item->excluded && !item->selected) ? 0.6 : 1);
if (item->excluded || !this->hexViewerState.data.has_value()) {
if (item->selected) {
painter->drawPixmap(boundingRect, HexViewerItemRenderer::selectedMissingDataPixmap.value());
return;
}
painter->drawPixmap(boundingRect, HexViewerItemRenderer::missingDataPixmap.value());
return;
}
const auto byteIndex = item->startAddress - this->hexViewerState.memoryDescriptor.addressRange.startAddress;
const auto value = (*(this->hexViewerState.data))[byteIndex];
const auto hoveredPrimary = this->hexViewerState.hoveredByteItem == item;
if (this->hexViewerState.settings.displayAsciiValues) {
if (item->selected) {
painter->drawPixmap(
boundingRect,
HexViewerItemRenderer::selectedAsciiPixmapsByValue[value]
);
return;
}
if (item->changed) {
painter->drawPixmap(
boundingRect,
HexViewerItemRenderer::changedMemoryAsciiPixmapsByValue[value]
);
return;
}
if (item->stackMemory && this->hexViewerState.settings.groupStackMemory) {
painter->drawPixmap(
boundingRect,
HexViewerItemRenderer::stackMemoryAsciiPixmapsByValue[value]
);
return;
}
if (item->grouped && this->hexViewerState.settings.highlightFocusedMemory) {
painter->drawPixmap(
boundingRect,
HexViewerItemRenderer::groupedAsciiPixmapsByValue[value]
);
return;
}
if (hoveredPrimary) {
painter->drawPixmap(
boundingRect,
HexViewerItemRenderer::hoveredPrimaryAsciiPixmapsByValue[value]
);
return;
}
painter->drawPixmap(boundingRect, HexViewerItemRenderer::standardAsciiPixmapsByValue[value]);
return;
}
if (item->selected) {
painter->drawPixmap(
boundingRect,
HexViewerItemRenderer::selectedPixmapsByValue[value]
);
return;
}
if (item->changed) {
painter->drawPixmap(
boundingRect,
HexViewerItemRenderer::changedMemoryPixmapsByValue[value]
);
return;
}
if (item->stackMemory && this->hexViewerState.settings.groupStackMemory) {
painter->drawPixmap(
boundingRect,
HexViewerItemRenderer::stackMemoryPixmapsByValue[value]
);
return;
}
if (item->grouped && this->hexViewerState.settings.highlightFocusedMemory) {
painter->drawPixmap(
boundingRect,
HexViewerItemRenderer::groupedPixmapsByValue[value]
);
return;
}
if (hoveredPrimary) {
painter->drawPixmap(
boundingRect,
HexViewerItemRenderer::hoveredPrimaryPixmapsByValue[value]
);
return;
}
painter->drawPixmap(
boundingRect,
HexViewerItemRenderer::standardPixmapsByValue[value]
);
}
void HexViewerItemRenderer::paintFocusedRegionGroupItem(const FocusedRegionGroupItem* item, QPainter* painter) {
if (!this->hexViewerState.settings.displayAnnotations) {
return;
}
const auto position = item->position();
// The region name label
{
auto labelText = item->focusedMemoryRegion.name;
static constexpr auto lineColor = QColor(0x4F, 0x4F, 0x4F);
static constexpr auto labelFontColor = QColor(0x68, 0x68, 0x68);
static auto labelFont = QFont("'Ubuntu', sans-serif");
labelFont.setPixelSize(12);
painter->setFont(labelFont);
const auto groupWidth = item->groupSize.width();
const auto fontMetrics = painter->fontMetrics();
auto labelSize = fontMetrics.size(Qt::TextSingleLine, labelText);
if (labelSize.width() > groupWidth) {
labelSize.setWidth(groupWidth);
labelText = fontMetrics.elidedText(
labelText,
Qt::TextElideMode::ElideRight,
groupWidth
);
}
const auto heightOffset = item->groupSize.height() - FocusedRegionGroupItem::ANNOTATION_HEIGHT + 4;
const auto verticalLineYStart = position.y() + static_cast<int>(heightOffset);
const auto verticalLineYEnd = position.y() + static_cast<int>(heightOffset + 5);
const auto labelRect = QRect(
position.x() + (groupWidth - labelSize.width()) / 2,
verticalLineYEnd + 10,
labelSize.width(),
labelSize.height()
);
painter->setPen(lineColor);
if (item->focusedMemoryRegion.addressRange.startAddress !=
item->focusedMemoryRegion.addressRange.endAddress) {
const auto lineStartX =
position.x() + item->items.front()->relativePosition.x() + (ByteItem::WIDTH / 2);
const auto lineEndX = item->multiLine
? position.x() + groupWidth - (ByteItem::WIDTH / 2)
: position.x() + item->items.back()->relativePosition.x() + (ByteItem::WIDTH / 2);
painter->drawLine(QLine(
lineStartX,
verticalLineYStart,
lineStartX,
verticalLineYEnd
));
painter->drawLine(QLine(
lineEndX,
verticalLineYStart,
lineEndX,
verticalLineYEnd
));
painter->drawLine(QLine(
lineStartX,
verticalLineYEnd,
lineEndX,
verticalLineYEnd
));
}
painter->drawLine(QLine(
position.x() + groupWidth / 2,
verticalLineYEnd,
position.x() + groupWidth / 2,
verticalLineYEnd + 4
));
painter->setPen(labelFontColor);
painter->drawText(labelRect, Qt::AlignCenter, labelText);
}
// The value label
if (item->focusedMemoryRegion.dataType != MemoryRegionDataType::UNKNOWN) {
using Targets::TargetMemoryEndianness;
auto labelText = item->valueLabel.value_or("??");
static const auto lineColor = QColor(0x4F, 0x4F, 0x4F);
static const auto labelFontColor = QColor(0x94, 0x6F, 0x30);
static auto labelFont = QFont("'Ubuntu', sans-serif");
labelFont.setPixelSize(12);
labelFont.setItalic(true);
painter->setFont(labelFont);
const auto groupWidth = item->groupSize.width();
const auto fontMetrics = painter->fontMetrics();
auto labelSize = fontMetrics.size(Qt::TextSingleLine, labelText);
if (labelSize.width() > groupWidth) {
labelSize.setWidth(groupWidth);
labelText = fontMetrics.elidedText(
labelText,
Qt::TextElideMode::ElideRight,
groupWidth
);
}
const auto heightOffset = FocusedRegionGroupItem::ANNOTATION_HEIGHT - 4;
const auto verticalLineYStart = position.y() + static_cast<int>(heightOffset - 5);
const auto verticalLineYEnd = position.y() + static_cast<int>(heightOffset);
const auto labelRect = QRect(
position.x() + (groupWidth - labelSize.width()) / 2,
verticalLineYStart - 10 - labelSize.height(),
labelSize.width(),
labelSize.height()
);
painter->setPen(lineColor);
if (item->focusedMemoryRegion.addressRange.startAddress != item->focusedMemoryRegion.addressRange.endAddress) {
const auto lineStartX = position.x() + item->items.front()->relativePosition.x() + (ByteItem::WIDTH / 2);
const auto lineEndX = item->multiLine
? position.x() + groupWidth - + (ByteItem::WIDTH / 2)
: position.x() + item->items.back()->relativePosition.x() + (ByteItem::WIDTH / 2);
painter->drawLine(QLine(
lineStartX,
verticalLineYStart,
lineStartX,
verticalLineYEnd
));
painter->drawLine(QLine(
lineEndX,
verticalLineYStart,
lineEndX,
verticalLineYEnd
));
painter->drawLine(QLine(
lineStartX,
verticalLineYStart,
lineEndX,
verticalLineYStart
));
/*
* Draw a circle just above the first byte item of the region, to indicate the first byte used to generate
* the value (which will depend on the configured endianness of the region).
*/
painter->setBrush(lineColor);
constexpr auto radius = 2;
painter->drawEllipse(
QPoint(
item->focusedMemoryRegion.endianness == TargetMemoryEndianness::BIG ? lineStartX : lineEndX,
!item->multiLine || item->focusedMemoryRegion.endianness == TargetMemoryEndianness::BIG
? verticalLineYStart
: position.y() + item->groupSize.height() - FocusedRegionGroupItem::ANNOTATION_HEIGHT + 4 + 5
),
radius,
radius
);
}
painter->drawLine(QLine(
position.x() + groupWidth / 2,
verticalLineYStart - 4,
position.x() + groupWidth / 2,
verticalLineYStart
));
painter->setPen(labelFontColor);
painter->drawText(labelRect, Qt::AlignCenter, labelText);
}
}
void HexViewerItemRenderer::paintStackMemoryGroupItem(const StackMemoryGroupItem* item, QPainter* painter) {
const auto position = item->position();
const auto headingText = QString("Stack Memory");
const auto stackSize = this->hexViewerState.memoryDescriptor.addressRange.endAddress - item->startAddress + 1;
const auto& memoryCapacity = this->hexViewerState.memoryDescriptor.size();
const auto stackSizeHeadingText = QString("Stack size:");
const auto stackSizeValueText = QString::number(stackSize) + " byte(s) ("
+ QString::number(static_cast<float>(stackSize) / static_cast<float>(memoryCapacity / 100), 'f' , 1)
+ "% of memory capacity)";
const auto stackPointerHeadingText = QString("Stack pointer:");
const auto stackPointerValueText = "0x" + QString::number(
item->stackPointer,
16
).rightJustified(8, '0').toUpper();
static constexpr auto lineColor = QColor(0x4F, 0x4F, 0x4F);
static constexpr auto headingLabelFontColor = QColor(0x6F, 0x6F, 0x6F);
static constexpr auto valueLabelFontColor = QColor(0x94, 0x6F, 0x30, 230);
static auto headingLabelFont = QFont("'Ubuntu', sans-serif");
headingLabelFont.setPixelSize(13);
static auto valueFont = QFont("'Ubuntu', sans-serif");
valueFont.setPixelSize(13);
valueFont.setItalic(true);
painter->setFont(headingLabelFont);
const auto groupWidth = item->groupSize.width();
const auto fontMetrics = painter->fontMetrics();
auto headingLabelSize = fontMetrics.size(Qt::TextSingleLine, headingText);
auto stackSizeHeadingLabelSize = fontMetrics.size(Qt::TextSingleLine, stackSizeHeadingText);
auto stackSizeLabelSize = fontMetrics.size(Qt::TextSingleLine, stackSizeValueText);
auto stackPointerHeadingLabelSize = fontMetrics.size(Qt::TextSingleLine, stackPointerHeadingText);
auto stackPointerLabelSize = fontMetrics.size(Qt::TextSingleLine, stackPointerValueText);
static constexpr auto labelLineHeight = 4;
static constexpr auto labelBottomMargin = 10;
static constexpr auto labelRightMargin = 3;
const auto heightOffset = headingLabelSize.height() + stackSizeHeadingLabelSize.height()
+ stackPointerHeadingLabelSize.height() + (labelBottomMargin * 3) + 15;
const auto verticalLineYStart = static_cast<int>(position.y() + heightOffset - 5);
const auto verticalLineYEnd = static_cast<int>(position.y() + heightOffset);
const auto lineStartX = position.x() + ByteItem::WIDTH / 2;
const auto lineEndX = position.x() + groupWidth - (ByteItem::WIDTH / 2);
const auto labelRect = QRect(
position.x() + (groupWidth - headingLabelSize.width()) / 2,
verticalLineYStart - stackPointerHeadingLabelSize.height() - stackSizeHeadingLabelSize.height()
- headingLabelSize.height() - labelLineHeight - (labelBottomMargin * 2) - 3,
headingLabelSize.width(),
headingLabelSize.height()
);
const auto stackPointerHeadingLabelRect = QRect(
labelRect.left() + (labelRect.width() / 2) - (
(stackPointerHeadingLabelSize.width() + stackPointerLabelSize.width()) / 2
),
labelRect.bottom() + labelBottomMargin,
stackPointerHeadingLabelSize.width(),
stackPointerHeadingLabelSize.height()
);
const auto stackPointerValueLabelRect = QRect(
stackPointerHeadingLabelRect.right() + labelRightMargin,
labelRect.bottom() + labelBottomMargin,
stackPointerLabelSize.width(),
stackPointerLabelSize.height()
);
const auto stackSizeHeadingLabelRect = QRect(
labelRect.left() + (labelRect.width() / 2) - (
(stackSizeHeadingLabelSize.width() + stackSizeLabelSize.width()) / 2
),
stackPointerHeadingLabelRect.bottom() + labelBottomMargin,
stackSizeHeadingLabelSize.width(),
stackSizeHeadingLabelSize.height()
);
const auto stackSizeValueLabelRect = QRect(
stackSizeHeadingLabelRect.right() + labelRightMargin,
stackPointerHeadingLabelRect.bottom() + labelBottomMargin,
stackSizeLabelSize.width(),
stackSizeLabelSize.height()
);
painter->setPen(lineColor);
painter->drawLine(QLine(
lineStartX,
verticalLineYStart,
lineStartX,
verticalLineYEnd
));
painter->drawLine(QLine(
lineEndX,
verticalLineYStart,
lineEndX,
verticalLineYEnd
));
painter->drawLine(QLine(
lineStartX,
verticalLineYStart,
lineEndX,
verticalLineYStart
));
painter->drawLine(QLine(
position.x() + groupWidth / 2,
verticalLineYStart - labelLineHeight,
position.x() + groupWidth / 2,
verticalLineYStart
));
painter->setPen(headingLabelFontColor);
painter->drawText(labelRect, Qt::AlignCenter, headingText);
painter->drawText(stackSizeHeadingLabelRect, Qt::AlignCenter, stackSizeHeadingText);
painter->drawText(stackPointerHeadingLabelRect, Qt::AlignCenter, stackPointerHeadingText);
painter->setFont(valueFont);
painter->setPen(valueLabelFontColor);
painter->drawText(stackSizeValueLabelRect, Qt::AlignCenter, stackSizeValueText);
painter->drawText(stackPointerValueLabelRect, Qt::AlignCenter, stackPointerValueText);
}
void HexViewerItemRenderer::generatePixmapCaches() {
const auto lock = std::unique_lock(HexViewerItemRenderer::pixmapCacheMutex);
if (HexViewerItemRenderer::pixmapCachesGenerated) {
return;
}
static constexpr auto standardBackgroundColor = QColor(0x32, 0x33, 0x30, 0);
static constexpr auto highlightedBackgroundColor = QColor(0x3C, 0x59, 0x5C, 255);
static constexpr auto selectedBackgroundColor = QColor(0x3C, 0x59, 0x5C, 255);
static constexpr auto groupedBackgroundColor = QColor(0x44, 0x44, 0x41, 255);
static constexpr auto stackMemoryBackgroundColor = QColor(0x44, 0x44, 0x41, 200);
static constexpr auto stackMemoryBarColor = QColor(0x67, 0x57, 0x20, 255);
static constexpr auto changedMemoryBackgroundColor = QColor(0x5C, 0x49, 0x5D, 200);
static constexpr auto changedMemoryFadedBackgroundColor = QColor(0x5C, 0x49, 0x5D, 125);
static const auto hoveredStackMemoryBackgroundColor = QColor(
stackMemoryBackgroundColor.red(),
stackMemoryBackgroundColor.green(),
stackMemoryBackgroundColor.blue(),
255
);
static constexpr auto hoveredBackgroundColor = QColor(0x8E, 0x8B, 0x83, 70);
static constexpr auto standardFontColor = QColor(0xAF, 0xB1, 0xB3);
static constexpr auto fadedFontColor = QColor(0xAF, 0xB1, 0xB3, 100);
static constexpr auto asciiFontColor = QColor(0xA7, 0x77, 0x26);
static constexpr auto changedMemoryAsciiFontColor = QColor(0xB7, 0x7F, 0x21);
const auto byteItemRect = QRect(0, 0, ByteItem::WIDTH, ByteItem::HEIGHT);
const auto byteItemSize = byteItemRect.size();
auto standardTemplatePixmap = QPixmap(byteItemSize);
standardTemplatePixmap.fill(standardBackgroundColor);
auto highlightedTemplatePixmap = QPixmap(byteItemSize);
highlightedTemplatePixmap.fill(highlightedBackgroundColor);
auto selectedTemplatePixmap = QPixmap(byteItemSize);
selectedTemplatePixmap.fill(selectedBackgroundColor);
auto groupedTemplatePixmap = QPixmap(byteItemSize);
groupedTemplatePixmap.fill(groupedBackgroundColor);
auto stackMemoryTemplatePixmap = QPixmap(byteItemSize);
stackMemoryTemplatePixmap.fill(stackMemoryBackgroundColor);
{
auto painter = QPainter(&stackMemoryTemplatePixmap);
painter.setBrush(stackMemoryBarColor);
painter.setPen(Qt::PenStyle::NoPen);
painter.drawRect(0, byteItemSize.height() - 3, byteItemSize.width(), 3);
}
auto changedMemoryTemplatePixmap = QPixmap(byteItemSize);
changedMemoryTemplatePixmap.fill(changedMemoryBackgroundColor);
auto changedMemoryFadedTemplatePixmap = QPixmap(byteItemSize);
changedMemoryFadedTemplatePixmap.fill(changedMemoryFadedBackgroundColor);
auto hoveredStackMemoryTemplatePixmap = QPixmap(byteItemSize);
hoveredStackMemoryTemplatePixmap.fill(hoveredStackMemoryBackgroundColor);
auto hoveredPrimaryTemplatePixmap = QPixmap(byteItemSize);
hoveredPrimaryTemplatePixmap.fill(hoveredBackgroundColor);
static auto font = QFont("'Ubuntu', sans-serif", 8);
for (std::uint16_t value = 0x00; value <= 0xFF; ++value) {
const auto hexValue = QString::number(value, 16).rightJustified(2, '0').toUpper();
const auto asciiValue = value >= 32 && value <= 126
? std::optional("'" + QString(QChar(value)) + "'")
: std::nullopt;
{
auto standardPixmap = standardTemplatePixmap;
auto painter = QPainter(&standardPixmap);
painter.setFont(font);
painter.setPen(standardFontColor);
painter.drawText(byteItemRect, Qt::AlignCenter, hexValue);
HexViewerItemRenderer::standardPixmapsByValue.emplace_back(std::move(standardPixmap));
}
{
auto selectedPixmap = selectedTemplatePixmap;
auto painter = QPainter(&selectedPixmap);
painter.setFont(font);
painter.setPen(standardFontColor);
painter.drawText(byteItemRect, Qt::AlignCenter, hexValue);
HexViewerItemRenderer::selectedPixmapsByValue.emplace_back(std::move(selectedPixmap));
}
{
auto groupedPixmap = groupedTemplatePixmap;
auto painter = QPainter(&groupedPixmap);
painter.setFont(font);
painter.setPen(standardFontColor);
painter.drawText(byteItemRect, Qt::AlignCenter, hexValue);
HexViewerItemRenderer::groupedPixmapsByValue.emplace_back(std::move(groupedPixmap));
}
{
auto stackMemoryPixmap = stackMemoryTemplatePixmap;
auto painter = QPainter(&stackMemoryPixmap);
painter.setFont(font);
painter.setPen(standardFontColor);
painter.drawText(byteItemRect, Qt::AlignCenter, hexValue);
HexViewerItemRenderer::stackMemoryPixmapsByValue.emplace_back(std::move(stackMemoryPixmap));
}
{
auto changedMemoryPixmap = changedMemoryTemplatePixmap;
auto painter = QPainter(&changedMemoryPixmap);
painter.setFont(font);
painter.setPen(standardFontColor);
painter.drawText(byteItemRect, Qt::AlignCenter, hexValue);
HexViewerItemRenderer::changedMemoryPixmapsByValue.emplace_back(std::move(changedMemoryPixmap));
}
{
auto hoveredPrimaryPixmap = hoveredPrimaryTemplatePixmap;
auto painter = QPainter(&hoveredPrimaryPixmap);
painter.setFont(font);
painter.setPen(standardFontColor);
painter.drawText(byteItemRect, Qt::AlignCenter, hexValue);
HexViewerItemRenderer::hoveredPrimaryPixmapsByValue.emplace_back(std::move(hoveredPrimaryPixmap));
}
{
auto standardAsciiPixmap = standardTemplatePixmap;
auto painter = QPainter(&standardAsciiPixmap);
painter.setFont(font);
painter.setPen(asciiValue.has_value() ? asciiFontColor : fadedFontColor);
painter.drawText(byteItemRect, Qt::AlignCenter, asciiValue.value_or(hexValue));
HexViewerItemRenderer::standardAsciiPixmapsByValue.emplace_back(std::move(standardAsciiPixmap));
}
{
auto selectedAsciiPixmap = selectedTemplatePixmap;
auto painter = QPainter(&selectedAsciiPixmap);
painter.setFont(font);
painter.setPen(asciiValue.has_value() ? asciiFontColor : fadedFontColor);
painter.drawText(byteItemRect, Qt::AlignCenter, asciiValue.value_or(hexValue));
HexViewerItemRenderer::selectedAsciiPixmapsByValue.emplace_back(std::move(selectedAsciiPixmap));
}
{
auto groupedAsciiPixmap = groupedTemplatePixmap;
auto painter = QPainter(&groupedAsciiPixmap);
painter.setFont(font);
painter.setPen(asciiValue.has_value() ? asciiFontColor : fadedFontColor);
painter.drawText(byteItemRect, Qt::AlignCenter, asciiValue.value_or(hexValue));
HexViewerItemRenderer::groupedAsciiPixmapsByValue.emplace_back(std::move(groupedAsciiPixmap));
}
{
auto stackMemoryAsciiPixmap = stackMemoryTemplatePixmap;
auto painter = QPainter(&stackMemoryAsciiPixmap);
painter.setFont(font);
painter.setPen(asciiValue.has_value() ? asciiFontColor : fadedFontColor);
painter.drawText(byteItemRect, Qt::AlignCenter, asciiValue.value_or(hexValue));
HexViewerItemRenderer::stackMemoryAsciiPixmapsByValue.emplace_back(std::move(stackMemoryAsciiPixmap));
}
{
auto changedMemoryAsciiPixmap = asciiValue.has_value()
? changedMemoryTemplatePixmap
: changedMemoryFadedTemplatePixmap;
auto painter = QPainter(&changedMemoryAsciiPixmap);
painter.setFont(font);
painter.setPen(asciiValue.has_value() ? changedMemoryAsciiFontColor : fadedFontColor);
painter.drawText(byteItemRect, Qt::AlignCenter, asciiValue.value_or(hexValue));
HexViewerItemRenderer::changedMemoryAsciiPixmapsByValue.emplace_back(std::move(changedMemoryAsciiPixmap));
}
{
auto hoveredPrimaryAsciiPixmap = hoveredPrimaryTemplatePixmap;
auto painter = QPainter(&hoveredPrimaryAsciiPixmap);
painter.setFont(font);
painter.setPen(asciiValue.has_value() ? asciiFontColor : fadedFontColor);
painter.drawText(byteItemRect, Qt::AlignCenter, asciiValue.value_or(hexValue));
HexViewerItemRenderer::hoveredPrimaryAsciiPixmapsByValue.emplace_back(std::move(hoveredPrimaryAsciiPixmap));
}
}
{
HexViewerItemRenderer::missingDataPixmap = standardTemplatePixmap;
auto painter = QPainter(&HexViewerItemRenderer::missingDataPixmap.value());
painter.setFont(font);
painter.setPen(standardFontColor);
painter.drawText(byteItemRect, Qt::AlignCenter, "??");
}
{
HexViewerItemRenderer::selectedMissingDataPixmap = selectedTemplatePixmap;
auto painter = QPainter(&HexViewerItemRenderer::selectedMissingDataPixmap.value());
painter.setFont(font);
painter.setPen(standardFontColor);
painter.drawText(byteItemRect, Qt::AlignCenter, "??");
}
HexViewerItemRenderer::pixmapCachesGenerated = true;
}
}

View File

@@ -0,0 +1,82 @@
#pragma once
#include <QGraphicsItem>
#include <QSize>
#include <QGraphicsView>
#include <QWidget>
#include <QPainter>
#include <atomic>
#include <mutex>
#include <QPixmap>
#include <vector>
#include <optional>
#include "HexViewerItemIndex.hpp"
#include "HexViewerSharedState.hpp"
#include "HexViewerItem.hpp"
#include "TopLevelGroupItem.hpp"
#include "ByteItem.hpp"
#include "FocusedRegionGroupItem.hpp"
#include "StackMemoryGroupItem.hpp"
namespace Bloom::Widgets
{
/**
* Renders hex viewer items in a QGraphicsScene.
*/
class HexViewerItemRenderer: public QGraphicsItem
{
public:
QSize size;
HexViewerItemRenderer(
const HexViewerSharedState& hexViewerState,
const HexViewerItemIndex& itemIndex,
const QGraphicsView* view
);
[[nodiscard]] QRectF boundingRect() const override {
return QRectF(0, 0, this->size.width(), this->size.height());
}
void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override;
protected:
const HexViewerSharedState& hexViewerState;
const HexViewerItemIndex& itemIndex;
const QGraphicsView* view;
const QWidget* viewport;
static inline std::atomic<bool> pixmapCachesGenerated = false;
static inline std::mutex pixmapCacheMutex;
static inline std::vector<QPixmap> standardPixmapsByValue = {};
static inline std::vector<QPixmap> selectedPixmapsByValue = {};
static inline std::vector<QPixmap> groupedPixmapsByValue = {};
static inline std::vector<QPixmap> stackMemoryPixmapsByValue = {};
static inline std::vector<QPixmap> changedMemoryPixmapsByValue = {};
static inline std::vector<QPixmap> standardAsciiPixmapsByValue = {};
static inline std::vector<QPixmap> selectedAsciiPixmapsByValue = {};
static inline std::vector<QPixmap> groupedAsciiPixmapsByValue = {};
static inline std::vector<QPixmap> stackMemoryAsciiPixmapsByValue = {};
static inline std::vector<QPixmap> changedMemoryAsciiPixmapsByValue = {};
static inline std::vector<QPixmap> hoveredPrimaryPixmapsByValue = {};
static inline std::vector<QPixmap> hoveredPrimaryAsciiPixmapsByValue = {};
static inline std::optional<QPixmap> missingDataPixmap = {};
static inline std::optional<QPixmap> selectedMissingDataPixmap = {};
void paintItem(const HexViewerItem* item, QPainter* painter);
inline void paintByteItem(const ByteItem* item, QPainter* painter) __attribute__((__always_inline__));
inline void paintFocusedRegionGroupItem(
const FocusedRegionGroupItem* item,
QPainter* painter
) __attribute__((__always_inline__));
inline void paintStackMemoryGroupItem(
const StackMemoryGroupItem* item,
QPainter* painter
) __attribute__((__always_inline__));
static void generatePixmapCaches();
};
}

View File

@@ -182,6 +182,8 @@ namespace Bloom::Widgets
}
void ItemGraphicsScene::init() {
this->byteAddressContainer->setPos(this->addressContainerPosition());
const auto constructHexViewerTopLevelGroupItem = QSharedPointer<ConstructHexViewerTopLevelGroupItem>(
new ConstructHexViewerTopLevelGroupItem(
this->focusedMemoryRegions,
@@ -196,11 +198,16 @@ namespace Bloom::Widgets
&ConstructHexViewerTopLevelGroupItem::topLevelGroupItem,
this,
[this] (TopLevelGroupItem* item) {
const auto margins = this->margins();
this->topLevelGroup.reset(item);
this->topLevelGroup->setPosition(
QPoint(ByteAddressContainer::WIDTH + this->margins.left(), this->margins.top())
QPoint(ByteAddressContainer::WIDTH + margins.left(), margins.top())
);
this->flattenedItems = this->topLevelGroup->flattenedItems();
this->itemIndex = std::make_unique<HexViewerItemIndex>(this->topLevelGroup.get(), this);
this->initRenderer();
emit this->ready();
}
);
@@ -232,13 +239,14 @@ namespace Bloom::Widgets
void ItemGraphicsScene::rebuildItemHierarchy() {
this->topLevelGroup->rebuildItemHierarchy();
this->flattenedItems = this->topLevelGroup->flattenedItems();
this->itemIndex->refreshFlattenedItems();
this->adjustSize();
}
void ItemGraphicsScene::adjustSize() {
const auto margins = this->margins();
const auto width = this->getSceneWidth();
const auto availableWidth = width - ByteAddressContainer::WIDTH - this->margins.left() - this->margins.right();
const auto availableWidth = width - ByteAddressContainer::WIDTH - margins.left() - margins.right();
auto hoverRectX = this->hoverRectX->rect();
hoverRectX.setWidth(width);
@@ -249,38 +257,28 @@ namespace Bloom::Widgets
this->hoverRectY->setRect(hoverRectY);
this->topLevelGroup->adjustItemPositions(availableWidth);
this->refreshItemPositionIndices();
this->itemIndex->refreshIndex();
this->setSceneRect(
0,
0,
const auto sceneSize = QSize(
width,
std::max(
static_cast<int>(this->topLevelGroup->size().height())
+ this->margins.top() + this->margins.bottom(),
+ margins.top() + margins.bottom(),
this->parent->height()
)
);
this->byteAddressContainer->adjustAddressLabels(this->firstByteItemByLine);
const auto* view = this->views().first();
const auto itemsRequired = static_cast<std::uint32_t>(
(availableWidth / (ByteItem::WIDTH + (ByteItem::RIGHT_MARGIN / 2)))
* (
(view->viewport()->height() + (4 * ItemGraphicsScene::GRID_SIZE))
/ (ByteItem::HEIGHT + (ByteItem::BOTTOM_MARGIN / 2))
)
this->setSceneRect(
0,
0,
sceneSize.width(),
sceneSize.height()
);
while (this->graphicsItems.size() < itemsRequired) {
auto* item = new GraphicsItem(&(this->state));
item->setEnabled(this->enabled);
this->graphicsItems.push_back(item);
this->addItem(item);
if (this->renderer != nullptr) {
this->renderer->size = sceneSize;
}
this->allocateGraphicsItems();
this->byteAddressContainer->adjustAddressLabels(this->itemIndex->byteItemLines);
this->update();
}
@@ -291,8 +289,8 @@ namespace Bloom::Widgets
this->enabled = enabled;
for (auto& graphicsItem : this->graphicsItems) {
graphicsItem->setEnabled(this->enabled);
if (this->renderer != nullptr) {
this->renderer->setEnabled(this->enabled);
}
this->byteAddressContainer->setEnabled(enabled);
@@ -314,65 +312,6 @@ namespace Bloom::Widgets
return QPointF();
}
void ItemGraphicsScene::allocateGraphicsItems() {
const auto verticalScrollBarValue = this->getScrollbarValue();
constexpr auto bufferPointSize = 2;
const auto gridPointIndex = static_cast<decltype(this->gridPoints)::size_type>(std::max(
static_cast<int>(
std::floor(
static_cast<float>(verticalScrollBarValue) / static_cast<float>(ItemGraphicsScene::GRID_SIZE)
)
) - 1 - bufferPointSize,
0
));
// Sanity check
assert(this->gridPoints.size() > gridPointIndex);
const auto& allocatableGraphicsItems = this->graphicsItems;
auto allocatableGraphicsItemsCount = allocatableGraphicsItems.size();
const auto allocateRangeStartItemIt = this->gridPoints[gridPointIndex];
const auto allocateRangeEndItemIt = this->flattenedItems.end();
const auto& firstItem = *allocateRangeStartItemIt;
/*
* Ensure that a graphics item for each parent, grandparent, etc. is allocated for the first item in the
* allocatable range.
*/
auto* parentItem = firstItem->parent;
while (
parentItem != nullptr
&& parentItem != this->topLevelGroup.get()
&& allocatableGraphicsItemsCount > 0
) {
allocatableGraphicsItems[allocatableGraphicsItemsCount - 1]->setHexViewerItem(parentItem);
--allocatableGraphicsItemsCount;
parentItem = parentItem->parent;
}
for (auto itemIt = allocateRangeStartItemIt; itemIt != allocateRangeEndItemIt; ++itemIt) {
if (allocatableGraphicsItemsCount < 1) {
// No more graphics items available to allocate
break;
}
allocatableGraphicsItems[allocatableGraphicsItemsCount - 1]->setHexViewerItem(*itemIt);
--allocatableGraphicsItemsCount;
}
// If we still have some available graphics items, clear them
while (allocatableGraphicsItemsCount > 0) {
allocatableGraphicsItems[allocatableGraphicsItemsCount - 1]->setHexViewerItem(nullptr);
--allocatableGraphicsItemsCount;
}
this->update();
}
void ItemGraphicsScene::addExternalContextMenuAction(ContextMenuAction* action) {
QObject::connect(action, &QAction::triggered, this, [this, action] () {
emit action->invoked(this->selectedByteItemsByAddress);
@@ -381,6 +320,24 @@ namespace Bloom::Widgets
this->externalContextMenuActions.push_back(action);
}
void ItemGraphicsScene::initRenderer() {
this->renderer = new HexViewerItemRenderer(
this->state,
*(this->itemIndex.get()),
this->views().first()
);
this->renderer->setPos(0, 0);
this->addItem(this->renderer);
}
QMargins ItemGraphicsScene::margins() {
return QMargins(10, 10, 10, 10);
}
QPointF ItemGraphicsScene::addressContainerPosition() {
return QPointF(0, 0);
}
bool ItemGraphicsScene::event(QEvent* event) {
if (event->type() == QEvent::Type::GraphicsSceneLeave && this->state.hoveredByteItem != nullptr) {
this->onByteItemLeave();
@@ -407,20 +364,7 @@ namespace Bloom::Widgets
this->update();
if (button == Qt::MouseButton::RightButton) {
ByteItem* clickedByteItem = nullptr;
for (const auto& item : this->items(mousePosition)) {
auto* clickedGraphicsItem = dynamic_cast<GraphicsItem*>(item);
if (clickedGraphicsItem == nullptr) {
continue;
}
clickedByteItem = dynamic_cast<ByteItem*>(clickedGraphicsItem->hexViewerItem);
if (clickedByteItem != nullptr) {
break;
}
}
ByteItem* clickedByteItem = this->itemIndex->byteItemAt(mousePosition);
if (clickedByteItem == nullptr || clickedByteItem->selected) {
return;
@@ -447,26 +391,17 @@ namespace Bloom::Widgets
this->clearByteItemSelection();
}
for (const auto& item : this->items(mousePosition)) {
auto* clickedGraphicsItem = dynamic_cast<GraphicsItem*>(item);
if (clickedGraphicsItem == nullptr) {
continue;
}
auto* byteItem = dynamic_cast<ByteItem*>(clickedGraphicsItem->hexViewerItem);
if (byteItem == nullptr) {
continue;
}
auto* clickedByteItem = this->itemIndex->byteItemAt(mousePosition);
if (clickedByteItem != nullptr) {
if ((modifiers & Qt::ShiftModifier) != 0) {
for (
auto i = byteItem->startAddress;
auto i = static_cast<std::int64_t>(clickedByteItem->startAddress);
i >= this->state.memoryDescriptor.addressRange.startAddress;
--i
) {
auto& byteItem = this->topLevelGroup->byteItemsByAddress.at(i);
auto& byteItem = this->topLevelGroup->byteItemsByAddress.at(
static_cast<Targets::TargetMemoryAddress>(i)
);
if (byteItem.selected) {
break;
@@ -479,15 +414,13 @@ namespace Bloom::Widgets
return;
}
this->toggleByteItemSelection(*byteItem);
this->toggleByteItemSelection(*clickedByteItem);
emit this->selectionChanged(this->selectedByteItemsByAddress);
break;
}
}
void ItemGraphicsScene::mouseMoveEvent(QGraphicsSceneMouseEvent* mouseEvent) {
const auto mousePosition = mouseEvent->scenePos();
auto hoveredItems = this->items(mousePosition);
if (this->rubberBandRectItem != nullptr && this->rubberBandInitPoint.has_value()) {
this->update();
@@ -504,52 +437,25 @@ namespace Bloom::Widgets
this->clearByteItemSelection();
} else {
const auto oldItems = this->items(oldRect, Qt::IntersectsItemShape);
for (auto* item : oldItems) {
auto* graphicsItem = dynamic_cast<GraphicsItem*>(item);
if (graphicsItem == nullptr) {
continue;
}
auto* byteItem = dynamic_cast<ByteItem*>(graphicsItem->hexViewerItem);
if (byteItem != nullptr && byteItem->selected) {
const auto oldItems = this->itemIndex->intersectingByteItems(oldRect);
for (auto* byteItem : oldItems) {
if (byteItem->selected) {
this->deselectByteItem(*byteItem);
}
}
}
const auto items = this->items(this->rubberBandRectItem->rect(), Qt::IntersectsItemShape);
for (auto* item : items) {
auto* graphicsItem = dynamic_cast<GraphicsItem*>(item);
if (graphicsItem == nullptr) {
continue;
}
auto* byteItem = dynamic_cast<ByteItem*>(graphicsItem->hexViewerItem);
if (byteItem != nullptr && !byteItem->selected) {
this->selectByteItem(*byteItem);
}
const auto items = this->itemIndex->intersectingByteItems(this->rubberBandRectItem->rect());
for (auto& byteItem : items) {
this->selectByteItem(*byteItem);
}
emit this->selectionChanged(this->selectedByteItemsByAddress);
}
for (const auto& item : hoveredItems) {
auto* hoveredGraphicsItem = dynamic_cast<GraphicsItem*>(item);
if (hoveredGraphicsItem == nullptr) {
continue;
}
auto* hoveredByteItem = dynamic_cast<ByteItem*>(hoveredGraphicsItem->hexViewerItem);
if (hoveredByteItem != nullptr) {
this->onByteItemEnter(*hoveredByteItem);
return;
}
auto* hoveredByteItem = this->itemIndex->byteItemAt(mousePosition);
if (hoveredByteItem != nullptr) {
this->onByteItemEnter(*hoveredByteItem);
return;
}
if (this->state.hoveredByteItem != nullptr) {
@@ -638,47 +544,6 @@ namespace Bloom::Widgets
menu->exec(event->screenPos());
}
void ItemGraphicsScene::refreshItemPositionIndices() {
const auto pointsRequired = static_cast<std::uint32_t>(
this->sceneRect().height() / ItemGraphicsScene::GRID_SIZE
);
this->gridPoints.clear();
this->gridPoints.reserve(pointsRequired);
this->firstByteItemByLine.clear();
auto currentPoint = 0;
auto currentLineYPosition = 0;
for (auto itemIt = this->flattenedItems.begin(); itemIt != this->flattenedItems.end(); ++itemIt) {
auto& item = *itemIt;
if (item->allocatedGraphicsItem != nullptr) {
item->allocatedGraphicsItem->setPos(item->position());
}
const auto byteItem = dynamic_cast<const ByteItem*>(item);
if (byteItem == nullptr) {
continue;
}
const auto itemYStartPosition = byteItem->position().y();
const auto itemYEndPosition = itemYStartPosition + byteItem->size().height();
if (itemYStartPosition > currentLineYPosition) {
this->firstByteItemByLine.push_back(byteItem);
currentLineYPosition = itemYStartPosition;
}
if (itemYEndPosition >= currentPoint) {
// This byte item is the first to exceed or intersect with the currentPoint
this->gridPoints.push_back(itemIt);
currentPoint += ItemGraphicsScene::GRID_SIZE;
}
}
}
int ItemGraphicsScene::getScrollbarValue() {
return this->views().first()->verticalScrollBar()->value();
}
@@ -713,19 +578,11 @@ namespace Bloom::Widgets
this->hoverRectY->update();
}
if (byteItem.allocatedGraphicsItem != nullptr) {
byteItem.allocatedGraphicsItem->update();
}
emit this->hoveredAddress(byteItem.startAddress);
}
void ItemGraphicsScene::onByteItemLeave() {
if (this->state.hoveredByteItem != nullptr) {
if (this->state.hoveredByteItem->allocatedGraphicsItem != nullptr) {
this->state.hoveredByteItem->allocatedGraphicsItem->update();
}
this->state.hoveredByteItem = nullptr;
}

View File

@@ -22,7 +22,8 @@
#include "src/Insight/UserInterfaces/InsightWindow/Widgets/Label.hpp"
#include "GraphicsItem.hpp"
#include "HexViewerItemIndex.hpp"
#include "HexViewerItemRenderer.hpp"
#include "TopLevelGroupItem.hpp"
#include "GroupItem.hpp"
#include "ByteItem.hpp"
@@ -60,7 +61,6 @@ namespace Bloom::Widgets
void setEnabled(bool enabled);
void refreshValues();
QPointF getByteItemPositionByAddress(Targets::TargetMemoryAddress address);
void allocateGraphicsItems();
void addExternalContextMenuAction(ContextMenuAction* action);
signals:
@@ -69,8 +69,6 @@ namespace Bloom::Widgets
void selectionChanged(const std::unordered_map<Targets::TargetMemoryAddress, ByteItem*>& selectedByteItemsByAddress);
protected:
static constexpr auto GRID_SIZE = 100;
bool enabled = true;
HexViewerSharedState state;
@@ -79,14 +77,9 @@ namespace Bloom::Widgets
const std::vector<ExcludedMemoryRegion>& excludedMemoryRegions;
std::unique_ptr<TopLevelGroupItem> topLevelGroup = nullptr;
std::unique_ptr<HexViewerItemIndex> itemIndex = nullptr;
std::vector<HexViewerItem*> flattenedItems;
std::vector<decltype(ItemGraphicsScene::flattenedItems)::iterator> gridPoints;
std::vector<const ByteItem*> firstByteItemByLine;
std::vector<GraphicsItem*> graphicsItems;
const QMargins margins = QMargins(10, 10, 10, 10);
HexViewerItemRenderer* renderer = nullptr;
Targets::TargetState targetState = Targets::TargetState::UNKNOWN;
@@ -137,6 +130,9 @@ namespace Bloom::Widgets
return std::max(this->parent->viewport()->width(), 200) - 2;
}
virtual void initRenderer();
virtual QMargins margins();
virtual QPointF addressContainerPosition();
bool event(QEvent* event) override;
void mouseDoubleClickEvent(QGraphicsSceneMouseEvent* mouseEvent) override;
void mousePressEvent(QGraphicsSceneMouseEvent* mouseEvent) override;
@@ -144,7 +140,6 @@ namespace Bloom::Widgets
void mouseReleaseEvent(QGraphicsSceneMouseEvent* mouseEvent) override;
void keyPressEvent(QKeyEvent* keyEvent) override;
void contextMenuEvent(QGraphicsSceneContextMenuEvent* event) override;
void refreshItemPositionIndices();
int getScrollbarValue();
void onTargetStateChanged(Targets::TargetState newState);
void onByteItemEnter(ByteItem& byteItem);

View File

@@ -90,12 +90,4 @@ namespace Bloom::Widgets
this->scene->adjustSize();
}
void ItemGraphicsView::scrollContentsBy(int dx, int dy) {
if (this->scene != nullptr) {
this->scene->allocateGraphicsItems();
}
return QGraphicsView::scrollContentsBy(dx, dy);
}
}

View File

@@ -48,6 +48,5 @@ namespace Bloom::Widgets
bool event(QEvent* event) override;
void resizeEvent(QResizeEvent* event) override;
void scrollContentsBy(int dx, int dy) override;
};
}

View File

@@ -1,7 +1,6 @@
#include "StackMemoryGroupItem.hpp"
#include <cassert>
#include <QColor>
namespace Bloom::Widgets
{
@@ -89,152 +88,6 @@ namespace Bloom::Widgets
}
}
void StackMemoryGroupItem::paint(
QPainter* painter,
const HexViewerSharedState* hexViewerState,
const QGraphicsItem* graphicsItem
) const {
painter->setRenderHints(QPainter::RenderHint::Antialiasing, false);
if (!graphicsItem->isEnabled()) {
painter->setOpacity(0.5);
}
const auto headingText = QString("Stack Memory");
const auto stackSize = hexViewerState->memoryDescriptor.addressRange.endAddress - this->startAddress + 1;
const auto& memoryCapacity = hexViewerState->memoryDescriptor.size();
const auto stackSizeHeadingText = QString("Stack size:");
const auto stackSizeValueText = QString::number(stackSize) + " byte(s) ("
+ QString::number(static_cast<float>(stackSize) / static_cast<float>(memoryCapacity / 100), 'f' , 1)
+ "% of memory capacity)";
const auto stackPointerHeadingText = QString("Stack pointer:");
const auto stackPointerValueText = "0x" + QString::number(
this->stackPointer,
16
).rightJustified(8, '0').toUpper();
static constexpr auto lineColor = QColor(0x4F, 0x4F, 0x4F);
static constexpr auto headingLabelFontColor = QColor(0x6F, 0x6F, 0x6F);
static constexpr auto valueLabelFontColor = QColor(0x94, 0x6F, 0x30, 230);
static auto headingLabelFont = QFont("'Ubuntu', sans-serif");
headingLabelFont.setPixelSize(13);
static auto valueFont = QFont("'Ubuntu', sans-serif");
valueFont.setPixelSize(13);
valueFont.setItalic(true);
painter->setFont(headingLabelFont);
const auto groupWidth = this->groupSize.width();
const auto fontMetrics = painter->fontMetrics();
auto headingLabelSize = fontMetrics.size(Qt::TextSingleLine, headingText);
auto stackSizeHeadingLabelSize = fontMetrics.size(Qt::TextSingleLine, stackSizeHeadingText);
auto stackSizeLabelSize = fontMetrics.size(Qt::TextSingleLine, stackSizeValueText);
auto stackPointerHeadingLabelSize = fontMetrics.size(Qt::TextSingleLine, stackPointerHeadingText);
auto stackPointerLabelSize = fontMetrics.size(Qt::TextSingleLine, stackPointerValueText);
static constexpr auto labelLineHeight = 4;
static constexpr auto labelBottomMargin = 10;
static constexpr auto labelRightMargin = 3;
const auto heightOffset = headingLabelSize.height() + stackSizeHeadingLabelSize.height()
+ stackPointerHeadingLabelSize.height() + (labelBottomMargin * 3) + 15;
const auto verticalLineYStart = static_cast<int>(heightOffset - 5);
const auto verticalLineYEnd = static_cast<int>(heightOffset);
const auto lineStartX = ByteItem::WIDTH / 2;
const auto lineEndX = groupWidth - + (ByteItem::WIDTH / 2);
const auto labelRect = QRect(
(groupWidth - headingLabelSize.width()) / 2,
verticalLineYStart - stackPointerHeadingLabelSize.height() - stackSizeHeadingLabelSize.height()
- headingLabelSize.height() - labelLineHeight - (labelBottomMargin * 2) - 3,
headingLabelSize.width(),
headingLabelSize.height()
);
const auto stackPointerHeadingLabelRect = QRect(
labelRect.left() + (labelRect.width() / 2) - (
(stackPointerHeadingLabelSize.width() + stackPointerLabelSize.width()) / 2
),
labelRect.bottom() + labelBottomMargin,
stackPointerHeadingLabelSize.width(),
stackPointerHeadingLabelSize.height()
);
const auto stackPointerValueLabelRect = QRect(
stackPointerHeadingLabelRect.right() + labelRightMargin,
labelRect.bottom() + labelBottomMargin,
stackPointerLabelSize.width(),
stackPointerLabelSize.height()
);
const auto stackSizeHeadingLabelRect = QRect(
labelRect.left() + (labelRect.width() / 2) - (
(stackSizeHeadingLabelSize.width() + stackSizeLabelSize.width()) / 2
),
stackPointerHeadingLabelRect.bottom() + labelBottomMargin,
stackSizeHeadingLabelSize.width(),
stackSizeHeadingLabelSize.height()
);
const auto stackSizeValueLabelRect = QRect(
stackSizeHeadingLabelRect.right() + labelRightMargin,
stackPointerHeadingLabelRect.bottom() + labelBottomMargin,
stackSizeLabelSize.width(),
stackSizeLabelSize.height()
);
painter->setPen(lineColor);
painter->drawLine(QLine(
lineStartX,
verticalLineYStart,
lineStartX,
verticalLineYEnd
));
painter->drawLine(QLine(
lineEndX,
verticalLineYStart,
lineEndX,
verticalLineYEnd
));
painter->drawLine(QLine(
lineStartX,
verticalLineYStart,
lineEndX,
verticalLineYStart
));
painter->drawLine(QLine(
groupWidth / 2,
verticalLineYStart - labelLineHeight,
groupWidth / 2,
verticalLineYStart
));
painter->setPen(headingLabelFontColor);
painter->drawText(labelRect, Qt::AlignCenter, headingText);
painter->drawText(stackSizeHeadingLabelRect, Qt::AlignCenter, stackSizeHeadingText);
painter->drawText(stackPointerHeadingLabelRect, Qt::AlignCenter, stackPointerHeadingText);
painter->setFont(valueFont);
painter->setPen(valueLabelFontColor);
painter->drawText(stackSizeValueLabelRect, Qt::AlignCenter, stackSizeValueText);
painter->drawText(stackPointerValueLabelRect, Qt::AlignCenter, stackPointerValueText);
}
QMargins StackMemoryGroupItem::groupMargins(
const HexViewerSharedState* hexViewerState,
const int maximumWidth

View File

@@ -14,6 +14,8 @@ namespace Bloom::Widgets
class StackMemoryGroupItem: public GroupItem
{
public:
Targets::TargetStackPointer stackPointer;
StackMemoryGroupItem(
Targets::TargetStackPointer stackPointer,
const HexViewerSharedState& hexViewerState,
@@ -28,12 +30,6 @@ namespace Bloom::Widgets
void refreshValues();
void paint(
QPainter* painter,
const HexViewerSharedState* hexViewerState,
const QGraphicsItem* graphicsItem
) const override;
protected:
QMargins groupMargins(const HexViewerSharedState* hexViewerState, const int maximumWidth) const override;
@@ -42,9 +38,7 @@ namespace Bloom::Widgets
}
private:
Targets::TargetStackPointer stackPointer;
const HexViewerSharedState& hexViewerState;
std::list<FocusedRegionGroupItem> focusedRegionGroupItems;
};
}

View File

@@ -44,23 +44,8 @@ namespace Bloom::Widgets
);
}
ByteItem* DifferentialItemGraphicsScene::byteItemAtViewportVerticalCenter() {
const auto scrollBarValue = this->getScrollbarValue();
const auto midPosition = static_cast<qreal>(
scrollBarValue
);
const auto gridPointIndex = static_cast<decltype(this->gridPoints)::size_type>(
std::min(
static_cast<int>(
std::floor(static_cast<float>(midPosition) / static_cast<float>(ItemGraphicsScene::GRID_SIZE)) + 1
),
static_cast<int>(this->gridPoints.size() - 1)
)
);
return static_cast<ByteItem*>(*(this->gridPoints[gridPointIndex]));
ByteItem* DifferentialItemGraphicsScene::byteItemAtViewportTop() {
return this->itemIndex->closestByteItem(this->getScrollbarValue());
}
void DifferentialItemGraphicsScene::onOtherHoveredAddress(
@@ -83,8 +68,7 @@ namespace Bloom::Widgets
const auto scrollbarValue = this->getScrollbarValue();
if (
byteItem.allocatedGraphicsItem == nullptr
|| itemPosition < scrollbarValue
itemPosition < scrollbarValue
|| itemPosition > (scrollbarValue + this->views().first()->viewport()->height())
) {
// The item isn't visible

View File

@@ -25,7 +25,7 @@ namespace Bloom::Widgets
);
void setOther(DifferentialItemGraphicsScene* other);
ByteItem* byteItemAtViewportVerticalCenter();
ByteItem* byteItemAtViewportTop();
protected:
DifferentialHexViewerSharedState& diffHexViewerState;

View File

@@ -74,7 +74,7 @@ namespace Bloom::Widgets
return;
}
auto* byteItem = this->differentialScene->byteItemAtViewportVerticalCenter();
auto* byteItem = this->differentialScene->byteItemAtViewportTop();
this->state.syncingScroll = true;
this->other->alignScroll(byteItem->startAddress, byteItem->position().y() - this->verticalScrollBar()->value());