summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorisanae <14251494+isanae@users.noreply.github.com>2019-06-27 07:03:40 -0400
committerisanae <14251494+isanae@users.noreply.github.com>2019-07-02 10:10:19 -0400
commit0fc928fd815f27eef77491224bb8922b4e070f2d (patch)
treefe9da9434490629ceb95549136ed91c2030d3db4 /src
parent9cce32f19f435570b94c6f65c2bc6eb99bd542ac (diff)
rework of the images tab:
- uses a custom widget with a scrollbar instead of a QScrollArea - scrolling by image instead of pixel - refactored all the geometry stuff in ImagesGeometry
Diffstat (limited to 'src')
-rw-r--r--src/modinfodialog.ui51
-rw-r--r--src/modinfodialogimages.cpp393
-rw-r--r--src/modinfodialogimages.h189
3 files changed, 418 insertions, 215 deletions
diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui
index 3ea0a27b..a9af2593 100644
--- a/src/modinfodialog.ui
+++ b/src/modinfodialog.ui
@@ -172,7 +172,7 @@
<bool>false</bool>
</property>
<widget class="QWidget" name="widget_8" native="true">
- <layout class="QVBoxLayout" name="verticalLayout_2">
+ <layout class="QVBoxLayout" name="verticalLayout_2" stretch="1,0,0">
<property name="spacing">
<number>3</number>
</property>
@@ -189,20 +189,34 @@
<number>0</number>
</property>
<item>
- <widget class="ImagesScrollArea" name="imagesScrollArea">
- <property name="verticalScrollBarPolicy">
- <enum>Qt::ScrollBarAlwaysOn</enum>
- </property>
- <widget class="ImagesThumbnails" name="imagesThumbnails">
- <property name="geometry">
- <rect>
- <x>0</x>
- <y>0</y>
- <width>686</width>
- <height>436</height>
- </rect>
+ <widget class="QWidget" name="imagesScroller" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_4">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
</property>
- </widget>
+ <item>
+ <widget class="ImagesThumbnails" name="imagesThumbnails" native="true"/>
+ </item>
+ <item>
+ <widget class="ImagesScrollbar" name="imagesScrollerVBar">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
</widget>
</item>
<item>
@@ -1221,16 +1235,15 @@ p, li { white-space: pre-wrap; }
<header>texteditor.h</header>
</customwidget>
<customwidget>
- <class>ImagesScrollArea</class>
- <extends>QScrollArea</extends>
+ <class>ImagesThumbnails</class>
+ <extends>QWidget</extends>
<header>modinfodialogimages.h</header>
<container>1</container>
</customwidget>
<customwidget>
- <class>ImagesThumbnails</class>
- <extends>QWidget</extends>
+ <class>ImagesScrollbar</class>
+ <extends>QScrollBar</extends>
<header>modinfodialogimages.h</header>
- <container>1</container>
</customwidget>
</customwidgets>
<resources>
diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp
index e25fd838..5c93e3d4 100644
--- a/src/modinfodialogimages.cpp
+++ b/src/modinfodialogimages.cpp
@@ -3,11 +3,36 @@
#include "settings.h"
#include "utility.h"
+QSize resizeWithAspectRatio(const QSize& original, const QSize& available)
+{
+ const auto ratio = std::min({
+ 1.0,
+ static_cast<double>(available.width()) / original.width(),
+ static_cast<double>(available.height()) / original.height()});
+
+ const QSize scaledSize(
+ static_cast<int>(std::round(original.width() * ratio)),
+ static_cast<int>(std::round(original.height() * ratio)));
+
+ return scaledSize;
+}
+
+QRect centeredRect(const QRect& rect, const QSize& size)
+{
+ return QRect(
+ (rect.left()+rect.width()/2) - size.width()/2,
+ (rect.top()+rect.height()/2) - size.height()/2,
+ size.width(),
+ size.height());
+}
+
+
ImagesTab::ImagesTab(
OrganizerCore& oc, PluginContainer& plugin,
QWidget* parent, Ui::ModInfoDialog* ui, int id) :
ModInfoDialogTab(oc, plugin, parent, ui, id),
- m_image(new ScalableImage), m_margins(3), m_padding(5), m_border(1),
+ m_image(new ScalableImage),
+ m_margins(3), m_border(1), m_padding(0), m_spacing(5),
m_selection(nullptr), m_ddsAvailable(false), m_ddsEnabled(false)
{
getSupportedFormats();
@@ -22,10 +47,12 @@ ImagesTab::ImagesTab(
ui->tabImagesSplitter->setStretchFactor(0, 0);
ui->tabImagesSplitter->setStretchFactor(1, 1);
- ui->imagesScrollArea->setWidgetResizable(false);
- ui->imagesScrollArea->setTab(this);
ui->imagesThumbnails->setTab(this);
+ ui->imagesScrollerVBar->setTab(this);
+ ui->imagesScrollerVBar->setSingleStep(1);
+ connect(ui->imagesScrollerVBar, &QScrollBar::valueChanged, [&]{ onScrolled(); });
+
ui->imagesShowDDS->setEnabled(m_ddsAvailable);
m_filter.setEdit(ui->imagesFilter);
@@ -35,12 +62,16 @@ ImagesTab::ImagesTab(
connect(ui->imagesShowDDS, &QCheckBox::toggled, [&]{ onShowDDS(); });
ui->imagesShowDDS->setEnabled(m_ddsAvailable);
+
+ ui->imagesThumbnails->setAutoFillBackground(false);
+ ui->imagesThumbnails->setAttribute(Qt::WA_OpaquePaintEvent, true);
}
void ImagesTab::clear()
{
m_files.clear();
m_filteredFiles.clear();
+ ui->imagesScrollerVBar->setValue(0);
select(nullptr);
setHasData(false);
@@ -202,157 +233,98 @@ void ImagesTab::select(const File* f)
m_selection = f;
}
-int ImagesTab::calcThumbSize(int availableWidth) const
-{
- return availableWidth - (m_margins * 2);
-}
-
-int ImagesTab::calcWidgetHeight(int availableWidth) const
+ImagesGeometry ImagesTab::makeGeometry() const
{
- if (fileCount() == 0) {
- return 0;
- }
-
- const auto thumbSize = calcThumbSize(availableWidth);
-
- int h = 0;
-
- // first thumb
- h = thumbSize;
-
- // subsequent thumbs with padding before each
- const auto thumbWithPadding = m_padding + thumbSize;
- h += static_cast<int>(thumbWithPadding * (fileCount() - 1));
-
- // margin top and bottom
- h += m_margins * 2;
-
- return h;
-}
-
-QRect ImagesTab::calcTopThumbRect(int thumbSize) const
-{
- return {m_margins, m_margins, thumbSize, thumbSize};
+ return ImagesGeometry(
+ ui->imagesThumbnails->size(),
+ m_margins, m_border, m_padding, m_spacing);
}
-std::pair<std::size_t, std::size_t> ImagesTab::calcVisibleRange(
- int top, int bottom, int thumbSize) const
+void ImagesTab::paintThumbnailsArea(QPaintEvent* e)
{
- const std::size_t begin = top / (thumbSize + m_padding);
- const std::size_t end = bottom / (thumbSize + m_padding) + 1;
+ const auto geo = makeGeometry();
- return {begin, end};
-}
+ QPainter painter(ui->imagesThumbnails);
-QRect ImagesTab::calcBorderRect(
- const QRect& topRect, int thumbSize, std::size_t i) const
-{
- return {
- topRect.left(),
- static_cast<int>(topRect.top() + (i * (thumbSize + m_padding))),
- thumbSize,
- thumbSize
- };
-}
+ painter.fillRect(
+ ui->imagesThumbnails->rect(),
+ ui->imagesThumbnails->palette().color(QPalette::Window));
-QRect ImagesTab::calcImageRect(
- const QRect& topRect, int thumbSize, std::size_t i) const
-{
- return calcBorderRect(topRect, thumbSize, i).adjusted(
- m_border, m_border, -m_border, -m_border);
-}
+ const auto visible = geo.fullyVisibleCount() + 1;
+ const auto first = ui->imagesScrollerVBar->value();
-QSize ImagesTab::calcScaledImageSize(
- const QSize& originalSize, const QSize& imageSize) const
-{
- const auto ratio = std::min({
- 1.0,
- static_cast<double>(imageSize.width()) / originalSize.width(),
- static_cast<double>(imageSize.height()) / originalSize.height()});
-
- const QSize scaledSize(
- static_cast<int>(std::round(originalSize.width() * ratio)),
- static_cast<int>(std::round(originalSize.height() * ratio)));
-
- return scaledSize;
-}
-
-void ImagesTab::paintThumbnails(QPaintEvent* e)
-{
- PaintContext cx(ui->imagesThumbnails);
- cx.thumbSize = calcThumbSize(ui->imagesThumbnails->width());
- cx.topRect = calcTopThumbRect(cx.thumbSize);
-
- const auto [begin, end] = calcVisibleRange(
- e->rect().top(), e->rect().bottom(), cx.thumbSize);
+ for (std::size_t i=0; i<visible; ++i) {
+ const auto fileIndex = first + i;
+ auto* file = getFile(fileIndex);
+ if (!file) {
+ break;
+ }
- const auto n = fileCount();
- for (std::size_t i=begin; i<end && i<n; ++i) {
- paintThumbnail(cx, i);
+ paintThumbnail(painter, geo, *file, i);
}
}
-void ImagesTab::paintThumbnail(PaintContext& cx, std::size_t i)
+void ImagesTab::paintThumbnail(
+ QPainter& painter, const ImagesGeometry& geo,
+ File& file, std::size_t i)
{
- paintThumbnailBorder(cx, i);
- paintThumbnailImage(cx, i);
+ paintThumbnailBorder(painter, geo, i);
+ paintThumbnailImage(painter, geo, file, i);
}
-void ImagesTab::paintThumbnailBorder(PaintContext& cx, std::size_t i)
+void ImagesTab::paintThumbnailBorder(
+ QPainter& painter, const ImagesGeometry& geo, std::size_t i)
{
- const auto borderRect = calcBorderRect(cx.topRect, cx.thumbSize, i);
+ auto borderRect = geo.borderRect(i);
+
+ // rects don't include the bottom right corner, but drawRect() does, so
+ // resize it
+ borderRect.setRight(borderRect.right() - 1);
+ borderRect.setBottom(borderRect.bottom() - 1);
- cx.painter.setPen(QColor(Qt::black));
- cx.painter.drawRect(borderRect);
+ painter.setPen(QColor(Qt::black));
+ painter.drawRect(borderRect);
}
-void ImagesTab::paintThumbnailImage(PaintContext& cx, std::size_t i)
+void ImagesTab::paintThumbnailImage(
+ QPainter& painter, const ImagesGeometry& geo,
+ File& file, std::size_t i)
{
- auto* file = getFile(i);
- if (!file) {
- qCritical() << "ImagesTab: no file at index " << i;
- return;
- }
-
- if (file->failed) {
+ if (file.failed) {
return;
}
- const auto imageRect = calcImageRect(cx.topRect, cx.thumbSize, i);
-
- if (needsReload(*file, imageRect.size())) {
- reload(*file, imageRect.size());
+ if (needsReload(geo, file)) {
+ reload(geo, file);
}
- if (file->thumbnail.isNull()) {
+ if (file.thumbnail.isNull()) {
return;
}
- // center scaled image in rect
- const QRect scaledThumbRect(
- (imageRect.left()+imageRect.width()/2) - file->thumbnail.width()/2,
- (imageRect.top()+imageRect.height()/2) - file->thumbnail.height()/2,
- file->thumbnail.width(),
- file->thumbnail.height());
+ const auto imageRect = geo.imageRect(i);
+ const auto scaledThumbRect = centeredRect(imageRect, file.thumbnail.size());
- cx.painter.drawImage(scaledThumbRect, file->thumbnail);
+ painter.drawImage(scaledThumbRect, file.thumbnail);
}
const ImagesTab::File* ImagesTab::fileAtPos(const QPoint& p) const
{
- const auto thumbSize = calcThumbSize(ui->imagesThumbnails->width());
+ const auto geo = makeGeometry();
- // calculate index purely based on y position
- const std::size_t i = p.y() / (thumbSize + m_padding);
- if (i >= fileCount()) {
+ // this is the index relative to the top
+ const auto offset = geo.indexAt(p);
+ if (offset == ImagesGeometry::BadIndex) {
return nullptr;
}
- // get actual rect
- const auto topRect = calcTopThumbRect(thumbSize);
- const auto rect = calcBorderRect(topRect, thumbSize, i);
+ const auto first = ui->imagesScrollerVBar->value();
+ if (first < 0) {
+ return nullptr;
+ }
- if (!rect.contains(p)) {
+ const auto i = static_cast<std::size_t>(first) + offset;
+ if (i >= fileCount()) {
return nullptr;
}
@@ -364,7 +336,7 @@ void ImagesTab::scrollAreaResized(const QSize&)
resizeWidget();
}
-void ImagesTab::thumbnailsMouseEvent(QMouseEvent* e)
+void ImagesTab::thumbnailAreaMouseEvent(QMouseEvent* e)
{
if (e->button() != Qt::LeftButton) {
return;
@@ -373,6 +345,19 @@ void ImagesTab::thumbnailsMouseEvent(QMouseEvent* e)
select(fileAtPos(e->pos()));
}
+void ImagesTab::thumbnailAreaWheelEvent(QWheelEvent* e)
+{
+ const auto d = (e->angleDelta() / 8).y();
+
+ ui->imagesScrollerVBar->setValue(
+ ui->imagesScrollerVBar->value() + (d > 0 ? -1 : 1));
+}
+
+void ImagesTab::onScrolled()
+{
+ ui->imagesThumbnails->update();
+}
+
void ImagesTab::showTooltip(QHelpEvent* e)
{
const auto* f = fileAtPos(e->pos());
@@ -410,7 +395,7 @@ void ImagesTab::onFilterChanged()
update();
}
-bool ImagesTab::needsReload(const File& file, const QSize& imageSize) const
+bool ImagesTab::needsReload(const ImagesGeometry& geo, const File& file) const
{
if (file.failed) {
return false;
@@ -420,15 +405,11 @@ bool ImagesTab::needsReload(const File& file, const QSize& imageSize) const
return true;
}
- const auto scaledSize = calcScaledImageSize(file.original.size(), imageSize);
- if (file.thumbnail.size() != scaledSize) {
- return true;
- }
-
- return false;
+ const auto scaledSize = geo.scaledImageSize(file.original.size());
+ return (file.thumbnail.size() != scaledSize);
}
-void ImagesTab::reload(File& file, const QSize& scaledSize)
+void ImagesTab::reload(const ImagesGeometry& geo, File& file)
{
file.failed = false;
@@ -441,56 +422,63 @@ void ImagesTab::reload(File& file, const QSize& scaledSize)
}
file.thumbnail = file.original.scaled(
- calcScaledImageSize(file.original.size(), scaledSize),
+ geo.scaledImageSize(file.original.size()),
Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
}
void ImagesTab::resizeWidget()
{
if (fileCount() == 0) {
- ui->imagesThumbnails->setGeometry(QRect());
+ ui->imagesScrollerVBar->setRange(0, 0);
+ ui->imagesScrollerVBar->setEnabled(false);
return;
}
- const auto availableWidth = ui->imagesScrollArea->viewport()->width();
+ const auto geo = makeGeometry();
+ const auto availableSize = ui->imagesThumbnails->size();
+ const auto fullyVisible = geo.fullyVisibleCount();
- const int widgetHeight = std::max(
- calcWidgetHeight(availableWidth),
- ui->imagesScrollArea->viewport()->height());
-
- ui->imagesThumbnails->setGeometry(QRect(0, 0, availableWidth, widgetHeight));
+ if (fullyVisible >= fileCount()) {
+ ui->imagesScrollerVBar->setRange(0, 0);
+ ui->imagesScrollerVBar->setEnabled(false);
+ } else {
+ const auto d = fileCount() - fullyVisible;
+ ui->imagesScrollerVBar->setRange(0, static_cast<int>(d));
+ ui->imagesScrollerVBar->setEnabled(true);
+ }
}
-void ImagesScrollArea::setTab(ImagesTab* tab)
+void ImagesThumbnails::setTab(ImagesTab* tab)
{
m_tab = tab;
}
-void ImagesScrollArea::resizeEvent(QResizeEvent* e)
+void ImagesThumbnails::paintEvent(QPaintEvent* e)
{
if (m_tab) {
- m_tab->scrollAreaResized(e->size());
+ m_tab->paintThumbnailsArea(e);
}
}
-
-void ImagesThumbnails::setTab(ImagesTab* tab)
+void ImagesThumbnails::mousePressEvent(QMouseEvent* e)
{
- m_tab = tab;
+ if (m_tab) {
+ m_tab->thumbnailAreaMouseEvent(e);
+ }
}
-void ImagesThumbnails::paintEvent(QPaintEvent* e)
+void ImagesThumbnails::wheelEvent(QWheelEvent* e)
{
if (m_tab) {
- m_tab->paintThumbnails(e);
+ m_tab->thumbnailAreaWheelEvent(e);
}
}
-void ImagesThumbnails::mousePressEvent(QMouseEvent* e)
+void ImagesThumbnails::resizeEvent(QResizeEvent* e)
{
if (m_tab) {
- m_tab->thumbnailsMouseEvent(e);
+ m_tab->scrollAreaResized(e->size());
}
}
@@ -505,6 +493,19 @@ bool ImagesThumbnails::event(QEvent* e)
}
+void ImagesScrollbar::setTab(ImagesTab* tab)
+{
+ m_tab = tab;
+}
+
+void ImagesScrollbar::wheelEvent(QWheelEvent* e)
+{
+ if (m_tab) {
+ m_tab->thumbnailAreaWheelEvent(e);
+ }
+}
+
+
ScalableImage::ScalableImage(QString path)
: m_path(std::move(path)), m_border(1)
{
@@ -564,14 +565,8 @@ void ScalableImage::paintEvent(QPaintEvent* e)
const QRect imageRect = widgetRect.adjusted(
m_border, m_border, -m_border, -m_border);
- const auto ratio = std::min({
- 1.0,
- static_cast<double>(imageRect.width()) / m_original.width(),
- static_cast<double>(imageRect.height()) / m_original.height()});
-
- const QSize scaledSize(
- static_cast<int>(std::round(m_original.width() * ratio)),
- static_cast<int>(std::round(m_original.height() * ratio)));
+ const QSize scaledSize = resizeWithAspectRatio(
+ m_original.size(), imageRect.size());
if (m_scaled.isNull() || m_scaled.size() != scaledSize) {
m_scaled = m_original.scaled(
@@ -580,16 +575,98 @@ void ScalableImage::paintEvent(QPaintEvent* e)
}
const QRect drawBorderRect = widgetRect.adjusted(0, 0, -1, -1);
-
- const QRect drawImageRect(
- (imageRect.left()+imageRect.width()/2) - m_scaled.width()/2,
- (imageRect.top()+imageRect.height()/2) - m_scaled.height()/2,
- m_scaled.width(), m_scaled.height());
-
+ const QRect drawImageRect = centeredRect(imageRect, m_scaled.size());
QPainter painter(this);
-
painter.setPen(QColor(Qt::black));
painter.drawRect(drawBorderRect);
painter.drawImage(drawImageRect, m_scaled);
}
+
+
+ImagesGeometry::ImagesGeometry(
+ const QSize& widgetSize, int margins, int border, int padding, int spacing) :
+ m_widgetSize(widgetSize),
+ m_margins(margins), m_padding(padding), m_border(border),
+ m_spacing(spacing), m_topRect(calcTopRect())
+{
+}
+
+QRect ImagesGeometry::calcTopRect() const
+{
+ const auto thumbWidth = m_widgetSize.width() - (m_margins * 2);
+ const auto imageSize = thumbWidth - (m_border * 2) - (m_padding * 2);
+ const auto thumbHeight = m_padding + m_border + imageSize + m_border + m_padding;
+
+ return {m_margins, m_margins, thumbWidth, thumbHeight};
+}
+
+std::size_t ImagesGeometry::fullyVisibleCount() const
+{
+ const auto r = thumbRect(0);
+ const auto visible = (m_widgetSize.height() / (r.height() + m_spacing));
+ return static_cast<std::size_t>(visible);
+}
+
+QRect ImagesGeometry::thumbRect(std::size_t i) const
+{
+ // rect for the top thumbnail
+ QRect r = m_topRect;
+
+ // move down
+ const auto thumbWithSpacing = m_spacing + r.height();
+ r.translate(0, static_cast<int>(i * thumbWithSpacing));
+
+ return r;
+}
+
+QRect ImagesGeometry::borderRect(std::size_t i) const
+{
+ auto r = thumbRect(i);
+
+ // border rect is currently the same as thumb rect, but may change if
+ // captions are added
+
+ return r;
+}
+
+QRect ImagesGeometry::imageRect(std::size_t i) const
+{
+ auto r = borderRect(i);
+
+ // remove border and padding
+ const auto m = m_border + m_padding;
+ r.adjust(m, m, -m, -m);
+
+ return r;
+}
+
+std::size_t ImagesGeometry::indexAt(const QPoint& p) const
+{
+ // calculate index purely based on y position
+ const std::size_t offset = p.y() / (m_topRect.height() + m_spacing);
+
+ if (!borderRect(offset).contains(p)) {
+ return BadIndex;
+ }
+
+ return offset;
+}
+
+QSize ImagesGeometry::scaledImageSize(const QSize& originalSize) const
+{
+ const auto availableSize = imageRect(0).size();
+ return resizeWithAspectRatio(originalSize, availableSize);
+}
+
+void ImagesGeometry::dump() const
+{
+ qDebug()
+ << "ImagesTab geometry:\n"
+ << " . widget size: " << m_widgetSize << "\n"
+ << " . margins: " << m_margins << "\n"
+ << " . border: " << m_border << "\n"
+ << " . padding: " << m_padding << "\n"
+ << " . spacing: " << m_spacing << "\n"
+ << " . top rect: " << m_topRect;
+}
diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h
index fa7115b0..60ce9def 100644
--- a/src/modinfodialogimages.h
+++ b/src/modinfodialogimages.h
@@ -3,26 +3,32 @@
#include "modinfodialogtab.h"
#include "filterwidget.h"
-#include <QScrollArea>
+#include <QScrollBar>
class ImagesTab;
-class ImagesScrollArea : public QScrollArea
+// vertical scrollbar, this is only to handle wheel events to scroll by one
+// instead of the system's scroll setting
+//
+class ImagesScrollbar : public QScrollBar
{
- Q_OBJECT;
-
public:
- using QScrollArea::QScrollArea;
+ using QScrollBar::QScrollBar;
void setTab(ImagesTab* tab);
protected:
- void resizeEvent(QResizeEvent* e) override;
+ // forwards to ImagesTab::thumbnailAreaWheelEvent()
+ //
+ void wheelEvent(QWheelEvent* event) override;
private:
ImagesTab* m_tab = nullptr;
};
+// widget inside the scroller, calls ImagesTab::paintThumbnailArea() when
+// needed and also forwards mouse clicks and tooltip events
+//
class ImagesThumbnails : public QWidget
{
Q_OBJECT;
@@ -32,8 +38,24 @@ public:
void setTab(ImagesTab* tab);
protected:
+ // forwards to ImagesTab::paintThumbnailArea()
+ //
void paintEvent(QPaintEvent* e) override;
+
+ // forwards to ImagesTab::thumbnailAreaMouseEvent()
+ //
void mousePressEvent(QMouseEvent* e) override;
+
+ // forwards to ImagesTab::thumbnailAreaWheelEvent()
+ //
+ void wheelEvent(QWheelEvent* e);
+
+ // forwards to ImagesTab::scrollAreaResized()
+ //
+ void resizeEvent(QResizeEvent* e) override;
+
+ // forwards to ImagesTab::showTooltip for tooltip events
+ //
bool event(QEvent* e) override;
private:
@@ -41,6 +63,8 @@ private:
};
+// a widget that draws an image scaled to fit while keeping the aspect ratio
+//
class ScalableImage : public QWidget
{
Q_OBJECT;
@@ -48,12 +72,16 @@ class ScalableImage : public QWidget
public:
ScalableImage(QString path={});
+ // sets the image to draw
void setImage(const QString& path);
void setImage(QImage image);
+
+ // removes the image, won't draw the border nor the image
void clear();
- bool hasHeightForWidth() const;
- int heightForWidth(int w) const;
+ // tells the QWidget's layout manager this widget is always square
+ bool hasHeightForWidth() const override;
+ int heightForWidth(int w) const override;
protected:
void paintEvent(QPaintEvent* e) override;
@@ -65,10 +93,105 @@ private:
};
+// handles all the geometry calculations by ImagesTab for painting or handling
+// mouse clicks
+//
+// a thumbnail looks like this:
+//
+// +-----------------------+ <-- thumb rect
+// | margins |
+// | |
+// | +-border--------+ <------ border rect
+// | | padding | |
+// | | | |
+// | | +-------+ <----------- image rect
+// | | | | | |
+// | | | image | | |
+// | | | | | |
+// | | +-------+ | |
+// | | | |
+// | +---------------+ |
+// | |
+// +-----------------------+
+//
+// spacing
+//
+// +-----------------------+ <-- thumb rect
+// | margins |
+// | |
+// ....
+//
+//
+class ImagesGeometry
+{
+public:
+ // returned by indexAt() if the point is outside any possible thumbnail
+ static constexpr std::size_t BadIndex =
+ std::numeric_limits<std::size_t>::max();
+
+ ImagesGeometry(
+ const QSize& widgetSize, int margins, int border, int padding, int spacing);
+
+ // returns the number of images fully visible in the widget
+ //
+ std::size_t fullyVisibleCount() const;
+
+ // rectangle around the whole thumbnail
+ //
+ QRect thumbRect(std::size_t i) const;
+
+ // rectangle of the border for the given thumbnail
+ //
+ QRect borderRect(std::size_t i) const;
+
+ // rectangle of the image for the given thumbnail
+ //
+ QRect imageRect(std::size_t i) const;
+
+ // returns the index of the image at the given point; this does not take into
+ // account any scrolling, the image at the top of widget is always 0
+ //
+ std::size_t indexAt(const QPoint& p) const;
+
+ // returns the size of the image that fits in imageRect() while keeping the
+ // same aspect ratio as the given one
+ //
+ QSize scaledImageSize(const QSize& originalSize) const;
+
+ // dumps stuff to qDebug()
+ //
+ void dump() const;
+
+private:
+ // size of the widget containing all the thumbnails
+ const QSize m_widgetSize;
+
+ // space outside the thumbnail border
+ const int m_margins;
+
+ // size of the border
+ const int m_border;
+
+ // space between the border and the image
+ const int m_padding;
+
+ // spacing between thumbnails
+ const int m_spacing;
+
+ // rectangle of the first thumbnail on top
+ const QRect m_topRect;
+
+
+ // calculates the top rectangle
+ //
+ QRect calcTopRect() const;
+};
+
+
class ImagesTab : public ModInfoDialogTab
{
Q_OBJECT;
- friend class ImagesScrollArea;
+ friend class ImagesScrollbar;
friend class ImagesThumbnails;
public:
@@ -95,23 +218,11 @@ private:
}
};
- struct PaintContext
- {
- QPainter painter;
- int thumbSize;
- QRect topRect;
-
- PaintContext(QWidget* w)
- : painter(w), thumbSize(0)
- {
- }
- };
-
ScalableImage* m_image;
std::vector<File> m_files;
std::vector<File*> m_filteredFiles;
std::vector<QString> m_supportedFormats;
- int m_margins, m_padding, m_border;
+ int m_margins, m_border, m_padding, m_spacing;
const File* m_selection;
FilterWidget m_filter;
bool m_ddsAvailable, m_ddsEnabled;
@@ -122,27 +233,29 @@ private:
bool needsFiltering() const;
void scrollAreaResized(const QSize& s);
- void paintThumbnails(QPaintEvent* e);
- void thumbnailsMouseEvent(QMouseEvent* e);
+ void paintThumbnailsArea(QPaintEvent* e);
+ void thumbnailAreaMouseEvent(QMouseEvent* e);
+ void thumbnailAreaWheelEvent(QWheelEvent* e);
+ void onScrolled();
+
void showTooltip(QHelpEvent* e);
void onExplore();
void onShowDDS();
void onFilterChanged();
- int calcThumbSize(int availableWidth) const;
- int calcWidgetHeight(int availableWidth) const;
- QRect calcTopThumbRect(int thumbSize) const;
- std::pair<std::size_t, std::size_t> calcVisibleRange(
- int top, int bottom, int thumbSize) const;
+ ImagesGeometry makeGeometry() const;
+
+ void paintThumbnail(
+ QPainter& painter, const ImagesGeometry& geo,
+ File& file, std::size_t i);
- QRect calcBorderRect(const QRect& topRect, int thumbSize, std::size_t i) const;
- QRect calcImageRect(const QRect& topRect, int thumbSize, std::size_t i) const;
- QSize calcScaledImageSize(
- const QSize& originalSize, const QSize& imageSize) const;
+ void paintThumbnailBorder(
+ QPainter& painter, const ImagesGeometry& geo,
+ std::size_t i);
- void paintThumbnail(PaintContext& cx, std::size_t i);
- void paintThumbnailBorder(PaintContext& cx, std::size_t i);
- void paintThumbnailImage(PaintContext& cx, std::size_t i);
+ void paintThumbnailImage(
+ QPainter& painter, const ImagesGeometry& geo,
+ File& file, std::size_t i);
const File* fileAtPos(const QPoint& p) const;
@@ -151,8 +264,8 @@ private:
File* getFile(std::size_t i);
void filterImages();
- bool needsReload(const File& file, const QSize& imageSize) const;
- void reload(File& file, const QSize& imageSize);
+ bool needsReload(const ImagesGeometry& geo, const File& file) const;
+ void reload(const ImagesGeometry& geo, File& file);
void resizeWidget();
};