summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorisanae <14251494+isanae@users.noreply.github.com>2019-06-27 10:27:02 -0400
committerisanae <14251494+isanae@users.noreply.github.com>2019-07-02 10:10:19 -0400
commit1c0024b2feda8b5c152d2e211205601ee807e517 (patch)
tree41c54eea23eb434c8dfd94ceb4fd6a81d8a6dec0 /src
parent933a2c7d1086ebc87ee2d214deaa3752ea8c6cf1 (diff)
moved all images classes into a namespace with shorter names
split theme and metrics into their own class added filename to thumbs
Diffstat (limited to 'src')
-rw-r--r--src/modinfodialog.ui8
-rw-r--r--src/modinfodialogimages.cpp379
-rw-r--r--src/modinfodialogimages.h251
3 files changed, 399 insertions, 239 deletions
diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui
index e745967d..3da13261 100644
--- a/src/modinfodialog.ui
+++ b/src/modinfodialog.ui
@@ -207,14 +207,14 @@
<number>0</number>
</property>
<item>
- <widget class="ImagesThumbnails" name="imagesThumbnails" native="true">
+ <widget class="ImagesTabHelpers::ThumbnailsWidget" name="imagesThumbnails" native="true">
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>
</widget>
</item>
<item>
- <widget class="ImagesScrollbar" name="imagesScrollerVBar">
+ <widget class="ImagesTabHelpers::Scrollbar" name="imagesScrollerVBar">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
@@ -1239,13 +1239,13 @@ p, li { white-space: pre-wrap; }
<header>texteditor.h</header>
</customwidget>
<customwidget>
- <class>ImagesThumbnails</class>
+ <class>ImagesTabHelpers::ThumbnailsWidget</class>
<extends>QWidget</extends>
<header>modinfodialogimages.h</header>
<container>1</container>
</customwidget>
<customwidget>
- <class>ImagesScrollbar</class>
+ <class>ImagesTabHelpers::Scrollbar</class>
<extends>QScrollBar</extends>
<header>modinfodialogimages.h</header>
</customwidget>
diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp
index 7738d55f..8c323ac6 100644
--- a/src/modinfodialogimages.cpp
+++ b/src/modinfodialogimages.cpp
@@ -3,6 +3,8 @@
#include "settings.h"
#include "utility.h"
+using namespace ImagesTabHelpers;
+
QSize resizeWithAspectRatio(const QSize& original, const QSize& available)
{
const auto ratio = std::min({
@@ -32,7 +34,6 @@ ImagesTab::ImagesTab(
QWidget* parent, Ui::ModInfoDialog* ui, int id) :
ModInfoDialogTab(oc, plugin, parent, ui, id),
m_image(new ScalableImage),
- m_margins(3), m_border(1), m_padding(0), m_spacing(5),
m_ddsAvailable(false), m_ddsEnabled(false)
{
getSupportedFormats();
@@ -69,11 +70,19 @@ ImagesTab::ImagesTab(
auto list = std::make_unique<QListWidget>();
parentWidget()->style()->polish(list.get());
- m_colors.border = QColor(Qt::black);
- m_colors.background = QColor(Qt::black);
- m_colors.selection = list->palette().color(QPalette::Highlight);
+ m_theme.borderColor = QColor(Qt::black);
+ m_theme.backgroundColor = QColor(Qt::black);
+ m_theme.textColor = list->palette().color(QPalette::WindowText);
+
+ m_theme.highlightBackgroundColor = list->palette().color(QPalette::Highlight);
+ m_theme.highlightTextColor = list->palette().color(QPalette::HighlightedText);
+
+ m_theme.font = list->font();
- m_image->setColors(m_colors.border, m_colors.background);
+ const QFontMetrics fm(m_theme.font);
+ m_metrics.textHeight = fm.height();
+
+ m_image->setColors(m_theme.borderColor, m_theme.backgroundColor);
}
}
@@ -165,8 +174,8 @@ void ImagesTab::switchToFiltered()
if (hasTextFilter) {
// check filter widget
const auto m = m_filter.matches([&](auto&& what) {
- return f.path.contains(what, Qt::CaseInsensitive);
- });
+ return f.path().contains(what, Qt::CaseInsensitive);
+ });
if (!m) {
// no match, skip
@@ -176,7 +185,7 @@ void ImagesTab::switchToFiltered()
if (!m_ddsEnabled) {
// skip .dds files
- if (f.path.endsWith(".dds", Qt::CaseInsensitive)) {
+ if (f.path().endsWith(".dds", Qt::CaseInsensitive)) {
continue;
}
}
@@ -227,9 +236,9 @@ void ImagesTab::select(std::size_t i)
// pass a null image in setImage() below
f->ensureOriginalLoaded();
- ui->imagesPath->setText(QDir::toNativeSeparators(f->path));
+ ui->imagesPath->setText(QDir::toNativeSeparators(f->path()));
ui->imagesExplore->setEnabled(true);
- m_image->setImage(f->original);
+ m_image->setImage(f->original());
ensureVisible(i);
} else {
ui->imagesPath->clear();
@@ -297,7 +306,7 @@ std::size_t ImagesTab::fileIndexAtPos(const QPoint& p) const
// this is the index relative to the top
const auto offset = geo.indexAt(p);
- if (offset == ImagesGeometry::BadIndex) {
+ if (offset == BadIndex) {
return BadIndex;
}
@@ -314,7 +323,7 @@ std::size_t ImagesTab::fileIndexAtPos(const QPoint& p) const
return i;
}
-const ImagesTab::File* ImagesTab::fileAtPos(const QPoint& p) const
+const File* ImagesTab::fileAtPos(const QPoint& p) const
{
const auto i = fileIndexAtPos(p);
if (i >= m_files.size()) {
@@ -324,11 +333,9 @@ const ImagesTab::File* ImagesTab::fileAtPos(const QPoint& p) const
return m_files.get(i);
}
-ImagesGeometry ImagesTab::makeGeometry() const
+Geometry ImagesTab::makeGeometry() const
{
- return ImagesGeometry(
- ui->imagesThumbnails->size(),
- m_margins, m_border, m_padding, m_spacing);
+ return Geometry(ui->imagesThumbnails->size(), m_metrics);
}
void ImagesTab::paintThumbnailsArea(QPaintEvent* e)
@@ -362,13 +369,14 @@ void ImagesTab::paintThumbnail(const PaintContext& cx)
paintThumbnailBackground(cx);
paintThumbnailBorder(cx);
paintThumbnailImage(cx);
+ paintThumbnailText(cx);
}
void ImagesTab::paintThumbnailBackground(const PaintContext& cx)
{
if (m_files.selectedIndex() == cx.fileIndex) {
const auto rect = cx.geo.thumbRect(cx.thumbIndex);
- cx.painter.fillRect(rect, m_colors.selection);
+ cx.painter.fillRect(rect, m_theme.highlightBackgroundColor);
}
}
@@ -381,30 +389,46 @@ void ImagesTab::paintThumbnailBorder(const PaintContext& cx)
borderRect.setRight(borderRect.right() - 1);
borderRect.setBottom(borderRect.bottom() - 1);
- cx.painter.setPen(m_colors.border);
+ cx.painter.setPen(m_theme.borderColor);
cx.painter.drawRect(borderRect);
}
void ImagesTab::paintThumbnailImage(const PaintContext& cx)
{
- if (cx.file->failed) {
+ if (cx.file->failed()) {
return;
}
- if (needsReload(cx.geo, *cx.file)) {
- reload(cx.geo, *cx.file);
- }
-
- if (cx.file->thumbnail.isNull()) {
- return;
- }
+ cx.file->loadIfNeeded(cx.geo);
const auto imageRect = cx.geo.imageRect(cx.thumbIndex);
const auto scaledThumbRect = centeredRect(
- imageRect, cx.file->thumbnail.size());
+ imageRect, cx.file->thumbnail().size());
- cx.painter.fillRect(scaledThumbRect, m_colors.background);
- cx.painter.drawImage(scaledThumbRect, cx.file->thumbnail);
+ cx.painter.fillRect(scaledThumbRect, m_theme.backgroundColor);
+ cx.painter.drawImage(scaledThumbRect, cx.file->thumbnail());
+}
+
+void ImagesTab::paintThumbnailText(const PaintContext& cx)
+{
+ const auto tr = cx.geo.textRect(cx.thumbIndex);
+
+ if (cx.fileIndex == m_files.selectedIndex()) {
+ cx.painter.setPen(m_theme.highlightTextColor);
+ } else {
+ cx.painter.setPen(m_theme.textColor);
+ }
+
+ cx.painter.setFont(m_theme.font);
+
+ QFontMetrics fm(m_theme.font);
+
+ const auto text = fm.elidedText(
+ cx.file->filename(), Qt::ElideRight, tr.width());
+
+ const auto flags = Qt::AlignHCenter | Qt::AlignVCenter | Qt::TextSingleLine;
+
+ cx.painter.drawText(tr, flags, text);
}
void ImagesTab::scrollAreaResized(const QSize&)
@@ -487,14 +511,14 @@ void ImagesTab::showTooltip(QHelpEvent* e)
}
QToolTip::showText(
- e->globalPos(), QDir::toNativeSeparators(f->path),
+ e->globalPos(), QDir::toNativeSeparators(f->path()),
ui->imagesThumbnails);
}
void ImagesTab::onExplore()
{
if (auto* f=m_files.selectedFile()) {
- MOBase::shell::ExploreFile(f->path);
+ MOBase::shell::ExploreFile(f->path());
}
}
@@ -512,34 +536,6 @@ void ImagesTab::onFilterChanged()
update();
}
-bool ImagesTab::needsReload(const ImagesGeometry& geo, const File& file) const
-{
- if (file.failed) {
- return false;
- }
-
- if (file.original.isNull() || file.thumbnail.isNull()) {
- return true;
- }
-
- const auto scaledSize = geo.scaledImageSize(file.original.size());
- return (file.thumbnail.size() != scaledSize);
-}
-
-void ImagesTab::reload(const ImagesGeometry& geo, File& file)
-{
- file.failed = false;
- file.ensureOriginalLoaded();
-
- if (file.failed) {
- return;
- }
-
- file.thumbnail = file.original.scaled(
- geo.scaledImageSize(file.original.size()),
- Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
-}
-
void ImagesTab::updateScrollbar()
{
if (m_files.size() == 0) {
@@ -565,40 +561,56 @@ void ImagesTab::updateScrollbar()
}
-void ImagesThumbnails::setTab(ImagesTab* tab)
+namespace ImagesTabHelpers
+{
+
+void Scrollbar::setTab(ImagesTab* tab)
+{
+ m_tab = tab;
+}
+
+void Scrollbar::wheelEvent(QWheelEvent* e)
+{
+ if (m_tab) {
+ m_tab->thumbnailAreaWheelEvent(e);
+ }
+}
+
+
+void ThumbnailsWidget::setTab(ImagesTab* tab)
{
m_tab = tab;
}
-void ImagesThumbnails::paintEvent(QPaintEvent* e)
+void ThumbnailsWidget::paintEvent(QPaintEvent* e)
{
if (m_tab) {
m_tab->paintThumbnailsArea(e);
}
}
-void ImagesThumbnails::mousePressEvent(QMouseEvent* e)
+void ThumbnailsWidget::mousePressEvent(QMouseEvent* e)
{
if (m_tab) {
m_tab->thumbnailAreaMouseEvent(e);
}
}
-void ImagesThumbnails::wheelEvent(QWheelEvent* e)
+void ThumbnailsWidget::wheelEvent(QWheelEvent* e)
{
if (m_tab) {
m_tab->thumbnailAreaWheelEvent(e);
}
}
-void ImagesThumbnails::resizeEvent(QResizeEvent* e)
+void ThumbnailsWidget::resizeEvent(QResizeEvent* e)
{
if (m_tab) {
m_tab->scrollAreaResized(e->size());
}
}
-void ImagesThumbnails::keyPressEvent(QKeyEvent* e)
+void ThumbnailsWidget::keyPressEvent(QKeyEvent* e)
{
if (m_tab) {
if (m_tab->thumbnailAreaKeyPressEvent(e)) {
@@ -609,7 +621,7 @@ void ImagesThumbnails::keyPressEvent(QKeyEvent* e)
QWidget::keyPressEvent(e);
}
-bool ImagesThumbnails::event(QEvent* e)
+bool ThumbnailsWidget::event(QEvent* e)
{
if (e->type() == QEvent::ToolTip) {
m_tab->showTooltip(static_cast<QHelpEvent*>(e));
@@ -620,19 +632,6 @@ 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)
{
@@ -724,104 +723,220 @@ void ScalableImage::paintEvent(QPaintEvent* e)
}
-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())
+Metrics::Metrics() :
+ margins(3),
+ border(1),
+ padding(0),
+ spacing(5),
+ textSpacing(2),
+ textHeight(0)
+{
+}
+
+
+Geometry::Geometry(QSize widgetSize, Metrics metrics)
+ : m_widgetSize(widgetSize), m_metrics(metrics), m_topRect(calcTopRect())
{
}
-QRect ImagesGeometry::calcTopRect() const
+QRect Geometry::calcTopRect() const
{
const auto thumbWidth = m_widgetSize.width();
- const auto imageSize = thumbWidth - (m_margins * 2) - (m_border * 2) - (m_padding * 2);
+ const auto& m = m_metrics;
+
+ const auto imageSize =
+ thumbWidth -
+ (m.margins * 2) -
+ (m.border * 2) -
+ (m.padding * 2);
+
const auto thumbHeight =
- m_margins + m_border + m_padding +
+ m.margins +
+ m.border + m.padding +
imageSize +
- m_border + m_padding + m_margins;
+ m.padding + m.border +
+ m.textSpacing + m.textHeight +
+ m.margins;
return {0, 0, thumbWidth, thumbHeight};
}
-std::size_t ImagesGeometry::fullyVisibleCount() const
+std::size_t Geometry::fullyVisibleCount() const
{
const auto r = thumbRect(0);
- const auto visible = (m_widgetSize.height() / (r.height() + m_spacing));
+
+ const auto thumbWithSpacing = r.height() + m_metrics.spacing;
+ const auto visible = (m_widgetSize.height() / thumbWithSpacing);
+
return static_cast<std::size_t>(visible);
}
-QRect ImagesGeometry::thumbRect(std::size_t i) const
+QRect Geometry::thumbRect(std::size_t i) const
{
// rect for the top thumbnail
QRect r = m_topRect;
// move down
- const auto thumbWithSpacing = m_spacing + r.height();
+ const auto thumbWithSpacing = m_metrics.spacing + r.height();
r.translate(0, static_cast<int>(i * thumbWithSpacing));
return r;
}
-QRect ImagesGeometry::borderRect(std::size_t i) const
+QRect Geometry::borderRect(std::size_t i) const
{
auto r = thumbRect(i);
+ const auto& m = m_metrics;
- // remove margins
- const auto m = m_margins;
- r.adjust(m, m, -m, -m);
+ // remove margins and text
+ r.adjust(m.margins, m.margins, -m.margins, -m.margins);
+
+ // remove text
+ r.adjust(0, 0, 0, -(m.textSpacing + m.textHeight));
return r;
}
-QRect ImagesGeometry::imageRect(std::size_t i) const
+QRect Geometry::imageRect(std::size_t i) const
{
auto r = borderRect(i);
// remove border and padding
- const auto m = m_border + m_padding;
+ const auto m = m_metrics.border + m_metrics.padding;
r.adjust(m, m, -m, -m);
return r;
}
-std::size_t ImagesGeometry::indexAt(const QPoint& p) const
+QRect Geometry::textRect(std::size_t i) const
+{
+ const auto r = borderRect(i);
+
+ return QRect(
+ r.left(),
+ r.bottom() + m_metrics.textSpacing,
+ r.width(),
+ m_metrics.textHeight);
+}
+
+QRect Geometry::selectionRect(std::size_t i) const
+{
+ const auto br = borderRect(i);
+ const auto tr = textRect(i);
+
+ return QRect(
+ br.left(),
+ br.top(),
+ br.width(),
+ tr.bottom() - br.top());
+}
+
+std::size_t Geometry::indexAt(const QPoint& p) const
{
// calculate index purely based on y position
- const std::size_t offset = p.y() / (m_topRect.height() + m_spacing);
+ const std::size_t offset = p.y() / (m_topRect.height() + m_metrics.spacing);
- if (!borderRect(offset).contains(p)) {
+ if (!selectionRect(offset).contains(p)) {
return BadIndex;
}
return offset;
}
-QSize ImagesGeometry::scaledImageSize(const QSize& originalSize) const
+QSize Geometry::scaledImageSize(const QSize& originalSize) const
{
const auto availableSize = imageRect(0).size();
return resizeWithAspectRatio(originalSize, availableSize);
}
-void ImagesGeometry::dump() const
+
+File::File(QString path)
+ : m_path(std::move(path)), m_failed(false)
+{
+}
+
+void File::ensureOriginalLoaded()
+{
+ if (m_original.isNull()) {
+ if (!m_original.load(m_path)) {
+ qCritical() << "failed to load image from " << m_path;
+ m_failed = true;
+ }
+ }
+}
+
+const QString& File::path() const
+{
+ return m_path;
+}
+
+const QString& File::filename() const
+{
+ if (m_filename.isEmpty()) {
+ m_filename = QFileInfo(m_path).fileName();
+ }
+
+ return m_filename;
+}
+
+const QImage& File::original() 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;
+ return m_original;
}
+const QImage& File::thumbnail() const
+{
+ return m_thumbnail;
+}
+
+bool File::failed() const
+{
+ return m_failed;
+}
+
+void File::loadIfNeeded(const Geometry& geo)
+{
+ if (needsLoad(geo)) {
+ load(geo);
+ }
+}
+
+bool File::needsLoad(const Geometry& geo) const
+{
+ if (m_failed) {
+ return false;
+ }
+
+ if (m_original.isNull() || m_thumbnail.isNull()) {
+ return true;
+ }
-ImagesTab::Files::Files()
+ const auto scaledSize = geo.scaledImageSize(m_original.size());
+ return (m_thumbnail.size() != scaledSize);
+}
+
+void File::load(const Geometry& geo)
+{
+ m_failed = false;
+ ensureOriginalLoaded();
+
+ if (m_failed) {
+ return;
+ }
+
+ const auto scaledSize = geo.scaledImageSize(m_original.size());
+
+ m_thumbnail = m_original.scaled(
+ scaledSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
+}
+
+
+Files::Files()
: m_selection(BadIndex), m_filtered(false)
{
}
-void ImagesTab::Files::clear()
+void Files::clear()
{
m_allFiles.clear();
m_filteredFiles.clear();
@@ -829,17 +944,17 @@ void ImagesTab::Files::clear()
m_filtered = false;
}
-void ImagesTab::Files::add(File f)
+void Files::add(File f)
{
m_allFiles.emplace_back(std::move(f));
}
-void ImagesTab::Files::addFiltered(File* f)
+void Files::addFiltered(File* f)
{
m_filteredFiles.push_back(f);
}
-bool ImagesTab::Files::empty() const
+bool Files::empty() const
{
if (m_filtered) {
return m_filteredFiles.empty();
@@ -848,7 +963,7 @@ bool ImagesTab::Files::empty() const
}
}
-std::size_t ImagesTab::Files::size() const
+std::size_t Files::size() const
{
if (m_filtered) {
return m_filteredFiles.size();
@@ -857,19 +972,19 @@ std::size_t ImagesTab::Files::size() const
}
}
-void ImagesTab::Files::switchToAll()
+void Files::switchToAll()
{
m_filtered = false;
m_filteredFiles.clear();
}
-void ImagesTab::Files::switchToFiltered()
+void Files::switchToFiltered()
{
m_filtered = true;
m_filteredFiles.clear();
}
-const ImagesTab::File* ImagesTab::Files::get(std::size_t i) const
+const File* Files::get(std::size_t i) const
{
if (m_filtered) {
if (i < m_filteredFiles.size()) {
@@ -884,12 +999,12 @@ const ImagesTab::File* ImagesTab::Files::get(std::size_t i) const
return nullptr;
}
-ImagesTab::File* ImagesTab::Files::get(std::size_t i)
+File* Files::get(std::size_t i)
{
return const_cast<File*>(std::as_const(*this).get(i));
}
-std::size_t ImagesTab::Files::indexOf(const File* f) const
+std::size_t Files::indexOf(const File* f) const
{
if (m_filtered) {
for (std::size_t i=0; i<m_filteredFiles.size(); ++i) {
@@ -908,32 +1023,40 @@ std::size_t ImagesTab::Files::indexOf(const File* f) const
return BadIndex;
}
-const ImagesTab::File* ImagesTab::Files::selectedFile() const
+const File* Files::selectedFile() const
{
return get(m_selection);
}
-ImagesTab::File* ImagesTab::Files::selectedFile()
+File* Files::selectedFile()
{
return get(m_selection);
}
-std::size_t ImagesTab::Files::selectedIndex() const
+std::size_t Files::selectedIndex() const
{
return m_selection;
}
-void ImagesTab::Files::select(std::size_t i)
+void Files::select(std::size_t i)
{
m_selection = i;
}
-std::vector<ImagesTab::File>& ImagesTab::Files::allFiles()
+std::vector<File>& Files::allFiles()
{
return m_allFiles;
}
-bool ImagesTab::Files::isFiltered() const
+bool Files::isFiltered() const
{
return m_filtered;
}
+
+
+PaintContext::PaintContext(QWidget* w, Geometry geo)
+ : painter(w), geo(geo), file(nullptr), thumbIndex(0), fileIndex(0)
+{
+}
+
+} // namespace
diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h
index 5ce66d5d..0d34b46a 100644
--- a/src/modinfodialogimages.h
+++ b/src/modinfodialogimages.h
@@ -7,10 +7,15 @@
class ImagesTab;
+namespace ImagesTabHelpers
+{
+
+static constexpr std::size_t BadIndex = std::numeric_limits<std::size_t>::max();
+
// vertical scrollbar, this is only to handle wheel events to scroll by one
// instead of the system's scroll setting
//
-class ImagesScrollbar : public QScrollBar
+class Scrollbar : public QScrollBar
{
public:
using QScrollBar::QScrollBar;
@@ -29,7 +34,7 @@ private:
// widget inside the scroller, calls ImagesTab::paintThumbnailArea() when
// needed and also forwards mouse clicks and tooltip events
//
-class ImagesThumbnails : public QWidget
+class ThumbnailsWidget : public QWidget
{
Q_OBJECT;
@@ -101,15 +106,47 @@ private:
};
+struct Theme
+{
+ QColor borderColor, backgroundColor, textColor;
+ QColor highlightBackgroundColor, highlightTextColor;
+ QFont font;
+};
+
+
+struct Metrics
+{
+ // space outside the thumbnail border
+ int margins;
+
+ // size of the border
+ int border;
+
+ // space between the border and the image
+ int padding;
+
+ // spacing between the thumbnail and the text
+ int textSpacing;
+
+ // height of the text
+ int textHeight;
+
+ // spacing between thumbnails
+ int spacing;
+
+ Metrics();
+};
+
+
// handles all the geometry calculations by ImagesTab for painting or handling
// mouse clicks
//
// a thumbnail looks like this:
//
-// +-----------------------+ <-- thumb rect
+// +-----------------------+ <--- thumb rect
// | margins |
// | |
-// | +-border--------+ <------ border rect
+// | +-border--------+ <------- border rect
// | | padding | |
// | | | |
// | | +-------+ <----------- image rect
@@ -119,6 +156,10 @@ private:
// | | +-------+ | |
// | | | |
// | +---------------+ |
+// | text spacing |
+// | +---------------+ <------- text rect
+// | | text | |
+// | +---------------+ |
// | |
// +-----------------------+
//
@@ -130,15 +171,10 @@ private:
// ....
//
//
-class ImagesGeometry
+class Geometry
{
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);
+ Geometry(QSize widgetSize, Metrics metrics);
// returns the number of images fully visible in the widget
//
@@ -156,9 +192,20 @@ public:
//
QRect imageRect(std::size_t i) const;
+ // rectangle of the text for the given thumbnail
+ //
+ QRect textRect(std::size_t i) const;
+
+ // rectangle that responds to selection: includes the border and extends down
+ // to the text
+ //
+ QRect selectionRect(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
//
+ // returns BadIndex if there's no thumbnail at this point
+ //
std::size_t indexAt(const QPoint& p) const;
// returns the size of the image that fits in imageRect() while keeping the
@@ -166,25 +213,12 @@ public:
//
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;
+ // metrics
+ const Metrics m_metrics;
// rectangle of the first thumbnail on top
const QRect m_topRect;
@@ -196,112 +230,116 @@ private:
};
-class ImagesTab : public ModInfoDialogTab
+class File
{
- Q_OBJECT;
- friend class ImagesScrollbar;
- friend class ImagesThumbnails;
-
public:
- ImagesTab(
- OrganizerCore& oc, PluginContainer& plugin,
- QWidget* parent, Ui::ModInfoDialog* ui, int id);
+ File(QString path);
- void clear() override;
- bool feedFile(const QString& rootPath, const QString& fullPath) override;
- void update() override;
- void saveState(Settings& s) override;
- void restoreState(const Settings& s) override;
+ void ensureOriginalLoaded();
+
+ const QString& path() const;
+ const QString& filename() const;
+ const QImage& original() const;
+ const QImage& thumbnail() const;
+ bool failed() const;
+
+ void loadIfNeeded(const Geometry& geo);
private:
- static constexpr std::size_t BadIndex =
- std::numeric_limits<std::size_t>::max();
+ QString m_path;
+ mutable QString m_filename;
+ QImage m_original, m_thumbnail;
+ bool m_failed;
+
+ bool needsLoad(const Geometry& geo) const;
+ void load(const Geometry& geo);
+};
+
+
+class Files
+{
+public:
+ Files();
+
+ void clear();
- struct File
- {
- QString path;
- QImage original, thumbnail;
- bool failed = false;
+ void add(File f);
+ void addFiltered(File* f);
- File(QString path)
- : path(std::move(path))
- {
- }
+ bool empty() const;
+ std::size_t size() const;
- void ensureOriginalLoaded()
- {
- if (original.isNull()) {
- if (!original.load(path)) {
- qCritical() << "failed to load image from " << path;
- failed = true;
- }
- }
- }
- };
+ void switchToAll();
+ void switchToFiltered();
- struct Colors
- {
- QColor border, background, selection;
- };
+ const File* get(std::size_t i) const;
+ File* get(std::size_t i);
+ std::size_t indexOf(const File* f) const;
- struct PaintContext
- {
- mutable QPainter painter;
- ImagesGeometry geo;
- File* file;
- std::size_t thumbIndex;
- std::size_t fileIndex;
+ const File* selectedFile() const;
+ File* selectedFile();
+ std::size_t selectedIndex() const;
+ void select(std::size_t i);
- PaintContext(QWidget* w, ImagesGeometry geo)
- : painter(w), geo(geo), file(nullptr), thumbIndex(0), fileIndex(0)
- {
- }
- };
+ std::vector<File>& allFiles();
- class Files
- {
- public:
- Files();
+ bool isFiltered() const;
- void clear();
+private:
+ std::vector<File> m_allFiles;
+ std::vector<File*> m_filteredFiles;
+ std::size_t m_selection;
+ bool m_filtered;
+};
- void add(File f);
- void addFiltered(File* f);
- bool empty() const;
- std::size_t size() const;
+struct PaintContext
+{
+ mutable QPainter painter;
+ Geometry geo;
+ File* file;
+ std::size_t thumbIndex;
+ std::size_t fileIndex;
- void switchToAll();
- void switchToFiltered();
+ PaintContext(QWidget* w, Geometry geo);
+};
- const File* get(std::size_t i) const;
- File* get(std::size_t i);
- std::size_t indexOf(const File* f) const;
+} // namespace
- const File* selectedFile() const;
- File* selectedFile();
- std::size_t selectedIndex() const;
- void select(std::size_t i);
- std::vector<File>& allFiles();
+class ImagesTab : public ModInfoDialogTab
+{
+ Q_OBJECT;
+ friend class ImagesTabHelpers::Scrollbar;
+ friend class ImagesTabHelpers::ThumbnailsWidget;
- bool isFiltered() const;
+public:
+ ImagesTab(
+ OrganizerCore& oc, PluginContainer& plugin,
+ QWidget* parent, Ui::ModInfoDialog* ui, int id);
- private:
- std::vector<File> m_allFiles;
- std::vector<File*> m_filteredFiles;
- std::size_t m_selection;
- bool m_filtered;
- };
+ void clear() override;
+ bool feedFile(const QString& rootPath, const QString& fullPath) override;
+ void update() override;
+ void saveState(Settings& s) override;
+ void restoreState(const Settings& s) override;
+private:
+ using ScalableImage = ImagesTabHelpers::ScalableImage;
+ using Files = ImagesTabHelpers::Files;
+ using File = ImagesTabHelpers::File;
+ using Theme = ImagesTabHelpers::Theme;
+ using Metrics = ImagesTabHelpers::Metrics;
+ using PaintContext = ImagesTabHelpers::PaintContext;
+ using Geometry = ImagesTabHelpers::Geometry;
ScalableImage* m_image;
std::vector<QString> m_supportedFormats;
Files m_files;
- int m_margins, m_border, m_padding, m_spacing;
FilterWidget m_filter;
bool m_ddsAvailable, m_ddsEnabled;
- Colors m_colors;
+ Theme m_theme;
+ Metrics m_metrics;
void getSupportedFormats();
void enableDDS(bool b);
@@ -325,18 +363,17 @@ private:
std::size_t fileIndexAtPos(const QPoint& p) const;
const File* fileAtPos(const QPoint& p) const;
- ImagesGeometry makeGeometry() const;
+ Geometry makeGeometry() const;
void paintThumbnail(const PaintContext& cx);
void paintThumbnailBackground(const PaintContext& cx);
void paintThumbnailBorder(const PaintContext& cx);
void paintThumbnailImage(const PaintContext& cx);
+ void paintThumbnailText(const PaintContext& cx);
void checkFiltering();
void switchToAll();
void switchToFiltered();
- bool needsReload(const ImagesGeometry& geo, const File& file) const;
- void reload(const ImagesGeometry& geo, File& file);
void updateScrollbar();
};