diff --git a/Submodules/UIKit/CMakeLists.txt b/Submodules/UIKit/CMakeLists.txt index 7b26301..35c1870 100644 --- a/Submodules/UIKit/CMakeLists.txt +++ b/Submodules/UIKit/CMakeLists.txt @@ -7,7 +7,10 @@ add_definitions( add_library(UIKit lib/platforms/SkiaCtx.cpp lib/Application.cpp - lib/NXSize.cpp + lib/Geometry.cpp + lib/NXLayer.cpp + lib/NXColor.cpp + lib/UIView.cpp ) # APPLE diff --git a/Submodules/UIKit/include/Application.h b/Submodules/UIKit/include/Application.h index 1150de6..24dfc76 100644 --- a/Submodules/UIKit/include/Application.h +++ b/Submodules/UIKit/include/Application.h @@ -2,8 +2,9 @@ #include #include "include/gpu/ganesh/GrDirectContext.h" -#import "tools/window/WindowContext.h" -#import "SkiaCtx.h" +#include "tools/window/WindowContext.h" +#include "SkiaCtx.h" +#include "UIView.h" namespace NXKit { @@ -16,6 +17,8 @@ class Application { sk_sp typeface; float fRotationAngle = 0; + std::shared_ptr keyWindow; + std::unique_ptr skiaCtx; static Application* shared; diff --git a/Submodules/UIKit/include/Geometry.h b/Submodules/UIKit/include/Geometry.h new file mode 100644 index 0000000..0a83f28 --- /dev/null +++ b/Submodules/UIKit/include/Geometry.h @@ -0,0 +1,85 @@ +#pragma once + +namespace NXKit { + +typedef double NXFloat; + +struct NXPoint { + NXFloat x, y; + + NXPoint(); + NXPoint(NXFloat x, NXFloat y); + + bool operator==(const NXPoint& rhs) const; + NXPoint operator+(const NXPoint& first) const; + NXPoint operator-(const NXPoint& first) const; + NXPoint& operator+=(const NXPoint& rhs); + NXPoint& operator-=(const NXPoint& rhs); + NXPoint operator/(const NXFloat& rhs); + NXPoint operator*(const NXFloat& rhs); + +// Point applying(const NXAffineTransform& t) const; + + bool valid(); + NXFloat magnitude(); +}; + +struct NXSize { + NXFloat width, height; + + NXSize(); + NXSize(NXFloat width, NXFloat height); + + bool operator==(const NXSize &rhs) const; + + NXSize operator+(const NXSize &first) const; + NXSize operator-(const NXSize &first) const; + NXSize &operator+=(const NXSize &rhs); + NXSize &operator-=(const NXSize &rhs); + + NXSize operator*(const NXFloat &first) const; + NXSize operator/(const NXFloat &first) const; + NXSize &operator*=(const NXFloat &rhs); + NXSize &operator/=(const NXFloat &rhs); + + bool valid(); +// NXSize inset(UIEdgeInsets inset) const; +}; + +struct NXRect { + NXPoint origin; + NXSize size; + + NXRect(); + NXRect(NXPoint origin, NXSize size); + NXRect(NXFloat x, NXFloat y, NXFloat width, NXFloat height); + + NXFloat width() const; + NXFloat height() const; + + NXFloat minX() const; + NXFloat midX() const; + NXFloat maxX() const; + + NXFloat minY() const; + NXFloat midY() const; + NXFloat maxY() const; + + bool contains(NXPoint point); + bool intersects(const NXRect& other) const; + NXRect intersection(const NXRect& other) const; + NXRect& offsetBy(const NXPoint& offset); + NXRect& offsetBy(const NXFloat& offsetX, const NXFloat& offsetY); +// NXRect& insetBy(const UIEdgeInsets& insets); +// NXRect applying(const NXAffineTransform& t) const; +// NXRect applying(const NXTransform3D& t) const; + + bool operator==(const NXRect& rhs); + + bool valid(); + + static NXRect zero; + static NXRect null; +}; + +} \ No newline at end of file diff --git a/Submodules/UIKit/include/NXColor.h b/Submodules/UIKit/include/NXColor.h new file mode 100644 index 0000000..0cf2af6 --- /dev/null +++ b/Submodules/UIKit/include/NXColor.h @@ -0,0 +1,21 @@ +#pragma once + +namespace NXKit { + +class NXColor { +public: + NXColor(); + NXColor(unsigned char red, unsigned char green, unsigned char blue); + NXColor(unsigned char red, unsigned char green, unsigned char blue, unsigned char alpha); + + unsigned char r(); + unsigned char g(); + unsigned char b(); + unsigned char a(); + +private: + friend class NXLayer; + int color; +}; + +} \ No newline at end of file diff --git a/Submodules/UIKit/include/NXLayer.h b/Submodules/UIKit/include/NXLayer.h new file mode 100644 index 0000000..91e645c --- /dev/null +++ b/Submodules/UIKit/include/NXLayer.h @@ -0,0 +1,70 @@ +#pragma once + +#include "include/core/SkCanvas.h" + +#include +#include + +#include +#include +#include + +namespace NXKit { + +class NXLayer: public enable_shared_from_this { +public: + NXLayer(); + ~NXLayer() {} + + // Getter Setters + void setAnchorPoint(NXPoint anchorPoint); + [[nodiscard]] NXPoint anchorPoint() const { return _anchorPoint; } + + void setBounds(NXRect bounds); + [[nodiscard]] NXRect bounds() const { return _bounds; } + + void setPosition(NXPoint position); + [[nodiscard]] NXPoint position() const { return _position; } + + void setBackgroundColor(std::optional backgroundColor); + [[nodiscard]] std::optional backgroundColor() const { return _backgroundColor; } + + void setCornerRadius(NXFloat cornerRadius); + [[nodiscard]] NXFloat cornerRadius() const { return _cornerRadius; } + + + // Layers + [[nodiscard]] std::vector> sublayers() { return _sublayers; } + + void addSublayer(const std::shared_ptr& layer); + void insertSublayerAt(const std::shared_ptr& layer, int index); + void insertSublayerAbove(const std::shared_ptr& layer, const std::shared_ptr& sibling); + void insertSublayerBelow(const std::shared_ptr& layer, const std::shared_ptr& sibling); + + void removeFromSuperlayer(); + + void skiaRender(SkCanvas* canvas); +private: + + std::weak_ptr superlayer; + std::vector> _sublayers; + + // Animatable + NXPoint _anchorPoint = NXPoint(0.5f, 0.5f); + NXPoint _position; + NXRect _bounds; + NXFloat _cornerRadius = 0; + std::optional _backgroundColor; + + /** + Indicates whether a layer somewhere has changed since the last render pass. + + The current implementation of this is quite simple and doesn't check whether the layer is actually in + the layer hierarchy or not. In theory this means that we're wasting render passes if users frequently + update layers that aren't in the tree. In practice it's not expected that UIKit users would do that + often enough for us to care about it. + **/ + static bool layerTreeIsDirty; +}; + +} diff --git a/Submodules/UIKit/include/NXSize.h b/Submodules/UIKit/include/NXSize.h index ab0a1b7..e69de29 100644 --- a/Submodules/UIKit/include/NXSize.h +++ b/Submodules/UIKit/include/NXSize.h @@ -1,34 +0,0 @@ -#pragma once - -namespace NXKit { - -typedef double NXFloat; - -struct NXSize { - NXFloat width; - NXFloat height; - - NXSize(); - - NXSize(NXFloat width, NXFloat height); - - bool operator==(const NXSize &rhs) const; - - NXSize operator+(const NXSize &first) const; - - NXSize operator-(const NXSize &first) const; - - NXSize &operator+=(const NXSize &rhs); - - NXSize &operator-=(const NXSize &rhs); - - NXSize operator*(const NXFloat &first) const; - - NXSize operator/(const NXFloat &first) const; - - NXSize &operator*=(const NXFloat &rhs); - - NXSize &operator/=(const NXFloat &rhs); -}; - -} \ No newline at end of file diff --git a/Submodules/UIKit/include/SkiaCtx.h b/Submodules/UIKit/include/SkiaCtx.h index 3955cdd..ffba3b6 100644 --- a/Submodules/UIKit/include/SkiaCtx.h +++ b/Submodules/UIKit/include/SkiaCtx.h @@ -7,6 +7,7 @@ #include "include/core/SkSurface.h" #include #include "include/core/SkFontMgr.h" +#include namespace NXKit { diff --git a/Submodules/UIKit/include/UIView.h b/Submodules/UIKit/include/UIView.h new file mode 100644 index 0000000..6ccbc20 --- /dev/null +++ b/Submodules/UIKit/include/UIView.h @@ -0,0 +1,30 @@ +#pragma once + +#include "NXLayer.h" + +namespace NXKit { + +class UIView: public enable_shared_from_this { +public: + UIView(); + + virtual void addSubview(std::shared_ptr view); + void insertSubviewAt(std::shared_ptr view, int index); + void insertSubviewBelow(std::shared_ptr view, std::shared_ptr belowSubview); + void removeFromSuperview(); + +// virtual std::shared_ptr window(); + + const std::vector>& subviews() const { return _subviews; } + std::weak_ptr superview() const { return _superview; } + + std::shared_ptr layer() const { return _layer; }; +private: + std::vector> _subviews; + std::weak_ptr _superview; + std::shared_ptr _layer; + + void setSuperview(std::shared_ptr superview); +}; + +} \ No newline at end of file diff --git a/Submodules/UIKit/include/tools/SharedBase.hpp b/Submodules/UIKit/include/tools/SharedBase.hpp new file mode 100644 index 0000000..fcb5273 --- /dev/null +++ b/Submodules/UIKit/include/tools/SharedBase.hpp @@ -0,0 +1,72 @@ +// +// SharedBase.hpp +// NXKit +// +// Created by Даниил Виноградов on 08.10.2022. +// + +#pragma once + +#include + +namespace NXKit { + +class enable_shared_from_this_pointer_holder { +public: + mutable std::shared_ptr __strong_this_ctor_; +}; + +template +class enable_shared_from_this: public enable_shared_from_this_pointer_holder { +public: +// mutable std::shared_ptr __strong_this_ctor_; + mutable std::weak_ptr __weak_this_; + + enable_shared_from_this() { + __strong_this_ctor_ = std::shared_ptr(static_cast(this)); + __weak_this_ = std::static_pointer_cast(__strong_this_ctor_); + } + + +protected: + std::shared_ptr shared_from_this() { + return std::shared_ptr(__weak_this_); + } + + std::weak_ptr weak_from_this() { + return __weak_this_; + } + + template + std::shared_ptr shared_from_base() { + return std::dynamic_pointer_cast(this->shared_from_this()); + } + + template + std::weak_ptr weak_from_base() { + if (!this->weak_from_this().expired()) { + return std::dynamic_pointer_cast(this->weak_from_this().lock()); + } + return std::weak_ptr(); + } +}; + +template +typename std::enable_if::value, std::shared_ptr<_Tp>>::type new_shared(_Args&& ...__args) { + std::allocator<_Tp> alloc; + using traits_t = std::allocator_traits; + auto obj = alloc.allocate(1); + traits_t::construct(alloc, obj, std::forward<_Args>(__args)...); + + auto ptr = std::static_pointer_cast<_Tp>(obj->__strong_this_ctor_); + obj->__strong_this_ctor_ = nullptr; + return ptr; +} + +template +typename std::enable_if::value, std::shared_ptr<_Tp>>::type new_shared(_Args&& ...__args) { + auto res = std::make_shared<_Tp>(std::forward<_Args>(__args)...); + return res; +} + +} \ No newline at end of file diff --git a/Submodules/UIKit/lib/Application.cpp b/Submodules/UIKit/lib/Application.cpp index 0656e06..b2a5f8d 100644 --- a/Submodules/UIKit/lib/Application.cpp +++ b/Submodules/UIKit/lib/Application.cpp @@ -7,6 +7,8 @@ #include #include +#include + // SKIA METAL GPU #include "include/gpu/ganesh/GrBackendSurface.h" @@ -36,7 +38,19 @@ Application::Application() { SkFontStyle style; typeface = skiaCtx->getFontMgr()->matchFamilyStyle(nullptr, style); -// SkTypeface::Make + + keyWindow = new_shared(); + keyWindow->layer()->setPosition({ 200, 200 }); + keyWindow->layer()->setBounds({0, 0, 150, 150}); + keyWindow->layer()->setBackgroundColor(NXColor(255, 255, 0)); + + auto sublayer = new_shared(); + sublayer->setPosition({ 220, 170 }); + sublayer->setBounds({0, 0, 250, 190}); + sublayer->setBackgroundColor(NXColor(0, 255, 0)); + sublayer->setCornerRadius(10); + + keyWindow->layer()->addSublayer(sublayer); while(platformRunLoop([this]() { return runLoop(); })); } @@ -61,131 +75,133 @@ void Application::render() { auto canvas = surface->getCanvas(); canvas->clear(SK_ColorCYAN); - SkPaint paint; - paint.setColor(SK_ColorRED); - paint.setAntiAlias(true); - - // Draw a rectangle with red paint - SkRect rect = SkRect::MakeXYWH(0, 0, 200, 200); - SkRRect rrect; - SkVector corners[] = {{24, 36}, {36, 24}, {24, 24}, {24, 24}}; - rrect.setRectRadii(rect, corners); - canvas->drawRect(rect, paint); - - paint.setColor(SK_ColorYELLOW); - rect = SkRect::MakeXYWH(10, 410, 4000, 200); - canvas->drawRect(rect, paint); - - paint.setColor(SK_ColorBLUE); - rect = SkRect::MakeXYWH(410, 10, 212, 4000); - canvas->drawRect(rect, paint); - - SkFont font(typeface); - font.setSubpixel(true); - font.setSize(49); - paint.setColor(SK_ColorBLACK); -// - canvas->save(); - static const char message[] = "Hello World "; - - // Translate and rotate - canvas->translate(300, 300); - fRotationAngle += 0.2f; - if (fRotationAngle > 360) { - fRotationAngle -= 360; - } - canvas->rotate(fRotationAngle); - - // Draw the text - canvas->drawString(message, 200, 20, font, paint); -// canvas->drawSimpleText(message, strlen(message), SkTextEncoding::kUTF8, 0, 0, font, paint); - - canvas->restore(); - - // Set up a linear gradient and draw a circle - { - SkPoint linearPoints[] = { { 0, 0 }, { 300, 300 } }; - SkColor linearColors[] = { SK_ColorGREEN, SK_ColorBLACK }; - paint.setShader(SkGradientShader::MakeLinear(linearPoints, linearColors, nullptr, 2, - SkTileMode::kMirror)); - paint.setAntiAlias(true); - - canvas->drawCircle(200, 200, 64, paint); - - // Detach shader - paint.setShader(nullptr); - } - - // Create middle overlay rectangle for background blur - const SkRect middle = SkRect::MakeXYWH(264, 264, 528, 528); - - canvas->save(); -// // Use middle rectangle as clip mask -// canvas->clipRect(middle, false); - canvas->clipRect(middle, true); - - SkPaint redPaint; - redPaint.setAntiAlias(true); - redPaint.setColor(SK_ColorGREEN); - canvas->drawCircle(190, 71, 40, redPaint); - - // Two blur filters, one that we're currently using and the newer one in current version of Skia. - // Both blur filters select a tile mode for clamping the blur filter at the rectangle's edges. - // However, the result on the CPU does NOT appear to clamp at all, while the result on GPU does! -// sk_sp oldBlurFilter = SkBlurImageFilter::Make(25, 25, nullptr, nullptr, SkBlurImageFilter::kClamp_TileMode); - sk_sp newBlurFilter = SkImageFilters::Blur(20, 20, SkTileMode::kClamp, nullptr); +// SkPaint paint; +// paint.setColor(SK_ColorRED); +// paint.setAntiAlias(true); // -// SkMatrix matrix; -// matrix.setScale(6, 6); +// // Draw a rectangle with red paint +// SkRect rect = SkRect::MakeXYWH(0, 0, 200, 200); +// SkRRect rrect; +// SkVector corners[] = {{24, 36}, {36, 24}, {24, 24}, {24, 24}}; +// rrect.setRectRadii(rect, corners); +// canvas->drawRect(rect, paint); // - SkPaint blurPaint; - blurPaint.setAntiAlias(true); - - blurPaint.setStyle(SkPaint::kFill_Style); - blurPaint.setStrokeWidth(10); - blurPaint.setImageFilter(std::move(newBlurFilter)); +// paint.setColor(SK_ColorYELLOW); +// rect = SkRect::MakeXYWH(10, 410, 4000, 200); +// canvas->drawRect(rect, paint); // - canvas->save(); -// // Make a separate layer using the blur filter, clipped to the middle rectangle's bounds - SkCanvas::SaveLayerRec slr(&middle, &blurPaint, SkCanvas::kInitWithPrevious_SaveLayerFlag); - canvas->saveLayer(slr); - canvas->restore(); - +// paint.setColor(SK_ColorBLUE); +// rect = SkRect::MakeXYWH(410, 10, 212, 4000); +// canvas->drawRect(rect, paint); +// +// SkFont font(typeface); +// font.setSubpixel(true); +// font.setSize(49); +// paint.setColor(SK_ColorBLACK); +//// +// canvas->save(); +// static const char message[] = "Hello World "; +// +// // Translate and rotate +// canvas->translate(300, 300); +// fRotationAngle += 0.2f; +// if (fRotationAngle > 360) { +// fRotationAngle -= 360; +// } +// canvas->rotate(fRotationAngle); +// +// // Draw the text +// canvas->drawString(message, 200, 20, font, paint); +//// canvas->drawSimpleText(message, strlen(message), SkTextEncoding::kUTF8, 0, 0, font, paint); +// +// canvas->restore(); +// +// // Set up a linear gradient and draw a circle +// { +// SkPoint linearPoints[] = { { 0, 0 }, { 300, 300 } }; +// SkColor linearColors[] = { SK_ColorGREEN, SK_ColorBLACK }; +// paint.setShader(SkGradientShader::MakeLinear(linearPoints, linearColors, nullptr, 2, +// SkTileMode::kMirror)); +// paint.setAntiAlias(true); +// +// canvas->drawCircle(200, 200, 64, paint); +// +// // Detach shader +// paint.setShader(nullptr); +// } // -// // Fill the clip middle rectangle with a transparent white - canvas->drawColor(0x40FFFFFF); - canvas->restore(); - canvas->restore(); - -// ----------------------- - // // Create middle overlay rectangle for background blur +// const SkRect middle = SkRect::MakeXYWH(264, 264, 528, 528); +// // canvas->save(); -// // Use middle rectangle as clip mask +//// // Use middle rectangle as clip mask +//// canvas->clipRect(middle, false); // canvas->clipRect(middle, true); // +// SkPaint redPaint; +// redPaint.setAntiAlias(true); +// redPaint.setColor(SK_ColorGREEN); +// canvas->drawCircle(190, 71, 40, redPaint); +// // // Two blur filters, one that we're currently using and the newer one in current version of Skia. // // Both blur filters select a tile mode for clamping the blur filter at the rectangle's edges. // // However, the result on the CPU does NOT appear to clamp at all, while the result on GPU does! //// sk_sp oldBlurFilter = SkBlurImageFilter::Make(25, 25, nullptr, nullptr, SkBlurImageFilter::kClamp_TileMode); -// sk_sp newBlurFilter = SkImageFilters::Blur(25, 25, SkTileMode::kClamp, nullptr); +// sk_sp newBlurFilter = SkImageFilters::Blur(20, 20, SkTileMode::kClamp, nullptr); +//// +//// SkMatrix matrix; +//// matrix.setScale(6, 6); +//// +// SkPaint blurPaint; +// blurPaint.setAntiAlias(true); // -// SkPaint p; -// p.setAntiAlias(true); -// p.setStyle(SkPaint::kFill_Style); -// p.setStrokeWidth(10); -// p.setImageFilter(std::move(newBlurFilter)); -// -// // Make a separate layer using the blur filter, clipped to the middle rectangle's bounds -// SkCanvas::SaveLayerRec slr(&middle, &p, SkCanvas::kInitWithPrevious_SaveLayerFlag); +// blurPaint.setStyle(SkPaint::kFill_Style); +// blurPaint.setStrokeWidth(10); +// blurPaint.setImageFilter(std::move(newBlurFilter)); +//// +// canvas->save(); +//// // Make a separate layer using the blur filter, clipped to the middle rectangle's bounds +// SkCanvas::SaveLayerRec slr(&middle, &blurPaint, SkCanvas::kInitWithPrevious_SaveLayerFlag); // canvas->saveLayer(slr); +// canvas->restore(); // -// // Fill the clip middle rectangle with a transparent white +//// +//// // Fill the clip middle rectangle with a transparent white // canvas->drawColor(0x40FFFFFF); // canvas->restore(); // canvas->restore(); +// +//// ----------------------- +// +//// // Create middle overlay rectangle for background blur +//// canvas->save(); +//// // Use middle rectangle as clip mask +//// canvas->clipRect(middle, true); +//// +//// // Two blur filters, one that we're currently using and the newer one in current version of Skia. +//// // Both blur filters select a tile mode for clamping the blur filter at the rectangle's edges. +//// // However, the result on the CPU does NOT appear to clamp at all, while the result on GPU does! +////// sk_sp oldBlurFilter = SkBlurImageFilter::Make(25, 25, nullptr, nullptr, SkBlurImageFilter::kClamp_TileMode); +//// sk_sp newBlurFilter = SkImageFilters::Blur(25, 25, SkTileMode::kClamp, nullptr); +//// +//// SkPaint p; +//// p.setAntiAlias(true); +//// p.setStyle(SkPaint::kFill_Style); +//// p.setStrokeWidth(10); +//// p.setImageFilter(std::move(newBlurFilter)); +//// +//// // Make a separate layer using the blur filter, clipped to the middle rectangle's bounds +//// SkCanvas::SaveLayerRec slr(&middle, &p, SkCanvas::kInitWithPrevious_SaveLayerFlag); +//// canvas->saveLayer(slr); +//// +//// // Fill the clip middle rectangle with a transparent white +//// canvas->drawColor(0x40FFFFFF); +//// canvas->restore(); +//// canvas->restore(); +// +//// ----------------------- -// ----------------------- + keyWindow->layer()->skiaRender(canvas); skiaCtx->flushAndSubmit(surface); skiaCtx->swapBuffers(); diff --git a/Submodules/UIKit/lib/Geometry.cpp b/Submodules/UIKit/lib/Geometry.cpp new file mode 100644 index 0000000..67822a4 --- /dev/null +++ b/Submodules/UIKit/lib/Geometry.cpp @@ -0,0 +1,234 @@ +#include +#include + +using namespace NXKit; + +// MARK: - POINT - +NXPoint::NXPoint(): x(0), y(0) { } +NXPoint::NXPoint(NXFloat x, NXFloat y): x(x), y(y) { } + +bool NXPoint::operator==(const NXPoint& rhs) const { + return this->x == rhs.x && this->y == rhs.y; +} + +NXPoint NXPoint::operator+(const NXPoint& first) const { + return NXPoint(x + first.x, y + first.y); +} + +NXPoint NXPoint::operator-(const NXPoint& first) const { + return NXPoint(x - first.x, y - first.y); +} + +NXPoint& NXPoint::operator+=(const NXPoint& rhs) { + this->x += rhs.x; + this->y += rhs.y; + return *this; +} + +NXPoint& NXPoint::operator-=(const NXPoint& rhs) { + this->x -= rhs.x; + this->y -= rhs.y; + return *this; +} + +NXPoint NXPoint::operator/(const NXFloat& rhs) { + auto res = *this; + res.x /= rhs; + res.y /= rhs; + return res; +} + +NXPoint NXPoint::operator*(const NXFloat& rhs) { + auto res = *this; + res.x *= rhs; + res.y *= rhs; + return res; +} + +//NXPoint NXPoint::applying(const NXAffineTransform& t) const { +// return NXPoint( +// x * t.m11 + y * t.m21 + t.tX, +// x * t.m12 + y * t.m22 + t.tY +// ); +//} + +bool NXPoint::valid() { + return !isnan(this->x) && !isnan(this->y); +} + +NXFloat NXPoint::magnitude() { + return sqrt(x * x + y * y); +} + +// MARK: - NXSize +NXSize::NXSize(): NXSize(0, 0) {} +NXSize::NXSize(NXFloat width, NXFloat height): width(width), height(height) {} + +bool NXSize::operator==(const NXSize& rhs) const { + return this->width == rhs.width && this->height == rhs.height; +} + +NXSize NXSize::operator+(const NXSize& first) const { + return NXSize(width + first.width, height + first.height); +} + +NXSize NXSize::operator-(const NXSize& first) const { + return NXSize(width - first.width, height - first.height); +} + +NXSize& NXSize::operator+=(const NXSize& rhs) { + this->width += rhs.width; + this->height += rhs.height; + return *this; +} + +NXSize& NXSize::operator-=(const NXSize& rhs) { + this->width -= rhs.width; + this->height -= rhs.height; + return *this; +} + +NXSize NXSize::operator*(const NXFloat& first) const { + return NXSize(width * first, height * first); +} + +NXSize NXSize::operator/(const NXFloat& first) const { + return NXSize(width / first, height / first); +} + +NXSize& NXSize::operator*=(const NXFloat& rhs) { + this->width *= rhs; + this->height *= rhs; + return *this; +} + +NXSize& NXSize::operator/=(const NXFloat& rhs) { + this->width /= rhs; + this->height /= rhs; + return *this; +} + +bool NXSize::valid() { + return !isnan(this->width) && !isnan(this->height); +} + +// MARK: - RECT - +NXRect::NXRect(): origin(), size() { } +NXRect::NXRect(NXPoint origin, NXSize size): origin(origin), size(size) { } +NXRect::NXRect(NXFloat x, NXFloat y, NXFloat width, NXFloat height): origin(x, y), size(width, height) { } + +NXFloat NXRect::width() const { return size.width; } +NXFloat NXRect::height() const { return size.height; } + +NXFloat NXRect::minX() const { return origin.x; } +NXFloat NXRect::midX() const { return origin.x + size.width / 2; } +NXFloat NXRect::maxX() const { return origin.x + size.width; } + +NXFloat NXRect::minY() const { return origin.y; } +NXFloat NXRect::midY() const { return origin.y + size.height / 2; } +NXFloat NXRect::maxY() const { return origin.y + size.height; } + +bool NXRect::contains(NXPoint point) { + return + (point.x >= minX()) && (point.x < maxX()) && + (point.y >= minY()) && (point.y < maxY()); +} + +bool NXRect::intersects(const NXRect& other) const { + return !((minX() > other.maxX() || maxX() < other.minX()) || (minY() > other.maxY() || maxY() < other.minY())); +} + +NXRect NXRect::intersection(const NXRect& other) const { + auto largestMinX = std::max(minX(), other.minX()); + auto largestMinY = std::max(minY(), other.minY()); + + auto smallestMaxX = std::min(maxX(), other.maxX()); + auto smallestMaxY = std::min(maxY(), other.maxY()); + + auto width = smallestMaxX - largestMinX; + auto height = smallestMaxY - largestMinY; + + if (width > 0 && height > 0) { + // The intersection rectangle has dimensions, i.e. there is an intersection: + return NXRect(largestMinX, largestMinY, width, height); + } else { + return NXRect::null; + } +} + +NXRect& NXRect::offsetBy(const NXPoint& offset) { + origin.x += offset.x; + origin.y += offset.y; + return *this; +} + +NXRect& NXRect::offsetBy(const NXFloat& offsetX, const NXFloat& offsetY) { + origin.x += offsetX; + origin.y += offsetY; + return *this; +} + +//NXRect& NXRect::insetBy(const UIEdgeInsets& insets) { +// origin.x -= insets.left; +// origin.y -= insets.top; +// size.width += insets.left + insets.right; +// size.height += insets.top + insets.bottom; +// return *this; +//} + +//NXRect NXRect::applying(const NXAffineTransform& t) const { +// if (t.isIdentity()) { return *this; } +// +// auto newTopLeft = NXPoint(minX(), minY()).applying(t); +// auto newTopRight = NXPoint(maxX(), minY()).applying(t); +// auto newBottomLeft = NXPoint(minX(), maxY()).applying(t); +// auto newBottomRight = NXPoint(maxX(), maxY()).applying(t); +// +// +// auto newMinX = min(newTopLeft.x, newTopRight.x, newBottomLeft.x, newBottomRight.x); +// auto newMaxX = max(newTopLeft.x, newTopRight.x, newBottomLeft.x, newBottomRight.x); +// +// auto newMinY = min(newTopLeft.y, newTopRight.y, newBottomLeft.y, newBottomRight.y); +// auto newMaxY = max(newTopLeft.y, newTopRight.y, newBottomLeft.y, newBottomRight.y); +// +// // XXX: What happens if the point that was furthest left is now on the right (because of a rotation)? +// // i.e. Should do we return a normalised rect or one with a negative width? +// return NXRect( +// newMinX, +// newMinY, +// newMaxX - newMinX, +// newMaxY - newMinY); +//} + +//NXRect NXRect::applying(const NXTransform3D& t) const { +// if (t == NXTransform3DIdentity) { return *this; } +// +// auto topLeft = t.transformingVector(minX(), minY(), 0); +// auto topRight = t.transformingVector(maxX(), minY(), 0); +// auto bottomLeft = t.transformingVector(minX(), maxY(), 0); +// auto bottomRight = t.transformingVector(maxX(), maxY(), 0); +// +// auto newMinX = min(topLeft.x, topRight.x, bottomLeft.x, bottomRight.x); +// auto newMaxX = max(topLeft.x, topRight.x, bottomLeft.x, bottomRight.x); +// +// auto newMinY = min(topLeft.y, topRight.y, bottomLeft.y, bottomRight.y); +// auto newMaxY = max(topLeft.y, topRight.y, bottomLeft.y, bottomRight.y); +// +// return NXRect(newMinX, +// newMinY, +// newMaxX - newMinX, +// newMaxY - newMinY); +//} + +bool NXRect::operator==(const NXRect& rhs) { + return + this->origin.x == rhs.origin.x && this->origin.y == rhs.origin.y && + this->size.width == rhs.size.width && this->size.height == rhs.size.height; +} + +bool NXRect::valid() { + return this->origin.valid() && this->size.valid(); +} + +NXRect NXRect::zero = NXRect(); +NXRect NXRect::null = NXRect(std::numeric_limits::infinity(), std::numeric_limits::infinity(), 0, 0); diff --git a/Submodules/UIKit/lib/NXColor.cpp b/Submodules/UIKit/lib/NXColor.cpp new file mode 100644 index 0000000..3267eb1 --- /dev/null +++ b/Submodules/UIKit/lib/NXColor.cpp @@ -0,0 +1,26 @@ +#include "NXColor.h" + +using namespace NXKit; + +NXColor::NXColor(unsigned char r, unsigned char g, unsigned char b, unsigned char a) { + color = (a & 0xff) << 24 | (r & 0xff) << 16 | (g & 0xff) << 8 | (b & 0xff); +} +NXColor::NXColor(unsigned char r, unsigned char g, unsigned char b): NXColor(r, g, b, 255) {} +NXColor::NXColor(): NXColor(0, 0, 0) {} + + +unsigned char NXColor::r() { + return static_cast((color >> 16) & 0xff); +} + +unsigned char NXColor::g() { + return static_cast((color >> 8) & 0xff); +} + +unsigned char NXColor::b() { + return static_cast(color & 0xff); +} + +unsigned char NXColor::a() { + return static_cast((color >> 24) & 0xff); +} \ No newline at end of file diff --git a/Submodules/UIKit/lib/NXLayer.cpp b/Submodules/UIKit/lib/NXLayer.cpp new file mode 100644 index 0000000..e924df1 --- /dev/null +++ b/Submodules/UIKit/lib/NXLayer.cpp @@ -0,0 +1,92 @@ +// +// Created by Даниил Виноградов on 07.12.2024. +// + +#include "NXLayer.h" +#include "include/core/SkRRect.h" + +using namespace NXKit; + +bool NXLayer::layerTreeIsDirty = true; + +NXLayer::NXLayer() = default; + +void NXLayer::setAnchorPoint(NXKit::NXPoint anchorPoint) { + _anchorPoint = anchorPoint; +} + +void NXLayer::setBackgroundColor(std::optional backgroundColor) { + _backgroundColor = backgroundColor; +} + +void NXLayer::setBounds(NXKit::NXRect bounds) { + _bounds = bounds; +} + +void NXLayer::setPosition(NXKit::NXPoint position) { + _position = position; +} + +void NXLayer::setCornerRadius(NXFloat cornerRadius) { + _cornerRadius = cornerRadius; +} + +void NXLayer::addSublayer(const std::shared_ptr& layer) { + layer->removeFromSuperlayer(); + _sublayers.push_back(layer); + layer->superlayer = this->shared_from_this(); + NXLayer::layerTreeIsDirty = true; +} + +void NXLayer::insertSublayerAt(const std::shared_ptr& layer, int index) { + layer->removeFromSuperlayer(); + _sublayers.insert(_sublayers.begin() + index, layer); + layer->superlayer = this->shared_from_this(); + NXLayer::layerTreeIsDirty = true; +} + +void NXLayer::insertSublayerAbove(const std::shared_ptr& layer, const std::shared_ptr& sibling) { + // TODO: Need to implement +} + +void NXLayer::insertSublayerBelow(const std::shared_ptr& layer, const std::shared_ptr& sibling) { + auto itr = std::find(_sublayers.cbegin(), _sublayers.cend(), sibling); + if (itr == _sublayers.cend()) { return; } + + layer->removeFromSuperlayer(); + _sublayers.insert(itr, layer); + layer->superlayer = this->shared_from_this(); + NXLayer::layerTreeIsDirty = true; +} + +void NXLayer::removeFromSuperlayer() { + auto super = superlayer.lock(); + if (super == nullptr) return; + +// // If it's mask - remove +// if (super->mask.get() == this) { +// super->mask = nullptr; +// return; +// } + + // Find and remove this from superlayer + super->_sublayers.erase(std::remove(super->_sublayers.begin(), super->_sublayers.end(), shared_from_this()), super->_sublayers.end()); + NXLayer::layerTreeIsDirty = true; +} + +void NXLayer::skiaRender(SkCanvas* canvas) { + SkPaint paint; + paint.setColor(_backgroundColor->color); + paint.setAntiAlias(true); + + SkRect rect = SkRect::MakeXYWH(_position.x, _position.y, _bounds.width(), _bounds.height()); + SkRRect rrect; + float radii = _cornerRadius; + SkVector corners[] = {{radii, radii}, {radii, radii}, {radii, radii}, {radii, radii}}; + rrect.setRectRadii(rect, corners); + canvas->drawRRect(rrect, paint); + + for (const auto& sublayer: _sublayers) { + sublayer->skiaRender(canvas); + } +} \ No newline at end of file diff --git a/Submodules/UIKit/lib/NXSize.cpp b/Submodules/UIKit/lib/NXSize.cpp deleted file mode 100644 index 09f35b3..0000000 --- a/Submodules/UIKit/lib/NXSize.cpp +++ /dev/null @@ -1,50 +0,0 @@ -#include - -using namespace NXKit; - -NXSize::NXSize(): NXSize(0, 0) {} -NXSize::NXSize(NXFloat width, NXFloat height): width(width), height(height) {} - -bool NXSize::operator==(const NXSize& rhs) const { - return this->width == rhs.width && this->height == rhs.height; -} - -NXSize NXSize::operator+(const NXSize& first) const { - return NXSize(width + first.width, height + first.height); -} - -NXSize NXSize::operator-(const NXSize& first) const { - return NXSize(width - first.width, height - first.height); -} - -NXSize& NXSize::operator+=(const NXSize& rhs) { - this->width += rhs.width; - this->height += rhs.height; - return *this; -} - -NXSize& NXSize::operator-=(const NXSize& rhs) { - this->width -= rhs.width; - this->height -= rhs.height; - return *this; -} - -NXSize NXSize::operator*(const NXFloat& first) const { - return NXSize(width * first, height * first); -} - -NXSize NXSize::operator/(const NXFloat& first) const { - return NXSize(width / first, height / first); -} - -NXSize& NXSize::operator*=(const NXFloat& rhs) { - this->width *= rhs; - this->height *= rhs; - return *this; -} - -NXSize& NXSize::operator/=(const NXFloat& rhs) { - this->width /= rhs; - this->height /= rhs; - return *this; -} \ No newline at end of file diff --git a/Submodules/UIKit/lib/UIView.cpp b/Submodules/UIKit/lib/UIView.cpp new file mode 100644 index 0000000..4b365d6 --- /dev/null +++ b/Submodules/UIKit/lib/UIView.cpp @@ -0,0 +1,77 @@ +#include + +using namespace NXKit; + +UIView::UIView() { + _layer = new_shared(); +} + +void UIView::addSubview(std::shared_ptr view) { + bool needToNotifyViewController = false; +// if (!view->_parentController.expired()) { +// auto window = this->window(); +// if (window) { +// needToNotifyViewController = true; +// } +// } + +// setNeedsLayout(); + view->removeFromSuperview(); + +// if (needToNotifyViewController) +// view->_parentController.lock()->viewWillAppear(true); + + _layer->addSublayer(view->_layer); + _subviews.push_back(view); + view->setSuperview(this->shared_from_this()); +// view->setNeedsUpdateSafeAreaInsets(); +} + +void UIView::setSuperview(std::shared_ptr superview) { + _superview = superview; +} + +void UIView::insertSubviewAt(std::shared_ptr view, int index) { + // TODO: Need to implement +} + +void UIView::insertSubviewBelow(std::shared_ptr view, std::shared_ptr belowSubview) { + auto itr = std::find(subviews().cbegin(), subviews().cend(), belowSubview); + if (itr == subviews().cend()) { return; } + + bool needToNotifyViewController = false; +// if (!view->_parentController.expired()) { +// auto window = this->window(); +// if (window) { +// needToNotifyViewController = true; +// } +// } + +// setNeedsLayout(); + view->removeFromSuperview(); + +// if (needToNotifyViewController) +// view->_parentController.lock()->viewWillAppear(true); + + _layer->insertSublayerBelow(view->_layer, belowSubview->layer()); + _subviews.insert(itr, view); + view->setSuperview(this->shared_from_this()); +// view->setNeedsUpdateSafeAreaInsets(); +} + +void UIView::removeFromSuperview() { + auto superview = this->_superview.lock(); + if (!superview) return; + + _layer->removeFromSuperlayer(); + + // If it's mask - remove +// if (superview->_mask.get() == this) { +// superview->_mask = nullptr; +// } +// else { + superview->_subviews.erase(std::remove(superview->_subviews.begin(), superview->_subviews.end(), shared_from_this()), superview->_subviews.end()); +// } + this->setSuperview(nullptr); +// superview->setNeedsLayout(); +}