Further refactoring of hex viewer item painting for additional performance gains
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
{}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,5 @@ namespace Bloom::Widgets
|
||||
|
||||
bool event(QEvent* event) override;
|
||||
void resizeEvent(QResizeEvent* event) override;
|
||||
void scrollContentsBy(int dx, int dy) override;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace Bloom::Widgets
|
||||
);
|
||||
|
||||
void setOther(DifferentialItemGraphicsScene* other);
|
||||
ByteItem* byteItemAtViewportVerticalCenter();
|
||||
ByteItem* byteItemAtViewportTop();
|
||||
|
||||
protected:
|
||||
DifferentialHexViewerSharedState& diffHexViewerState;
|
||||
|
||||
@@ -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());
|
||||
|
||||
Reference in New Issue
Block a user