diff --git a/app/skin/legacy_skin.hpp b/app/skin/legacy_skin.hpp index 819eeea..a58e7bb 100644 --- a/app/skin/legacy_skin.hpp +++ b/app/skin/legacy_skin.hpp @@ -8,6 +8,7 @@ #include "skin/skin.hpp" +// TODO: convert it to "pure factory" class LegacySkin : public Skin { public: bool hasAlternateSeparator() const override @@ -17,11 +18,13 @@ class LegacySkin : public Skin { bool supportsCustomSeparator() const override { return false; } - std::shared_ptr glyph(char32_t c) const override +protected: + std::shared_ptr create(char32_t c) const override { return _glyphs.value(c, nullptr); } +public: void addGlyph(char32_t c, std::shared_ptr g) { _glyphs[c] = std::move(g); diff --git a/skin_engine/CMakeLists.txt b/skin_engine/CMakeLists.txt index 72e7bc9..09231f5 100644 --- a/skin_engine/CMakeLists.txt +++ b/skin_engine/CMakeLists.txt @@ -20,6 +20,7 @@ qt_add_library(${PROJECT_NAME} SHARED skin/image_glyph.cpp skin/image_glyph.hpp skin/observable.hpp + skin/skin.cpp skin/skin.hpp skin_engine_global.hpp ) diff --git a/skin_engine/skin/container.hpp b/skin_engine/skin/container.hpp index f5289f3..ce777bd 100644 --- a/skin_engine/skin/container.hpp +++ b/skin_engine/skin/container.hpp @@ -25,7 +25,7 @@ class SKIN_ENGINE_EXPORT ContainerImplBase : public Skin::Glyph { public: explicit ContainerImplBase(std::shared_ptr algo); - QRectF rect() const override { return _rect; } + QRectF rect() const override { return _rect.marginsAdded(_mgs); } QPointF advance() const override { return _adv; } void draw(QPainter* p) const override; @@ -38,6 +38,10 @@ class SKIN_ENGINE_EXPORT ContainerImplBase : public Skin::Glyph { auto algorithm() const { return _algo; } + QMarginsF margins() const { return _mgs; } + // no geometry update propagation! + void setMargins(QMarginsF mgs) { _mgs = std::move(mgs); } + void updateGeometry(); private: @@ -46,6 +50,7 @@ class SKIN_ENGINE_EXPORT ContainerImplBase : public Skin::Glyph { // cache geometry data QRectF _rect; QPointF _adv; + QMarginsF _mgs; }; @@ -140,6 +145,14 @@ class SKIN_ENGINE_EXPORT ContainerGlyph : public Glyph { return std::static_pointer_cast(_impl->algorithm()); } + QMarginsF margins() const { return _impl->margins(); } + + void setMargins(QMarginsF mgs) + { + _impl->setMargins(std::move(mgs)); + Glyph::updateGeometry(); + } + // no implicit geometry updates propagation // to allow multiple glyphs to be updated at once // without unnecessary intermediate updates for each of them diff --git a/skin_engine/skin/font_skin.hpp b/skin_engine/skin/font_skin.hpp index 22667b1..a319ec5 100644 --- a/skin_engine/skin/font_skin.hpp +++ b/skin_engine/skin/font_skin.hpp @@ -12,7 +12,7 @@ #include -class SKIN_ENGINE_EXPORT FontSkin : public SkinBase { +class SKIN_ENGINE_EXPORT FontSkin : public Skin { public: explicit FontSkin(QFont font); diff --git a/skin_engine/skin/glyph.cpp b/skin_engine/skin/glyph.cpp index 1d35598..3312cb9 100644 --- a/skin_engine/skin/glyph.cpp +++ b/skin_engine/skin/glyph.cpp @@ -53,6 +53,7 @@ Glyph::Glyph(std::shared_ptr g) , _t(std::make_shared()) , _a(std::make_shared()) { + _g->subscribe(this); _t->subscribe(this); _a->subscribe(this); updateGeometry(); @@ -62,12 +63,15 @@ Glyph::~Glyph() { _a->unsubscribe(this); _t->unsubscribe(this); + _g->unsubscribe(this); } void Glyph::setGlyph(std::shared_ptr g) { if (_g == g) return; + if (_g) _g->unsubscribe(this); _g = std::move(g); + if (_g) _g->subscribe(this); updateGeometry(); dropCachedData(); } @@ -185,6 +189,12 @@ void Glyph::onAppearanceChanged() dropCachedData(); } +void Glyph::onGeometryChanged() +{ + updateGeometry(); + dropCachedData(); +} + void Glyph::updateGeometry() { if (!_g) return; diff --git a/skin_engine/skin/glyph.hpp b/skin_engine/skin/glyph.hpp index 2bb9053..c7d5937 100644 --- a/skin_engine/skin/glyph.hpp +++ b/skin_engine/skin/glyph.hpp @@ -63,7 +63,8 @@ class SKIN_ENGINE_EXPORT Transform : public Observable // non-shareable, as has unique data such as position -class SKIN_ENGINE_EXPORT Glyph : public AppearanceChangeListener { +class SKIN_ENGINE_EXPORT Glyph : public AppearanceChangeListener, + public GeometryChangeListener { public: Glyph() = default; explicit Glyph(std::shared_ptr g); @@ -124,7 +125,8 @@ class SKIN_ENGINE_EXPORT Glyph : public AppearanceChangeListener { void draw(QPainter* p) const; // listeners - void onAppearanceChanged(); + void onAppearanceChanged() override; + void onGeometryChanged() override; protected: void updateGeometry(); diff --git a/skin_engine/skin/graphics.cpp b/skin_engine/skin/graphics.cpp index 3fe4a15..4dc0f0a 100644 --- a/skin_engine/skin/graphics.cpp +++ b/skin_engine/skin/graphics.cpp @@ -34,6 +34,11 @@ void GraphicsBase::setLineSpacing(qreal spacing) _tg.updateGeometry(); } +void GraphicsBase::setMargins(QMarginsF margins) +{ + _tg.setMargins(std::move(margins)); +} + void GraphicsBase::setAlignment(Qt::Alignment a) { _tg.algorithm()->setAlignment(a); diff --git a/skin_engine/skin/graphics.hpp b/skin_engine/skin/graphics.hpp index 1404875..fa60122 100644 --- a/skin_engine/skin/graphics.hpp +++ b/skin_engine/skin/graphics.hpp @@ -23,6 +23,8 @@ class SKIN_ENGINE_EXPORT GraphicsBase { qreal charSpacing() const { return _char_spacing; } qreal lineSpacing() const { return _line_spacing; } + QMarginsF margins() const { return _tg.margins(); } + Qt::Alignment alignment() const { return _tg.algorithm()->alignment(); } Qt::Alignment alignment(size_t i) const { return _tg.algorithm()->alignment(i); } @@ -50,6 +52,8 @@ class SKIN_ENGINE_EXPORT GraphicsBase { void setCharSpacing(qreal spacing); void setLineSpacing(qreal spacing); + void setMargins(QMarginsF margins); + void setAlignment(Qt::Alignment a); void setAlignment(size_t i, Qt::Alignment a); diff --git a/skin_engine/skin/graphics_widgets.cpp b/skin_engine/skin/graphics_widgets.cpp index 00c5581..fc4ec65 100644 --- a/skin_engine/skin/graphics_widgets.cpp +++ b/skin_engine/skin/graphics_widgets.cpp @@ -90,6 +90,13 @@ void GraphicsWidgetBase::setLineSpacing(qreal spacing) update(); } +void GraphicsWidgetBase::setMargins(QMarginsF margins) +{ + _gt->setMargins(std::move(margins)); + updateGeometry(); + update(); +} + void GraphicsWidgetBase::setScaling(qreal sx, qreal sy) { _sx = sx; diff --git a/skin_engine/skin/graphics_widgets.hpp b/skin_engine/skin/graphics_widgets.hpp index c8a56a0..1632ca9 100644 --- a/skin_engine/skin/graphics_widgets.hpp +++ b/skin_engine/skin/graphics_widgets.hpp @@ -36,6 +36,8 @@ class SKIN_ENGINE_EXPORT GraphicsWidgetBase : public QWidget qreal charSpacing() const { return _gt->charSpacing(); } qreal lineSpacing() const { return _gt->lineSpacing(); } + QMarginsF margins() const { return _gt->margins(); } + qreal scalingX() const { return _sx; } qreal scalingY() const { return _sy; } @@ -75,6 +77,8 @@ public slots: void setCharSpacing(qreal spacing); void setLineSpacing(qreal spacing); + void setMargins(QMarginsF margins); + void setScalingX(qreal sx) { setScaling(sx, scalingY()); } void setScalingY(qreal sy) { setScaling(scalingX(), sy); } void setScaling(qreal sx, qreal sy); diff --git a/skin_engine/skin/skin.cpp b/skin_engine/skin/skin.cpp new file mode 100644 index 0000000..cba58ed --- /dev/null +++ b/skin_engine/skin/skin.cpp @@ -0,0 +1,70 @@ +/* + * SPDX-FileCopyrightText: 2024 Nick Korotysh + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "skin.hpp" + +namespace { + +class GlyphDecoratorBase : public Skin::Glyph { +public: + explicit GlyphDecoratorBase(std::shared_ptr g) + : _g(std::move(g)) + { + Q_ASSERT(_g); + } + + std::shared_ptr inner() const { return _g; } + + QRectF rect() const override { return _g->rect(); } + QPointF advance() const override { return _g->advance(); } + + void draw(QPainter* p) const override { _g->draw(p); } + +private: + std::shared_ptr _g; +}; + +} // namespace + +class Skin::DecoratedGlyph : public GlyphDecoratorBase { +public: + using GlyphDecoratorBase::GlyphDecoratorBase; + + QMarginsF margins() const { return _mgs; } + + void setMargins(QMarginsF mgs) + { + if (_mgs == mgs) return; + _mgs = std::move(mgs); + notify(&GeometryChangeListener::onGeometryChanged); + } + + QRectF rect() const override { return inner()->rect().marginsAdded(_mgs); } + +private: + QMarginsF _mgs; +}; + + +std::shared_ptr Skin::glyph(char32_t c) const +{ + auto& cached_glyph = _cache[c]; + if (!cached_glyph) { + if (auto g = create(c)) { + cached_glyph = std::make_shared(std::move(g)); + cached_glyph->setMargins(_mgs); + } + } + return cached_glyph; +} + +void Skin::setMargins(QMarginsF mgs) +{ + if (_mgs == mgs) return; + for (auto& g : std::as_const(_cache)) + g->setMargins(mgs); + _mgs = std::move(mgs); +} diff --git a/skin_engine/skin/skin.hpp b/skin_engine/skin/skin.hpp index 0a043f4..9a20ef3 100644 --- a/skin_engine/skin/skin.hpp +++ b/skin_engine/skin/skin.hpp @@ -13,9 +13,18 @@ #include #include "skin_engine_global.hpp" +#include "observable.hpp" class QPainter; +class SKIN_ENGINE_EXPORT GeometryChangeListener { +public: + virtual ~GeometryChangeListener() = default; + + virtual void onGeometryChanged() = 0; +}; + + class SKIN_ENGINE_EXPORT Skin { public: virtual ~Skin() = default; @@ -24,7 +33,7 @@ class SKIN_ENGINE_EXPORT Skin { virtual bool hasAlternateSeparator() const = 0; virtual bool supportsCustomSeparator() const = 0; - class Glyph { + class Glyph : public Observable { public: virtual ~Glyph() = default; @@ -35,22 +44,18 @@ class SKIN_ENGINE_EXPORT Skin { virtual void draw(QPainter* p) const = 0; }; - virtual std::shared_ptr glyph(char32_t c) const = 0; -}; + std::shared_ptr glyph(char32_t c) const; + QMarginsF margins() const { return _mgs; } -class SKIN_ENGINE_EXPORT SkinBase : public Skin { -public: - std::shared_ptr glyph(char32_t c) const override - { - auto& cached_glyph = _cache[c]; - if (!cached_glyph) cached_glyph = create(c); - return cached_glyph; - } + void setMargins(QMarginsF mgs); protected: virtual std::shared_ptr create(char32_t c) const = 0; private: - mutable QHash> _cache; + QMarginsF _mgs; + + class DecoratedGlyph; + mutable QHash> _cache; };