From 97afa782a0fbb8847135b3c93ca70d979d9fdfdf Mon Sep 17 00:00:00 2001 From: jmsgrogan Date: Thu, 19 Jan 2023 14:25:58 +0000 Subject: [PATCH] Add path rendering. --- src/base/geometry/CMakeLists.txt | 2 + src/base/geometry/path/Line.cpp | 75 ++++++ src/base/geometry/path/Line.h | 19 ++ src/base/geometry/path/LineSegment.cpp | 20 ++ src/base/geometry/path/LineSegment.h | 10 + src/base/geometry/path/Path.cpp | 50 +++- src/base/geometry/path/Path.h | 51 +++- src/base/geometry/path/PathElement.h | 8 + .../geometry/path/PathPostScriptConverter.cpp | 235 ++++++++++++++++++ .../geometry/path/PathPostScriptConverter.h | 61 +++++ src/base/geometry/points/PointCollection.cpp | 22 ++ src/base/geometry/points/PointCollection.h | 6 + src/publishing/CMakeLists.txt | 3 - .../graphics/directx/DirectX2dPainter.cpp | 186 ++++++++++---- .../graphics/directx/DirectX2dPainter.h | 10 + src/rendering/visual_elements/CMakeLists.txt | 4 + .../visual_elements/nodes/PathNode.cpp | 59 +++++ .../visual_elements/nodes/PathNode.h | 24 ++ src/rendering/visual_elements/svg/SvgNode.cpp | 62 +++-- src/rendering/visual_elements/svg/SvgNode.h | 3 + .../visual_elements}/svg/SvgPainter.cpp | 81 ++++-- .../visual_elements}/svg/SvgPainter.h | 8 +- .../visual_elements/svg/SvgReader.cpp | 4 + .../visual_elements/svg/SvgTextElement.cpp | 6 + .../svg/elements/SvgShapeElements.cpp | 10 + .../svg/elements/SvgShapeElements.h | 2 + src/ui/ui_controls/Button.cpp | 58 ++++- src/ui/ui_controls/Button.h | 15 +- src/ui/ui_elements/IconNode.cpp | 14 ++ src/ui/ui_elements/IconNode.h | 17 +- .../style/MediaResourceManager.cpp | 33 +++ .../ui_elements/style/MediaResourceManager.h | 19 ++ src/ui/ui_elements/style/MediaResources.cpp | 12 + src/ui/ui_elements/style/MediaResources.h | 23 ++ test/CMakeLists.txt | 2 +- test/geometry/CMakeLists.txt | 20 +- test/geometry/TestPath.cpp | 10 + test/publishing/TestSvgToNodeConverter.cpp | 24 +- test/ui_controls/TestButton.cpp | 11 +- 39 files changed, 1148 insertions(+), 131 deletions(-) create mode 100644 src/base/geometry/path/PathPostScriptConverter.cpp create mode 100644 src/base/geometry/path/PathPostScriptConverter.h create mode 100644 src/rendering/visual_elements/nodes/PathNode.cpp create mode 100644 src/rendering/visual_elements/nodes/PathNode.h rename src/{publishing => rendering/visual_elements}/svg/SvgPainter.cpp (73%) rename src/{publishing => rendering/visual_elements}/svg/SvgPainter.h (77%) create mode 100644 test/geometry/TestPath.cpp diff --git a/src/base/geometry/CMakeLists.txt b/src/base/geometry/CMakeLists.txt index 2402c98..7932c63 100644 --- a/src/base/geometry/CMakeLists.txt +++ b/src/base/geometry/CMakeLists.txt @@ -14,6 +14,7 @@ list(APPEND HEADERS path/Line.h path/LineSegment.h path/Path.h + path/PathPostScriptConverter.h path/PathElement.h points/Point.h points/PointCollection.h @@ -34,6 +35,7 @@ list(APPEND SOURCES path/Line.cpp path/LineSegment.cpp path/Path.cpp + path/PathPostScriptConverter.cpp path/PathElement.cpp points/Point.cpp points/PointCollection.cpp diff --git a/src/base/geometry/path/Line.cpp b/src/base/geometry/path/Line.cpp index d720399..c42e81a 100644 --- a/src/base/geometry/path/Line.cpp +++ b/src/base/geometry/path/Line.cpp @@ -7,6 +7,81 @@ Line::Line(const Point& start, const PointCollection& points) } +Line::Line(const Point& start, InputBufferType bufferType, const std::vector& buffer) + : mStartPoint(start) +{ + if (bufferType == InputBufferType::XY_REL) + { + for (std::size_t idx = 0; idx < buffer.size(); idx += 2) + { + const auto x = buffer[idx]; + const auto y = buffer[idx + 1]; + mPoints.addPoint(Point(mStartPoint.getX() + x, mStartPoint.getY() + y)); + } + } + else if (bufferType == InputBufferType::XY_ABS) + { + for (std::size_t idx = 0; idx < buffer.size(); idx += 2) + { + const auto x = buffer[idx]; + const auto y = buffer[idx + 1]; + mPoints.addPoint(Point(x, y)); + } + } + else if (bufferType == InputBufferType::HORIZONTAL_REL) + { + for (std::size_t idx = 0; idx < buffer.size(); idx ++) + { + const auto x = buffer[idx]; + mPoints.addPoint(Point(mStartPoint.getX() + x, mStartPoint.getY())); + } + } + else if (bufferType == InputBufferType::HORIZONTAL_ABS) + { + for (std::size_t idx = 0; idx < buffer.size(); idx++) + { + const auto x = buffer[idx]; + mPoints.addPoint(Point(x, mStartPoint.getY())); + } + } + else if (bufferType == InputBufferType::VERTICAL_REL) + { + for (std::size_t idx = 0; idx < buffer.size(); idx++) + { + const auto y = buffer[idx]; + mPoints.addPoint(Point(mStartPoint.getX(), mStartPoint.getY() + y)); + } + } + else if (bufferType == InputBufferType::VERTICAL_ABS) + { + for (std::size_t idx = 0; idx < buffer.size(); idx++) + { + const auto y = buffer[idx]; + mPoints.addPoint(Point(mStartPoint.getX(), y)); + } + } +} + +std::string Line::toPostScriptString() const +{ + std::string path = "L "; + for (const auto& point : mPoints.getPoints()) + { + path += std::to_string(point.getX()) + " " + std::to_string(point.getY()) + " "; + } + return path; +} + +Point Line::getFirstPoint() const +{ + return getLocation(); +} + +Point Line::getEndPoint() const +{ + return mPoints.getEndPoint(); +} + const PointCollection& Line::getPoints() const { return mPoints; diff --git a/src/base/geometry/path/Line.h b/src/base/geometry/path/Line.h index 2db4f99..e4e6235 100644 --- a/src/base/geometry/path/Line.h +++ b/src/base/geometry/path/Line.h @@ -4,12 +4,25 @@ #include "PointCollection.h" #include +#include class Line : public PathElement { public: + enum class InputBufferType + { + HORIZONTAL_REL, + HORIZONTAL_ABS, + VERTICAL_REL, + VERTICAL_ABS, + XY_REL, + XY_ABS + }; + Line(const Point& start, const PointCollection& points); + Line(const Point& start, InputBufferType bufferType, const std::vector& buffer); + const PointCollection& getPoints() const; Line::Type getType() const override; @@ -18,8 +31,14 @@ public: Bounds getBounds() const override; + Point getFirstPoint() const override; + + Point getEndPoint() const override; + void sample(SparseGrid* grid) const override {}; + std::string toPostScriptString() const override; + private: Point mStartPoint; PointCollection mPoints; diff --git a/src/base/geometry/path/LineSegment.cpp b/src/base/geometry/path/LineSegment.cpp index cf8225c..9c240dd 100644 --- a/src/base/geometry/path/LineSegment.cpp +++ b/src/base/geometry/path/LineSegment.cpp @@ -32,6 +32,11 @@ void LineSegment::sample(SparseGrid* grid) const } +std::string LineSegment::toPostScriptString() const +{ + return "L " + std::to_string(mP1.getX()) + " " + std::to_string(mP1.getY()); +} + Bounds LineSegment::getBounds() const { const auto minX = std::min(mP0.getX(), mP1.getX()); @@ -47,3 +52,18 @@ const Point& LineSegment::getLocation() const { return mP0; } + +Point LineSegment::getFirstPoint() const +{ + return getPoint0(); +} + +Point LineSegment::getEndPoint() const +{ + return getPoint1(); +} + +LineSegment::Type LineSegment::getType() const +{ + return AbstractGeometricItem::Type::LINE_SEGMENT; +} diff --git a/src/base/geometry/path/LineSegment.h b/src/base/geometry/path/LineSegment.h index 344a67b..98c6861 100644 --- a/src/base/geometry/path/LineSegment.h +++ b/src/base/geometry/path/LineSegment.h @@ -2,6 +2,8 @@ #include "PathElement.h" +#include + class LineSegment : public PathElement { public: @@ -21,6 +23,14 @@ public: const Point& getLocation() const override; + Point getFirstPoint() const override; + + Point getEndPoint() const override; + + std::string toPostScriptString() const override; + + Type getType() const override; + private: Point mP0; Point mP1; diff --git a/src/base/geometry/path/Path.cpp b/src/base/geometry/path/Path.cpp index 90e341b..b8e2510 100644 --- a/src/base/geometry/path/Path.cpp +++ b/src/base/geometry/path/Path.cpp @@ -1,11 +1,55 @@ #include "Path.h" -Path::~Path() +#include "StringUtils.h" +#include "FileLogger.h" + +#include "Line.h" +#include "LineSegment.h" +#include "PathPostScriptConverter.h" + +GeometryPath::~GeometryPath() { } -const std::vector& Path::getElements() const +const std::vector& GeometryPath::getFeatures() const { - return mElements; + return mFeatures; +} + +void GeometryPath::buildFromPostscript(const std::string& psString) +{ + PathPostScriptConverter converter; + converter.fromPostScript(this, psString); +} + +void GeometryPath::addFeature(GeometryPathFeaturePtr feature) +{ + mFeatures.push_back(std::move(feature)); +} + +const Point& GeometryPath::getLocation() const +{ + return mLocation; +} + +Bounds GeometryPath::getBounds() const +{ + return {}; +} + +GeometryPath::Type GeometryPath::getType() const +{ + return GeometryPath::Type::PATH; +} + +void GeometryPath::sample(SparseGrid* grid) const +{ + +} + +std::string GeometryPath::getAsPostScript() const +{ + PathPostScriptConverter converter; + return converter.toPostScript(this); } \ No newline at end of file diff --git a/src/base/geometry/path/Path.h b/src/base/geometry/path/Path.h index 7c1bcbb..67b0de0 100644 --- a/src/base/geometry/path/Path.h +++ b/src/base/geometry/path/Path.h @@ -4,15 +4,60 @@ #include #include +#include using PathElementPtr = std::unique_ptr; -class Path : public AbstractGeometricItem +class GeometryPathFeature { public: - ~Path(); - const std::vector& getElements() const; + void addElement(PathElementPtr element) + { + if (mElements.empty()) + { + mLocation = element->getFirstPoint(); + } + mElements.push_back(std::move(element)); + } + + const Point& getLocation() const + { + return mLocation; + } + + const std::vector& getElements() const + { + return mElements; + } private: + Point mLocation; std::vector mElements; +}; +using GeometryPathFeaturePtr = std::unique_ptr; + +class GeometryPath : public AbstractGeometricItem +{ +public: + ~GeometryPath(); + + void addFeature(GeometryPathFeaturePtr feature); + + void buildFromPostscript(const std::string& psString); + + std::string getAsPostScript() const; + + const Point& getLocation() const override; + + Bounds getBounds() const override; + + Type getType() const override; + + const std::vector& getFeatures() const; + + void sample(SparseGrid* grid) const override; + +private: + Point mLocation; + std::vector mFeatures; }; \ No newline at end of file diff --git a/src/base/geometry/path/PathElement.h b/src/base/geometry/path/PathElement.h index 93a9286..bb9672a 100644 --- a/src/base/geometry/path/PathElement.h +++ b/src/base/geometry/path/PathElement.h @@ -2,8 +2,16 @@ #include "AbstractGeometricItem.h" +#include + class PathElement : public AbstractGeometricItem { public: ~PathElement(); + + virtual Point getFirstPoint() const = 0; + + virtual Point getEndPoint() const = 0; + + virtual std::string toPostScriptString() const = 0; }; \ No newline at end of file diff --git a/src/base/geometry/path/PathPostScriptConverter.cpp b/src/base/geometry/path/PathPostScriptConverter.cpp new file mode 100644 index 0000000..62313f1 --- /dev/null +++ b/src/base/geometry/path/PathPostScriptConverter.cpp @@ -0,0 +1,235 @@ +#include "PathPostScriptConverter.h" + +#include "Path.h" + +#include "StringUtils.h" +#include "FileLogger.h" + +#include "Line.h" +#include "LineSegment.h" + +void PathPostScriptConverter::fromPostScript(GeometryPath* targetPath, const std::string& postScriptPath) +{ + mCurrentPoint = Point(); + + for (auto c : postScriptPath) + { + if (c == 'M' || c == 'm') + { + mLineState = LineState::IN_FIRST_POINT; + mPositionState = getPositionState(c); + + mWorkingFeature = std::make_unique(); + } + else if (c == 'H' || c == 'h') + { + onNewElement(c); + mLineState = LineState::IN_HORIZONTAL; + } + else if (c == 'V' || c == 'v') + { + onNewElement(c); + mLineState = LineState::IN_VERTICAL; + } + else if (c == 'L' || c == 'l') + { + onNewElement(c); + mLineState = LineState::IN_LINE; + } + else if (c == 'C' || c == 'c') + { + onNewElement(c); + mLineState = LineState::IN_CUBIC_BEZIER; + } + else if (c == 'Q' || c == 'q') + { + onNewElement(c); + mLineState = LineState::IN_QUADRATIC_BEZIER; + } + else if (c == 'A' || c == 'a') + { + onNewElement(c); + mLineState = LineState::IN_ARC; + } + else if (c == 'Z' || c == 'z') + { + onNewElement(c); + + mLineState = LineState::START; + targetPath->addFeature(std::move(mWorkingFeature)); + mWorkingFeature = nullptr; + } + else if (std::isblank(c)) + { + onNonNumeric(); + } + else if (std::isalpha(c)) + { + onNewElement(c); + mLineState = LineState::IN_UNSUPPORTED; + } + else + { + mBuffer.push_back(c); + } + } +} + +void PathPostScriptConverter::onNewElement(char c) +{ + onNonNumeric(); + + onElementEnd(); + + mPositionState = getPositionState(c); +} + +void PathPostScriptConverter::onNonNumeric() +{ + if (!mBuffer.empty()) + { + mPointBuffer.push_back(std::stod(mBuffer)); + mBuffer.clear(); + } +} + +PathPostScriptConverter::PositionState PathPostScriptConverter::getPositionState(char c) const +{ + return std::isupper(c) ? PositionState::ABSOLUTE : PositionState::RELATIVE; +} + +void PathPostScriptConverter::onElementEnd() +{ + if (mPointBuffer.empty()) + { + return; + } + + PathElementPtr element; + if (mLineState == LineState::IN_HORIZONTAL) + { + element = onHorizontalLineTo(); + } + else if (mLineState == LineState::IN_VERTICAL) + { + element = onVerticalLineTo(); + } + else if (mLineState == LineState::IN_LINE) + { + element = onLineTo(); + } + else if (mLineState == LineState::IN_FIRST_POINT) + { + onMoveTo(); + } + + if (element) + { + mCurrentPoint = element->getEndPoint(); + mWorkingFeature->addElement(std::move(element)); + } + + mPointBuffer.clear(); +} + +void PathPostScriptConverter::onMoveTo() +{ + if (mPointBuffer.size() != 2) + { + return; + } + if (mPositionState == PositionState::RELATIVE) + { + mCurrentPoint = Point(mCurrentPoint.getX() + mPointBuffer[0], mCurrentPoint.getY() + mPointBuffer[1]); + } + else + { + mCurrentPoint = Point(mPointBuffer[0], mPointBuffer[1]); + } +} + +PathElementPtr PathPostScriptConverter::onHorizontalLineTo() +{ + PathElementPtr element; + if (mPointBuffer.size() == 1) + { + if (mPositionState == PositionState::RELATIVE) + { + element = std::make_unique(mCurrentPoint, Point(mCurrentPoint.getX() + mPointBuffer[0], mCurrentPoint.getY())); + } + else + { + element = std::make_unique(mCurrentPoint, Point(mPointBuffer[0], mCurrentPoint.getY())); + } + } + else + { + Line::InputBufferType buffer_type = mPositionState == PositionState::RELATIVE ? Line::InputBufferType::HORIZONTAL_REL : Line::InputBufferType::HORIZONTAL_ABS; + element = std::make_unique(mCurrentPoint, buffer_type, mPointBuffer); + } + return element; +} + +PathElementPtr PathPostScriptConverter::onVerticalLineTo() +{ + PathElementPtr element; + if (mPointBuffer.size() == 1) + { + if (mPositionState == PositionState::RELATIVE) + { + element = std::make_unique(mCurrentPoint, Point(mCurrentPoint.getX(), mCurrentPoint.getY() + mPointBuffer[0])); + } + else + { + element = std::make_unique(mCurrentPoint, Point(mCurrentPoint.getX(), mPointBuffer[0])); + } + } + else + { + Line::InputBufferType buffer_type = mPositionState == PositionState::RELATIVE ? Line::InputBufferType::VERTICAL_REL : Line::InputBufferType::VERTICAL_ABS; + element = std::make_unique(mCurrentPoint, buffer_type, mPointBuffer); + } + return element; +} + +PathElementPtr PathPostScriptConverter::onLineTo() +{ + PathElementPtr element; + if (mPointBuffer.size() == 2) + { + if (mPositionState == PositionState::RELATIVE) + { + const auto next_point = Point(mCurrentPoint.getX() + mPointBuffer[0], mCurrentPoint.getY() + mPointBuffer[1]); + element = std::make_unique(mCurrentPoint, next_point); + } + else + { + const auto next_point = Point(mPointBuffer[0], mPointBuffer[1]); + element = std::make_unique(mCurrentPoint, next_point); + } + } + else + { + Line::InputBufferType buffer_type = mPositionState == PositionState::RELATIVE ? Line::InputBufferType::XY_REL : Line::InputBufferType::XY_ABS; + element = std::make_unique(mCurrentPoint, buffer_type, mPointBuffer); + } + return element; +} + +std::string PathPostScriptConverter::toPostScript(const GeometryPath* targetPath) +{ + std::string path; + for (const auto& feature : targetPath->getFeatures()) + { + auto start_loc = feature->getLocation(); + path += "M " + std::to_string(start_loc.getX()) + " " + std::to_string(start_loc.getY()); + + for (const auto& path_element : feature->getElements()) + { + path += " " + path_element->toPostScriptString(); + } + path += "Z "; + } + return path; +} + diff --git a/src/base/geometry/path/PathPostScriptConverter.h b/src/base/geometry/path/PathPostScriptConverter.h new file mode 100644 index 0000000..1be935d --- /dev/null +++ b/src/base/geometry/path/PathPostScriptConverter.h @@ -0,0 +1,61 @@ +#pragma once + +#include "PathElement.h" +#include "Point.h" + +#include +#include +#include + +class GeometryPath; +using PathElementPtr = std::unique_ptr; + +class GeometryPathFeature; +using GeometryPathFeaturePtr = std::unique_ptr; + +class PathPostScriptConverter +{ +public: + void fromPostScript(GeometryPath* targetPath, const std::string& postScriptPath); + + std::string toPostScript(const GeometryPath* targetPath); + +private: + enum class LineState + { + START, + IN_FIRST_POINT, + IN_HORIZONTAL, + IN_VERTICAL, + IN_LINE, + IN_CUBIC_BEZIER, + IN_QUADRATIC_BEZIER, + IN_ARC, + IN_UNSUPPORTED + }; + + enum class PositionState + { + ABSOLUTE, + RELATIVE + }; + + PositionState getPositionState(char c) const; + + void onNewElement(char c); + void onNonNumeric(); + void onElementEnd(); + + void onMoveTo(); + PathElementPtr onHorizontalLineTo(); + PathElementPtr onVerticalLineTo(); + PathElementPtr onLineTo(); + + LineState mLineState{ LineState::START }; + PositionState mPositionState{ PositionState::ABSOLUTE }; + std::string mBuffer; + std::vector mPointBuffer; + + GeometryPathFeaturePtr mWorkingFeature; + Point mCurrentPoint; +}; diff --git a/src/base/geometry/points/PointCollection.cpp b/src/base/geometry/points/PointCollection.cpp index 2acdd79..3c87cfa 100644 --- a/src/base/geometry/points/PointCollection.cpp +++ b/src/base/geometry/points/PointCollection.cpp @@ -14,6 +14,11 @@ void PointCollection::apply(const Transform& transform) } } +void PointCollection::addPoint(const Point& point) +{ + mPoints.push_back(point); +} + Bounds PointCollection::getBounds() const { Bounds bounds{0.0, 0.0, 0.0, 0.0}; @@ -29,4 +34,21 @@ Bounds PointCollection::getBounds() const bounds.includePoint(point.getX(), point.getY(), point.getZ()); } return bounds; +} + +Point PointCollection::getEndPoint() const +{ + if (mPoints.empty()) + { + return {}; + } + else + { + return mPoints[mPoints.size() - 1]; + } +} + +const std::vector& PointCollection::getPoints() const +{ + return mPoints; } \ No newline at end of file diff --git a/src/base/geometry/points/PointCollection.h b/src/base/geometry/points/PointCollection.h index 0af4b20..18bb11d 100644 --- a/src/base/geometry/points/PointCollection.h +++ b/src/base/geometry/points/PointCollection.h @@ -9,10 +9,16 @@ class PointCollection public: PointCollection(const std::vector points = {}); + void addPoint(const Point& point); + void apply(const Transform& transform); Bounds getBounds() const; + Point getEndPoint() const; + + const std::vector& getPoints() const; + private: std::vector mPoints; }; diff --git a/src/publishing/CMakeLists.txt b/src/publishing/CMakeLists.txt index c79e65f..7e941a5 100644 --- a/src/publishing/CMakeLists.txt +++ b/src/publishing/CMakeLists.txt @@ -15,7 +15,6 @@ list(APPEND publishing_HEADERS latex/LatexDocument.h latex/LatexMathExpression.h latex/LatexSymbols.h - svg/SvgPainter.h DocumentConverter.h ) @@ -37,7 +36,6 @@ list(APPEND publishing_LIB_INCLUDES plotting/PlotNode.cpp plotting/EquationNode.cpp DocumentConverter.cpp - svg/SvgPainter.cpp ) add_library(publishing SHARED ${publishing_LIB_INCLUDES} ${publishing_INCLUDES} ${publishing_HEADERS}) @@ -47,7 +45,6 @@ target_include_directories(publishing PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/pdf ${CMAKE_CURRENT_SOURCE_DIR}/plotting ${CMAKE_CURRENT_SOURCE_DIR}/latex - ${CMAKE_CURRENT_SOURCE_DIR}/svg ) set_target_properties( publishing PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON ) target_link_libraries( publishing PUBLIC core web graphics visual_elements) diff --git a/src/rendering/graphics/directx/DirectX2dPainter.cpp b/src/rendering/graphics/directx/DirectX2dPainter.cpp index 9aff9f2..867ee41 100644 --- a/src/rendering/graphics/directx/DirectX2dPainter.cpp +++ b/src/rendering/graphics/directx/DirectX2dPainter.cpp @@ -1,9 +1,17 @@ #include "DirectX2dPainter.h" #include "DirectX2dInterface.h" + #include "AbstractGeometricItem.h" #include "Rectangle.h" #include "Circle.h" +#include "Path.h" + +#include "LineSegment.h" +#include "Line.h" +#include "Path.h" + +#include "FileLogger.h" #include "SceneModel.h" @@ -34,68 +42,148 @@ void DirectX2dPainter::paint(SceneModel* model) if (model->getGeometry()->getType() == AbstractGeometricItem::Type::RECTANGLE) { - auto rect = dynamic_cast(model->getGeometry()); - - const auto loc = model->getTransform().getLocation(); - const auto scale_x = model->getTransform().getScaleX(); - const auto scale_y = model->getTransform().getScaleY(); - - const auto min_x = static_cast(loc.getX()); - const auto max_x = static_cast(loc.getX() + rect->getWidth()* scale_x); - - const auto min_y = static_cast(loc.getY()); - const auto max_y = static_cast(loc.getY() + rect->getHeight() * scale_y); - - D2D1_RECT_F d2d_rect{ min_x, max_y, max_x, min_y }; - if (rect->getRadius() == 0.0) - { - if (model->hasFillColor()) - { - mSolidBrush->SetColor(toD2dColor(model->getFillColor())); - rt->FillRectangle(d2d_rect, mSolidBrush.Get()); - } - if (model->hasOutlineColor()) - { - mSolidBrush->SetColor(toD2dColor(model->getOutlineColor())); - rt->DrawRectangle(d2d_rect, mSolidBrush.Get(), 1.0f); - } - } - else - { - D2D1_ROUNDED_RECT rounded_rect{ d2d_rect , static_cast(rect->getRadius()), static_cast(rect->getRadius()) }; - if (model->hasFillColor()) - { - mSolidBrush->SetColor(toD2dColor(model->getFillColor())); - rt->FillRoundedRectangle(rounded_rect, mSolidBrush.Get()); - } - if (model->hasOutlineColor()) - { - mSolidBrush->SetColor(toD2dColor(model->getOutlineColor())); - rt->DrawRoundedRectangle(rounded_rect, mSolidBrush.Get(), 1.0f); - } - } + paintRect(model); } else if (model->getGeometry()->getType() == AbstractGeometricItem::Type::CIRCLE) { - const auto loc = model->getTransform().getLocation(); - const auto scale_x = model->getTransform().getScaleX(); - const auto scale_y = model->getTransform().getScaleY(); + paintCircle(model); + } + else if (model->getGeometry()->getType() == AbstractGeometricItem::Type::PATH) + { + paintPath(model); + } +} - auto circle = dynamic_cast(model->getGeometry()); - const auto radius = circle->getRadius() * scale_x; - const auto radiusy = circle->getMinorRadius() * scale_y; +void DirectX2dPainter::paintRect(SceneModel* model) +{ + auto rt = mD2dInterface->getRenderTarget(); - D2D1_POINT_2F d2d_centre{ static_cast(loc.getX()), static_cast(loc.getY()) }; - D2D1_ELLIPSE ellipse{ d2d_centre, static_cast(radius), static_cast(radiusy) }; + auto rect = dynamic_cast(model->getGeometry()); + const auto loc = model->getTransform().getLocation(); + const auto scale_x = model->getTransform().getScaleX(); + const auto scale_y = model->getTransform().getScaleY(); + + const auto min_x = static_cast(loc.getX()); + const auto max_x = static_cast(loc.getX() + rect->getWidth() * scale_x); + + const auto min_y = static_cast(loc.getY()); + const auto max_y = static_cast(loc.getY() + rect->getHeight() * scale_y); + + D2D1_RECT_F d2d_rect{ min_x, max_y, max_x, min_y }; + if (rect->getRadius() == 0.0) + { + if (model->hasFillColor()) + { + mSolidBrush->SetColor(toD2dColor(model->getFillColor())); + rt->FillRectangle(d2d_rect, mSolidBrush.Get()); + } if (model->hasOutlineColor()) { mSolidBrush->SetColor(toD2dColor(model->getOutlineColor())); - rt->DrawEllipse(ellipse, mSolidBrush.Get(), 1.0f); + rt->DrawRectangle(d2d_rect, mSolidBrush.Get(), 1.0f); + } + } + else + { + D2D1_ROUNDED_RECT rounded_rect{ d2d_rect , static_cast(rect->getRadius()), static_cast(rect->getRadius()) }; + if (model->hasFillColor()) + { + mSolidBrush->SetColor(toD2dColor(model->getFillColor())); + rt->FillRoundedRectangle(rounded_rect, mSolidBrush.Get()); + } + if (model->hasOutlineColor()) + { + mSolidBrush->SetColor(toD2dColor(model->getOutlineColor())); + rt->DrawRoundedRectangle(rounded_rect, mSolidBrush.Get(), 1.0f); } } } +void DirectX2dPainter::paintCircle(SceneModel* model) +{ + auto rt = mD2dInterface->getRenderTarget(); + + const auto loc = model->getTransform().getLocation(); + const auto scale_x = model->getTransform().getScaleX(); + const auto scale_y = model->getTransform().getScaleY(); + + auto circle = dynamic_cast(model->getGeometry()); + const auto radius = circle->getRadius() * scale_x; + const auto radiusy = circle->getMinorRadius() * scale_y; + + D2D1_POINT_2F d2d_centre{ static_cast(loc.getX()), static_cast(loc.getY()) }; + D2D1_ELLIPSE ellipse{ d2d_centre, static_cast(radius), static_cast(radiusy) }; + + if (model->hasFillColor()) + { + mSolidBrush->SetColor(toD2dColor(model->getFillColor())); + rt->FillEllipse(ellipse, mSolidBrush.Get()); + } + if (model->hasOutlineColor()) + { + mSolidBrush->SetColor(toD2dColor(model->getOutlineColor())); + rt->DrawEllipse(ellipse, mSolidBrush.Get(), 1.0f); + } +} + +D2D_POINT_2F DirectX2dPainter::toD2dPoint(const Point& point) +{ + return D2D1::Point2F(static_cast(point.getX()), static_cast(point.getY())); +} + +void DirectX2dPainter::paintPath(SceneModel* model) +{ + Microsoft::WRL::ComPtr path_geom; + mD2dInterface->getFactory()->CreatePathGeometry(&path_geom); + + Microsoft::WRL::ComPtr path_sink; + + path_geom->Open(&path_sink); + + auto path_item = dynamic_cast(model->getGeometry()); + for (const auto& feature : path_item->getFeatures()) + { + const auto loc = feature->getLocation(); + MLOG_INFO("Starting feature at: " << loc.getX() << " " << loc.getY()); + path_sink->BeginFigure(toD2dPoint(loc), D2D1_FIGURE_BEGIN_FILLED); + + for (const auto& element : feature->getElements()) + { + if (element->getType() == AbstractGeometricItem::Type::LINE) + { + for (const auto& point : dynamic_cast(element.get())->getPoints().getPoints()) + { + MLOG_INFO("Adding line entry at: " << point.getX() << " " << point.getY()); + path_sink->AddLine(toD2dPoint(point)); + } + MLOG_INFO("Finished line"); + } + else if (element->getType() == AbstractGeometricItem::Type::LINE_SEGMENT) + { + const auto loc = element->getEndPoint(); + MLOG_INFO("Adding segment entry at: " << loc.getX() << " " << loc.getY()); + path_sink->AddLine(toD2dPoint(loc)); + } + } + path_sink->EndFigure(D2D1_FIGURE_END_CLOSED); + } + path_sink->Close(); + + auto rt = mD2dInterface->getRenderTarget(); + if (model->hasFillColor()) + { + mSolidBrush->SetColor(toD2dColor(model->getFillColor())); + rt->FillGeometry(path_geom.Get(), mSolidBrush.Get()); + } + if (model->hasOutlineColor()) + { + mSolidBrush->SetColor(toD2dColor(model->getOutlineColor())); + rt->DrawGeometry(path_geom.Get(), mSolidBrush.Get(), 1.0f); + } +} + + void DirectX2dPainter::setD2dInterface(DirectX2dInterface* d2dIterface) { mD2dInterface = d2dIterface; diff --git a/src/rendering/graphics/directx/DirectX2dPainter.h b/src/rendering/graphics/directx/DirectX2dPainter.h index 35c0d80..0d8fe14 100644 --- a/src/rendering/graphics/directx/DirectX2dPainter.h +++ b/src/rendering/graphics/directx/DirectX2dPainter.h @@ -5,6 +5,7 @@ #include class SceneModel; +class Point; class DirectX2dInterface; struct ID2D1SolidColorBrush; @@ -13,6 +14,7 @@ namespace D2D1 { class ColorF; } +struct D2D_POINT_2F; class DirectX2dPainter { @@ -30,6 +32,14 @@ public: private: static D2D1::ColorF toD2dColor(const Color& color); + static D2D_POINT_2F toD2dPoint(const Point& point); + + void paintRect(SceneModel* model); + + void paintCircle(SceneModel* model); + + void paintPath(SceneModel* model); + Microsoft::WRL::ComPtr mSolidBrush; DirectX2dInterface* mD2dInterface{ nullptr }; diff --git a/src/rendering/visual_elements/CMakeLists.txt b/src/rendering/visual_elements/CMakeLists.txt index 5bc122a..ac6a6c5 100644 --- a/src/rendering/visual_elements/CMakeLists.txt +++ b/src/rendering/visual_elements/CMakeLists.txt @@ -21,6 +21,8 @@ list(APPEND visual_elements_LIB_INCLUDES svg/SvgDocument.h svg/SvgWriter.h svg/SvgReader.h + svg/SvgPainter.h + svg/SvgPainter.cpp svg/SvgShapeElement.h svg/SvgElement.h svg/SvgTextElement.h @@ -34,6 +36,8 @@ list(APPEND visual_elements_LIB_INCLUDES svg/elements/SvgShapeElements.cpp nodes/MaterialNode.h nodes/MaterialNode.cpp + nodes/PathNode.h + nodes/PathNode.cpp nodes/ImageNode.h nodes/ImageNode.cpp nodes/MeshNode.h diff --git a/src/rendering/visual_elements/nodes/PathNode.cpp b/src/rendering/visual_elements/nodes/PathNode.cpp new file mode 100644 index 0000000..3936b74 --- /dev/null +++ b/src/rendering/visual_elements/nodes/PathNode.cpp @@ -0,0 +1,59 @@ +#include "PathNode.h" + +#include "Path.h" +#include "SceneInfo.h" + +PathNode::PathNode(const Point& loc, const std::string& psPath) + : GeometryNode(loc), + mPathString(psPath) +{ + +} + +std::unique_ptr PathNode::Create(const Point& loc, const std::string& psPath) +{ + return std::make_unique(loc, psPath); +} + +GeometryNode::Type PathNode::getType() +{ + return GeometryNode::Type::Path; +} + +const std::string& PathNode::getPathString() const +{ + return mPathString; +} + +void PathNode::setPathString(const std::string& psPath) +{ + mPathString = psPath; +} + +void PathNode::createOrUpdateGeometry(SceneInfo* sceneInfo) +{ + if (!mBackgroundItem) + { + if (sceneInfo->mSupportsGeometryPrimitives) + { + auto path = std::make_unique(); + path->buildFromPostscript(mPathString); + mBackgroundItem = std::make_unique(std::move(path)); + mBackgroundItem->setName(mName + "_Model"); + } + } + else + { + if (sceneInfo->mSupportsGeometryPrimitives) + { + auto path = std::make_unique(); + path->buildFromPostscript(mPathString); + mBackgroundItem->updateGeometry(std::move(path)); + } + } +} + +void PathNode::updateTransform() +{ + mBackgroundItem->updateTransform({ mLocation }); +} \ No newline at end of file diff --git a/src/rendering/visual_elements/nodes/PathNode.h b/src/rendering/visual_elements/nodes/PathNode.h new file mode 100644 index 0000000..ae016ef --- /dev/null +++ b/src/rendering/visual_elements/nodes/PathNode.h @@ -0,0 +1,24 @@ +#pragma once + +#include "GeometryNode.h" + +#include + +class PathNode : public GeometryNode +{ +public: + PathNode(const Point& loc, const std::string& psPath); + static std::unique_ptr Create(const Point& loc, const std::string& psPath); + + GeometryNode::Type getType() override; + + const std::string& getPathString() const; + + void setPathString(const std::string& psPath); + +private: + void createOrUpdateGeometry(SceneInfo* sceneInfo) override; + void updateTransform() override; + + std::string mPathString; +}; \ No newline at end of file diff --git a/src/rendering/visual_elements/svg/SvgNode.cpp b/src/rendering/visual_elements/svg/SvgNode.cpp index 91c498a..754ba0c 100644 --- a/src/rendering/visual_elements/svg/SvgNode.cpp +++ b/src/rendering/visual_elements/svg/SvgNode.cpp @@ -1,7 +1,9 @@ #include "SvgNode.h" #include "CircleNode.h" +#include "PathNode.h" +#include "SvgElement.h" #include "SvgShapeElements.h" SvgNode::SvgNode(const Point& location) @@ -39,26 +41,11 @@ void SvgNode::createOrUpdateGeometry(SceneInfo* sceneInfo) if (svg_element->getTagName() == "circle") { - auto svg_circle = dynamic_cast(svg_element.get()); - auto loc = svg_circle->getLocation(); - auto radius = svg_circle->getRadius(); - auto minor_radius = radius; - - if (svg_circle->getType() == SvgCircle::Type::ELLIPSE) - { - minor_radius = svg_circle->getMinorRadius(); - } - - if (svg_element->hasAttribute("transform")) - { - const auto transform = svg_circle->getTransform(); - loc.move(transform.getLocation().getX(), transform.getLocation().getY()); - radius *= transform.getScaleX(); - minor_radius *= transform.getScaleY(); - } - auto circle_node = std::make_unique(loc, radius); - circle_node->setMinorRadius(minor_radius); - node = std::move(circle_node); + onCircle(svg_element.get(), node); + } + else if (svg_element->getTagName() == "path") + { + onPath(svg_element.get(), node); } if (!node) @@ -71,7 +58,6 @@ void SvgNode::createOrUpdateGeometry(SceneInfo* sceneInfo) addChild(raw_node); } - } void SvgNode::update(SceneInfo* sceneInfo) @@ -87,4 +73,38 @@ void SvgNode::update(SceneInfo* sceneInfo) updateTransform(); mTransformIsDirty = false; } +} + +void SvgNode::onCircle(XmlElement* element, std::unique_ptr& node) +{ + auto svg_circle = dynamic_cast(element); + auto loc = svg_circle->getLocation(); + auto radius = svg_circle->getRadius(); + auto minor_radius = radius; + + if (svg_circle->getType() == SvgCircle::Type::ELLIPSE) + { + minor_radius = svg_circle->getMinorRadius(); + } + + if (element->hasAttribute("transform")) + { + const auto transform = svg_circle->getTransform(); + loc.move(transform.getLocation().getX(), transform.getLocation().getY()); + radius *= transform.getScaleX(); + minor_radius *= transform.getScaleY(); + } + + auto circle_node = std::make_unique(loc, radius); + circle_node->setMinorRadius(minor_radius); + node = std::move(circle_node); +} + +void SvgNode::onPath(XmlElement* element, std::unique_ptr& node) +{ + auto svg_path = dynamic_cast(element); + + Point loc; + auto path_node = std::make_unique(loc, svg_path->getPath()); + node = std::move(path_node); } \ No newline at end of file diff --git a/src/rendering/visual_elements/svg/SvgNode.h b/src/rendering/visual_elements/svg/SvgNode.h index c2c02b5..e936a79 100644 --- a/src/rendering/visual_elements/svg/SvgNode.h +++ b/src/rendering/visual_elements/svg/SvgNode.h @@ -18,6 +18,9 @@ private: void createOrUpdateGeometry(SceneInfo* sceneInfo); void updateTransform(); + void onCircle(XmlElement* element, std::unique_ptr& node); + void onPath(XmlElement* element, std::unique_ptr& node); + bool mContentDirty{ true }; std::vector > mManagedChildren; diff --git a/src/publishing/svg/SvgPainter.cpp b/src/rendering/visual_elements/svg/SvgPainter.cpp similarity index 73% rename from src/publishing/svg/SvgPainter.cpp rename to src/rendering/visual_elements/svg/SvgPainter.cpp index f43a7dc..7a00294 100644 --- a/src/publishing/svg/SvgPainter.cpp +++ b/src/rendering/visual_elements/svg/SvgPainter.cpp @@ -14,6 +14,7 @@ #include "Circle.h" #include "Rectangle.h" +#include "Path.h" #include "SvgShapeElements.h" #include "SvgTextElement.h" @@ -127,35 +128,62 @@ void SvgPainter::paintPrimitive(SvgDocument* document, SceneModel* model) const { if (model->getGeometry()->getType() == AbstractGeometricItem::Type::RECTANGLE) { - auto model_rect = dynamic_cast(model->getGeometry()); - - auto rect = std::make_unique(); - rect->setWidth(model_rect->getWidth()); - rect->setHeight(model_rect->getHeight()); - - if (model_rect->getRadius() > 0.0) - { - rect->setRadius(model_rect->getRadius()); - } - - setStyle(model, rect.get()); - document->getRoot()->addChild(std::move(rect)); + paintRect(document, model); } else if (model->getGeometry()->getType() == AbstractGeometricItem::Type::CIRCLE) { - auto model_circle = dynamic_cast(model->getGeometry()); - - auto is_ellipse = model_circle->getMinorRadius() != model_circle->getRadius(); - auto circle = std::make_unique(model_circle->isEllipse() ? SvgCircle::Type::ELLIPSE : SvgCircle::Type::REGULAR); - circle->setRadius(model_circle->getRadius()); - if (model_circle->isEllipse()) - { - circle->setMinorRadius(model_circle->getMinorRadius()); - } - - setStyle(model, circle.get()); - document->getRoot()->addChild(std::move(circle)); + paintCircle(document, model); } + else if (model->getGeometry()->getType() == AbstractGeometricItem::Type::PATH) + { + paintPath(document, model); + } +} + +void SvgPainter::paintRect(SvgDocument* document, SceneModel* model) const +{ + auto model_rect = dynamic_cast(model->getGeometry()); + + auto rect = std::make_unique(); + rect->setWidth(model_rect->getWidth()); + rect->setHeight(model_rect->getHeight()); + + if (model_rect->getRadius() > 0.0) + { + rect->setRadius(model_rect->getRadius()); + } + + setStyle(model, rect.get()); + document->getRoot()->addChild(std::move(rect)); +} + +void SvgPainter::paintCircle(SvgDocument* document, SceneModel* model) const +{ + auto model_circle = dynamic_cast(model->getGeometry()); + + auto is_ellipse = model_circle->getMinorRadius() != model_circle->getRadius(); + auto circle = std::make_unique(model_circle->isEllipse() ? SvgCircle::Type::ELLIPSE : SvgCircle::Type::REGULAR); + circle->setRadius(model_circle->getRadius()); + if (model_circle->isEllipse()) + { + circle->setMinorRadius(model_circle->getMinorRadius()); + } + + setStyle(model, circle.get()); + document->getRoot()->addChild(std::move(circle)); +} + +void SvgPainter::paintPath(SvgDocument* document, SceneModel* model) const +{ + auto model_path = dynamic_cast(model->getGeometry()); + + auto path_string = model_path->getAsPostScript(); + + auto svg_path = std::make_unique(); + svg_path->setPath(path_string); + + setStyle(model, svg_path.get()); + document->getRoot()->addChild(std::move(svg_path)); } void SvgPainter::paintText(SvgDocument* document, SceneText* text) const @@ -163,7 +191,8 @@ void SvgPainter::paintText(SvgDocument* document, SceneText* text) const auto svg_text = std::make_unique(); svg_text->setContent(text->getTextData().mContent); auto loc = text->getTransform().getLocation(); - loc.move(0.0, text->getTextHeight()); + + loc.move(text->getTextWidth() / 2.0, text->getTextHeight()/2.0); svg_text->setLocation(loc); svg_text->setFontFamily(text->getTextData().mFont.getFaceName()); diff --git a/src/publishing/svg/SvgPainter.h b/src/rendering/visual_elements/svg/SvgPainter.h similarity index 77% rename from src/publishing/svg/SvgPainter.h rename to src/rendering/visual_elements/svg/SvgPainter.h index cbf9cfa..3649dbd 100644 --- a/src/publishing/svg/SvgPainter.h +++ b/src/rendering/visual_elements/svg/SvgPainter.h @@ -1,7 +1,5 @@ #pragma once -#include "AbstractPainter.h" - #include class SvgDocument; @@ -24,6 +22,12 @@ private: void paintPrimitive(SvgDocument* document, SceneModel* model) const; + void paintRect(SvgDocument* document, SceneModel* model) const; + + void paintCircle(SvgDocument* document, SceneModel* model) const; + + void paintPath(SvgDocument* document, SceneModel* model) const; + void paintText(SvgDocument* document, SceneText* model) const; void setStyle(SceneModel* model, SvgShapeElement* element) const; diff --git a/src/rendering/visual_elements/svg/SvgReader.cpp b/src/rendering/visual_elements/svg/SvgReader.cpp index d82938d..6fb6c78 100644 --- a/src/rendering/visual_elements/svg/SvgReader.cpp +++ b/src/rendering/visual_elements/svg/SvgReader.cpp @@ -58,6 +58,10 @@ void SvgReader::onChild(XmlElement* element, XmlElement* svg_parent) const { new_svg = std::make_unique(); } + else if (element->getTagName() == "path") + { + new_svg = std::make_unique(); + } else { return; diff --git a/src/rendering/visual_elements/svg/SvgTextElement.cpp b/src/rendering/visual_elements/svg/SvgTextElement.cpp index dfcbaf2..3d219d9 100644 --- a/src/rendering/visual_elements/svg/SvgTextElement.cpp +++ b/src/rendering/visual_elements/svg/SvgTextElement.cpp @@ -7,7 +7,13 @@ SvgTextElement::SvgTextElement() :SvgElement("text") { + auto baseline = std::make_unique("dominant-baseline"); + baseline->setValue("middle"); + addAttribute(std::move(baseline)); + auto anchor = std::make_unique("text-anchor"); + anchor->setValue("middle"); + addAttribute(std::move(anchor)); } void SvgTextElement::setFillOpacity(float opacity) diff --git a/src/rendering/visual_elements/svg/elements/SvgShapeElements.cpp b/src/rendering/visual_elements/svg/elements/SvgShapeElements.cpp index db90bf5..a2511ea 100644 --- a/src/rendering/visual_elements/svg/elements/SvgShapeElements.cpp +++ b/src/rendering/visual_elements/svg/elements/SvgShapeElements.cpp @@ -181,6 +181,16 @@ SvgPath::SvgPath() } +std::string SvgPath::getPath() const +{ + std::string d; + if (auto attr = getAttribute("d"); attr) + { + d = attr->getValue(); + } + return d; +} + void SvgPath::setPath(const std::string& mPath) { auto path = std::make_unique("d"); diff --git a/src/rendering/visual_elements/svg/elements/SvgShapeElements.h b/src/rendering/visual_elements/svg/elements/SvgShapeElements.h index f529bca..e62fa1f 100644 --- a/src/rendering/visual_elements/svg/elements/SvgShapeElements.h +++ b/src/rendering/visual_elements/svg/elements/SvgShapeElements.h @@ -68,6 +68,8 @@ class SvgPath : public SvgShapeElement public: SvgPath(); + std::string getPath() const; + void setPath(const std::string& mPath); void setFillRule(const std::string& fillRule); }; diff --git a/src/ui/ui_controls/Button.cpp b/src/ui/ui_controls/Button.cpp index 1ac8175..67e212c 100644 --- a/src/ui/ui_controls/Button.cpp +++ b/src/ui/ui_controls/Button.cpp @@ -3,12 +3,15 @@ #include "TextNode.h" #include "GeometryNode.h" #include "TransformNode.h" -#include "ThemeManager.h" -#include "PaintEvent.h" +#include "IconNode.h" #include "FontTokens.h" +#include "ThemeManager.h" +#include "MediaResourceManager.h" +#include "PaintEvent.h" #include "MouseEvent.h" + #include "FileLogger.h" Button::Button(ButtonData::Component component) @@ -48,6 +51,15 @@ void Button::setLabel(const std::string& text) } } +void Button::setSvgIcon(Resource::Icon::Svg icon) +{ + if (icon != mIcon) + { + mIcon = icon; + mContentDirty = true; + } +} + void Button::setState(ButtonData::State state) { if (mStyle.mState != state) @@ -170,6 +182,10 @@ void Button::doPaint(const PaintEvent* event) { updateBackground(event); updateLabel(event); + + updateIcon(event); + + mContentDirty = false; } void Button::updateLabel(const PaintEvent* event) @@ -204,7 +220,6 @@ void Button::updateLabel(const PaintEvent* event) if (mContentDirty) { mTextNode->setContent(mLabel); - mContentDirty = false; } if (mVisibilityDirty) @@ -212,3 +227,40 @@ void Button::updateLabel(const PaintEvent* event) mTextNode->setIsVisible(mVisible); } } + +void Button::updateIcon(const PaintEvent* event) +{ + if (!mIconNode && mIcon != Resource::Icon::Svg::NONE) + { + mIconNode = std::make_unique(mLocation); + mIconNode->setName(mName + "_IconNode"); + mIconNode->setContent(IconNode::IconType::Svg, MediaResourceManager::getSvgIconNode(mIcon)); + mRootNode->addChild(mIconNode.get()); + } + + if (!mIconNode) + { + return; + } + + if (mTransformDirty) + { + mIconNode->setLocation(mLocation); + } + + if (mMaterialDirty) + { + auto icon_fill = event->getThemesManager()->getColor(mIconColor); + icon_fill.setAlpha(mIconOpacity); + } + + if (mContentDirty) + { + mIconNode->setContent(IconNode::IconType::Svg, MediaResourceManager::getSvgIconNode(mIcon)); + } + + if (mVisibilityDirty) + { + mIconNode->setIsVisible(mVisible); + } +} diff --git a/src/ui/ui_controls/Button.h b/src/ui/ui_controls/Button.h index dbead48..5e068df 100644 --- a/src/ui/ui_controls/Button.h +++ b/src/ui/ui_controls/Button.h @@ -4,6 +4,8 @@ #include "Widget.h" #include "Color.h" +#include "MediaResources.h" + #include #include @@ -23,6 +25,8 @@ public: static std::unique_ptr