mirror of
https://github.com/XITRIX/NXKit.git
synced 2026-05-30 11:46:52 +00:00
WIP: ScrollView
This commit is contained in:
@@ -32,6 +32,7 @@ add_library(UIKit
|
||||
lib/UIColor.cpp
|
||||
lib/UIControl.cpp
|
||||
lib/UIControlGestureRecognizer.cpp
|
||||
lib/UIEdgeInsets.cpp
|
||||
lib/UIEvent.cpp
|
||||
lib/UIFocus.cpp
|
||||
lib/UIFocusAnimationCoordinator.cpp
|
||||
@@ -45,6 +46,7 @@ add_library(UIKit
|
||||
lib/UIPress.cpp
|
||||
lib/UIPressesEvent.cpp
|
||||
lib/UIResponder.cpp
|
||||
lib/UIScrollView.cpp
|
||||
lib/UITapGestureRecognizer.cpp
|
||||
lib/UITouch.cpp
|
||||
lib/UITraitCollection.cpp
|
||||
@@ -57,6 +59,10 @@ add_library(UIKit
|
||||
lib/NXAffineTransform.cpp
|
||||
lib/NXTransform3D.cpp
|
||||
lib/NXData.cpp
|
||||
lib/UIScrollViewExtensions/DecelerationTimingParameters.cpp
|
||||
lib/UIScrollViewExtensions/RubberBand.cpp
|
||||
lib/UIScrollViewExtensions/SpringTimingParameters.cpp
|
||||
lib/UIScrollViewExtensions/TimerAnimation.cpp
|
||||
lib/SkTools/EventTracingPriv.cpp
|
||||
lib/SkTools/SkDebugfTracer.cpp
|
||||
)
|
||||
|
||||
@@ -75,6 +75,9 @@ public:
|
||||
void setMask(const std::shared_ptr<CALayer>& mask);
|
||||
[[nodiscard]] std::shared_ptr<CALayer> mask() const { return _mask; }
|
||||
|
||||
void setContentsTemplateMode(bool newValue) { _isContentsTemplate = newValue; }
|
||||
[[nodiscard]] bool isContentsTemplateMode() { return _isContentsTemplate; }
|
||||
|
||||
void setMasksToBounds(bool newValue) { _masksToBounds = newValue; }
|
||||
[[nodiscard]] bool masksToBounds() { return _masksToBounds; }
|
||||
|
||||
@@ -146,6 +149,7 @@ private:
|
||||
bool _isHidden = false;
|
||||
bool _needsDisplay = true;
|
||||
|
||||
bool _isContentsTemplate = false;
|
||||
std::shared_ptr<CGImage> _contents;
|
||||
bool _masksToBounds = false;
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <optional>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <NXData.h>
|
||||
#include <Geometry.h>
|
||||
|
||||
@@ -14,10 +15,10 @@ public:
|
||||
sk_sp<SkImage> pointee;
|
||||
|
||||
// CGImage(NXSize size);
|
||||
CGImage(const std::shared_ptr<NXData>& sourceData);
|
||||
explicit CGImage(const std::shared_ptr<NXData>& sourceData);
|
||||
// CGImage(SDL_Surface* surface);
|
||||
CGImage(sk_sp<SkImage> image, std::shared_ptr<NXData> sourceData);
|
||||
CGImage(sk_sp<SkImage> image): CGImage(image, nullptr) {}
|
||||
explicit CGImage(sk_sp<SkImage> image, std::shared_ptr<NXData> sourceData);
|
||||
explicit CGImage(sk_sp<SkImage> image): CGImage(std::move(image), nullptr) {}
|
||||
~CGImage();
|
||||
|
||||
[[nodiscard]] NXSize size() const;
|
||||
|
||||
@@ -16,17 +16,19 @@ struct NXPoint {
|
||||
NXPoint(NXFloat x, NXFloat y);
|
||||
|
||||
bool operator==(const NXPoint& rhs) const;
|
||||
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);
|
||||
NXPoint operator/(const NXFloat& rhs) const;
|
||||
NXPoint operator*(const NXFloat& rhs) const;
|
||||
|
||||
NXPoint applying(const NXAffineTransform& t) const;
|
||||
[[nodiscard]] NXPoint applying(const NXAffineTransform& t) const;
|
||||
[[nodiscard]] NXFloat distanceToSegment(NXPoint v, NXPoint w) const;
|
||||
|
||||
bool valid() const;
|
||||
NXFloat magnitude() const;
|
||||
[[nodiscard]] bool valid() const;
|
||||
[[nodiscard]] NXFloat magnitude() const;
|
||||
|
||||
static NXPoint zero;
|
||||
};
|
||||
@@ -50,7 +52,7 @@ struct NXSize {
|
||||
NXSize &operator*=(const NXFloat &rhs);
|
||||
NXSize &operator/=(const NXFloat &rhs);
|
||||
|
||||
bool valid() const;
|
||||
[[nodiscard]] bool valid() const;
|
||||
// NXSize inset(UIEdgeInsets inset) const;
|
||||
};
|
||||
|
||||
@@ -62,16 +64,16 @@ struct NXRect {
|
||||
NXRect(NXPoint origin, NXSize size);
|
||||
NXRect(NXFloat x, NXFloat y, NXFloat width, NXFloat height);
|
||||
|
||||
NXFloat width() const;
|
||||
NXFloat height() const;
|
||||
[[nodiscard]] NXFloat width() const;
|
||||
[[nodiscard]] NXFloat height() const;
|
||||
|
||||
NXFloat minX() const;
|
||||
NXFloat midX() const;
|
||||
NXFloat maxX() const;
|
||||
[[nodiscard]] NXFloat minX() const;
|
||||
[[nodiscard]] NXFloat midX() const;
|
||||
[[nodiscard]] NXFloat maxX() const;
|
||||
|
||||
NXFloat minY() const;
|
||||
NXFloat midY() const;
|
||||
NXFloat maxY() const;
|
||||
[[nodiscard]] NXFloat minY() const;
|
||||
[[nodiscard]] NXFloat midY() const;
|
||||
[[nodiscard]] NXFloat maxY() const;
|
||||
|
||||
void setWidth(NXFloat newValue);
|
||||
void setHeight(NXFloat newValue);
|
||||
@@ -84,8 +86,8 @@ struct NXRect {
|
||||
void setMidY(NXFloat newValue);
|
||||
void setMaxY(NXFloat newValue);
|
||||
|
||||
bool contains(NXPoint point) const;
|
||||
bool intersects(const NXRect& other) const;
|
||||
[[nodiscard]] bool contains(NXPoint point) const;
|
||||
[[nodiscard]] bool intersects(const NXRect& other) const;
|
||||
NXRect& offsetBy(const NXPoint& offset);
|
||||
NXRect& offsetBy(const NXFloat& offsetX, const NXFloat& offsetY);
|
||||
|
||||
@@ -97,10 +99,15 @@ struct NXRect {
|
||||
NXRect applying(NXAffineTransform transform);
|
||||
NXRect applying(NXTransform3D transform);
|
||||
|
||||
NXRect intersection(NXRect other) const;
|
||||
[[nodiscard]] NXRect intersection(NXRect other) const;
|
||||
|
||||
bool isNull() const;
|
||||
[[nodiscard]] bool isNull() const;
|
||||
static NXRect null;
|
||||
};
|
||||
|
||||
struct Geometry {
|
||||
static NXFloat rubberBandClamp(NXFloat x, NXFloat coeff, NXFloat dim);
|
||||
static NXFloat rubberBandClamp(NXFloat x, NXFloat coeff, NXFloat dim, NXFloat limitStart, NXFloat limitEnd);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// UIEdgeInsets.hpp
|
||||
// SDLTest
|
||||
//
|
||||
// Created by Даниил Виноградов on 12.03.2023.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Geometry.h>
|
||||
|
||||
namespace NXKit {
|
||||
|
||||
struct UIEdgeInsets {
|
||||
float top;
|
||||
float left;
|
||||
float bottom;
|
||||
float right;
|
||||
|
||||
UIEdgeInsets(float top, float left, float bottom, float right);
|
||||
UIEdgeInsets(): UIEdgeInsets(0, 0, 0, 0) {}
|
||||
|
||||
bool operator==(const UIEdgeInsets& rhs) const;
|
||||
bool operator!=(const UIEdgeInsets& rhs) const;
|
||||
UIEdgeInsets operator+(const UIEdgeInsets& rhs) const;
|
||||
UIEdgeInsets operator-(const UIEdgeInsets& rhs) const;
|
||||
UIEdgeInsets& operator+=(const UIEdgeInsets& rhs);
|
||||
UIEdgeInsets& operator-=(const UIEdgeInsets& rhs);
|
||||
|
||||
static UIEdgeInsets zero;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -13,11 +13,16 @@ public:
|
||||
static std::shared_ptr<UIImage> fromPath(const std::string& path);
|
||||
static std::shared_ptr<UIImage> fromData(const std::shared_ptr<NXData>& data, NXFloat scale = SkiaCtx::main()->getScaleFactor());
|
||||
|
||||
std::shared_ptr<CGImage> cgImage() { return _cgImage; }
|
||||
[[nodiscard]] std::shared_ptr<CGImage> cgImage() const { return _cgImage; }
|
||||
[[nodiscard]] NXSize size() const { return _size / _scale; }
|
||||
[[nodiscard]] NXFloat scale() const { return _scale; }
|
||||
|
||||
void setRenderModeAsTemplate(bool isTemplate) { _isTemplate = isTemplate; }
|
||||
|
||||
private:
|
||||
friend class UIImageView;
|
||||
|
||||
bool _isTemplate = false;
|
||||
std::shared_ptr<CGImage> _cgImage;
|
||||
NXSize _size;
|
||||
NXFloat _scale;
|
||||
|
||||
@@ -10,9 +10,10 @@
|
||||
#include <UIControl.h>
|
||||
#include <UIImageView.h>
|
||||
#include <UILabel.h>
|
||||
#include <UIViewController.h>
|
||||
#include <UITapGestureRecognizer.h>
|
||||
#include <UIPanGestureRecognizer.h>
|
||||
#include <UIScrollView.h>
|
||||
#include <UITapGestureRecognizer.h>
|
||||
#include <UIViewController.h>
|
||||
#include <tools/Logger.hpp>
|
||||
#include <yoga/Yoga.h>
|
||||
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
//
|
||||
// UIScrollView.hpp
|
||||
// SDLTest
|
||||
//
|
||||
// Created by Даниил Виноградов on 12.03.2023.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <UIView.h>
|
||||
#include <UIPanGestureRecognizer.h>
|
||||
#include <UIScrollViewExtensions/DecelerationTimingParameters.h>
|
||||
#include <UIScrollViewExtensions/TimerAnimation.h>
|
||||
#include <UIScrollViewExtensions/UIScrollViewDecelerationRate.h>
|
||||
|
||||
namespace NXKit {
|
||||
|
||||
class UIScrollView;
|
||||
class UIScrollViewDelegate {
|
||||
public:
|
||||
virtual void scrollViewWillBeginDragging(std::shared_ptr<UIScrollView> scrollView) {}
|
||||
virtual void scrollViewDidScroll(std::shared_ptr<UIScrollView> scrollView) {}
|
||||
virtual void scrollViewDidEndDragging(std::shared_ptr<UIScrollView> scrollView, bool willDecelerate) {}
|
||||
};
|
||||
|
||||
enum class UIScrollViewContentInsetAdjustmentBehavior {
|
||||
scrollableAxes,
|
||||
never,
|
||||
always
|
||||
};
|
||||
|
||||
class UIScrollView: public UIView {
|
||||
public:
|
||||
static std::shared_ptr<UIScrollView> init();
|
||||
|
||||
std::weak_ptr<UIScrollViewDelegate> delegate;
|
||||
|
||||
UIScrollView(NXRect frame = NXRect());
|
||||
|
||||
// virtual void addSubview(std::shared_ptr<UIView> view) override;
|
||||
// bool applyXMLAttribute(std::string name, std::string value) override;
|
||||
void layoutSubviews() override;
|
||||
|
||||
void layoutMarginsDidChange() override;
|
||||
|
||||
NXPoint contentOffset() { return bounds().origin; }
|
||||
void setContentOffset(NXPoint offset, bool animated);
|
||||
|
||||
UIEdgeInsets contentInset() { return _contentInset; }
|
||||
void setContentInset(UIEdgeInsets contentInset) { _contentInset = contentInset; }
|
||||
|
||||
bool bounceHorizontally() const { return _bounceHorizontally; }
|
||||
void setBounceHorizontally(bool bounceHorizontally);
|
||||
|
||||
bool bounceVertically() const { return _bounceVertically; }
|
||||
void setBounceVertically(bool bounceVertically);
|
||||
|
||||
UIScrollViewDecelerationRate decelerationRate() const { return _decelerationRate; }
|
||||
void setDecelerationRate(UIScrollViewDecelerationRate decelerationRate) { _decelerationRate = decelerationRate; }
|
||||
|
||||
UIScrollViewContentInsetAdjustmentBehavior contentInsetAdjustmentBehavior() { return _contentInsetAdjustmentBehavior; }
|
||||
void setContentInsetAdjustmentBehavior(UIScrollViewContentInsetAdjustmentBehavior contentInsetAdjustmentBehavior);
|
||||
|
||||
NXSize contentSize();
|
||||
// void setContentSize(Size size) { _contentSize = size; }
|
||||
|
||||
private:
|
||||
std::shared_ptr<UIPanGestureRecognizer> _panGestureRecognizer;
|
||||
bool _isDecelerating = false;
|
||||
NXPoint weightedAverageVelocity;
|
||||
NXPoint _initialContentOffset;
|
||||
UIScrollViewDecelerationRate _decelerationRate = UIScrollViewDecelerationRate::normal;
|
||||
|
||||
bool _bounceHorizontally = false;
|
||||
bool _bounceVertically = false;
|
||||
|
||||
bool shouldBounceHorizontally();
|
||||
bool shouldBounceVertically();
|
||||
|
||||
UIScrollViewContentInsetAdjustmentBehavior _contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentBehavior::scrollableAxes;
|
||||
|
||||
std::shared_ptr<TimerAnimation> _timerAnimation;
|
||||
UIEdgeInsets _lastLayoutMargins;
|
||||
UIEdgeInsets _contentInset;
|
||||
// Size _contentSize;
|
||||
|
||||
void onPan();
|
||||
void onPanGestureStateChanged();
|
||||
|
||||
NXPoint visibleContentOffset();
|
||||
NXPoint getBoundsCheckedContentOffset(NXPoint newContentOffset);
|
||||
|
||||
NXRect contentOffsetBounds();
|
||||
|
||||
void layoutScrollIndicatorsIfNeeded();
|
||||
void showScrollIndicators();
|
||||
void hideScrollIndicators();
|
||||
|
||||
void startDeceleratingIfNecessary();
|
||||
void cancelDeceleratingIfNeccessary();
|
||||
void cancelDecelerationAnimations();
|
||||
|
||||
void bounceWithVelocity(NXPoint velocity);
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// Created by Даниил Виноградов on 24.01.2025.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Geometry.h>
|
||||
#include <Timer.h>
|
||||
|
||||
namespace NXKit {
|
||||
struct DecelerationTimingParameters {
|
||||
public:
|
||||
NXPoint initialValue;
|
||||
NXPoint initialVelocity;
|
||||
NXFloat decelerationRate;
|
||||
NXFloat threshold;
|
||||
|
||||
DecelerationTimingParameters(NXPoint initialValue, NXPoint initialVelocity, NXFloat decelerationRate, NXFloat threshold):
|
||||
initialValue(initialValue),
|
||||
initialVelocity(initialVelocity),
|
||||
decelerationRate(decelerationRate),
|
||||
threshold(threshold) { }
|
||||
|
||||
[[nodiscard]] NXPoint destination() const;
|
||||
[[nodiscard]] NXFloat duration() const;
|
||||
[[nodiscard]] NXPoint valueAt(float time) const;
|
||||
|
||||
|
||||
[[nodiscard]] NXFloat durationTo(NXPoint value) const;
|
||||
[[nodiscard]] NXPoint velocityAt(NXFloat time) const;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
//
|
||||
// Created by Даниил Виноградов on 12.01.2024.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Geometry.h>
|
||||
|
||||
namespace NXKit {
|
||||
|
||||
struct RubberBand {
|
||||
float coeff;
|
||||
NXSize dims;
|
||||
NXRect bounds;
|
||||
|
||||
RubberBand(float coeff, NXSize dims, NXRect bounds):
|
||||
coeff(coeff), dims(dims), bounds(bounds) {}
|
||||
|
||||
NXPoint clamp(NXPoint point) const;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
//
|
||||
// Created by Даниил Виноградов on 12.01.2024.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Geometry.h>
|
||||
#include <tools/SharedBase.hpp>
|
||||
|
||||
namespace NXKit {
|
||||
struct TimingParameters {
|
||||
[[nodiscard]] virtual NXFloat duration() const { return 0; }
|
||||
[[nodiscard]] virtual NXPoint valueAt(NXFloat time) const { return {}; }
|
||||
};
|
||||
|
||||
struct Spring {
|
||||
NXFloat mass;
|
||||
NXFloat stiffness;
|
||||
NXFloat dampingRatio;
|
||||
|
||||
Spring(NXFloat mass, NXFloat stiffness, NXFloat dampingRatio):
|
||||
mass(mass), stiffness(stiffness), dampingRatio(dampingRatio) {}
|
||||
|
||||
[[nodiscard]] NXFloat damping() const;
|
||||
[[nodiscard]] NXFloat beta() const;
|
||||
[[nodiscard]] NXFloat dampedNaturalFrequency() const;
|
||||
};
|
||||
|
||||
struct SpringTimingParameters: TimingParameters {
|
||||
Spring spring;
|
||||
NXPoint displacement;
|
||||
NXPoint initialVelocity;
|
||||
NXFloat threshold;
|
||||
|
||||
SpringTimingParameters(Spring spring, NXPoint displacement, NXPoint initialVelocity, NXFloat threshold);
|
||||
|
||||
[[nodiscard]] NXFloat duration() const override;
|
||||
[[nodiscard]] NXPoint valueAt(NXFloat time) const override;
|
||||
|
||||
private:
|
||||
std::shared_ptr<TimingParameters> impl;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// Created by Daniil Vinogradov on 12/01/2024.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <CADisplayLink.h>
|
||||
#include <Timer.h>
|
||||
|
||||
namespace NXKit {
|
||||
|
||||
class TimerAnimation: enable_shared_from_this_pointer_holder {
|
||||
public:
|
||||
TimerAnimation(double duration, std::function<void(float, double)> animations, std::function<void(bool)> completion = [](bool _){});
|
||||
~TimerAnimation();
|
||||
|
||||
void invalidate();
|
||||
|
||||
private:
|
||||
CADisplayLink displayLink;
|
||||
bool _running = true;
|
||||
Timer firstFrameTimestamp;
|
||||
double duration;
|
||||
std::function<void(float, double)> animations;
|
||||
std::function<void(bool)> completion;
|
||||
|
||||
void handleFrame();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
//
|
||||
// Created by Daniil Vinogradov on 12/01/2024.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace NXKit {
|
||||
|
||||
class UIScrollViewDecelerationRate {
|
||||
public:
|
||||
enum Value {
|
||||
normal,
|
||||
fast
|
||||
};
|
||||
|
||||
UIScrollViewDecelerationRate() = default;
|
||||
constexpr UIScrollViewDecelerationRate(Value value) : value(value) { }
|
||||
|
||||
// Allow switch and comparisons.
|
||||
constexpr operator Value() const { return value; }
|
||||
|
||||
// Prevent usage: if(fruit)
|
||||
explicit operator bool() const = delete;
|
||||
|
||||
[[nodiscard]] constexpr float rawValue() const {
|
||||
switch (value) {
|
||||
case normal: {
|
||||
return 0.998f;
|
||||
}
|
||||
case fast: {
|
||||
return 0.99f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
Value value;
|
||||
};
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "CALayer.h"
|
||||
#include <YGLayout.h>
|
||||
#include <UIEdgeInsets.h>
|
||||
#include <UITraitEnvironment.h>
|
||||
#include <UIViewContentMode.h>
|
||||
#include <UIResponder.h>
|
||||
@@ -34,7 +35,7 @@ public:
|
||||
void setAlpha(NXFloat alpha) { _layer->setOpacity(alpha); }
|
||||
[[nodiscard]] NXFloat alpha() const { return _layer->opacity(); }
|
||||
|
||||
void setHidden(bool hidden) { _layer->setHidden(hidden); }
|
||||
void setHidden(bool hidden);
|
||||
[[nodiscard]] bool isHidden() const { return _layer->isHidden(); }
|
||||
|
||||
void setClipsToBounds(bool clipsToBounds) { _layer->setMasksToBounds(clipsToBounds); }
|
||||
@@ -43,7 +44,7 @@ public:
|
||||
void setTransform(NXAffineTransform transform) { _layer->setAffineTransform(transform); }
|
||||
[[nodiscard]] NXAffineTransform transform() const { return _layer->affineTransform(); }
|
||||
|
||||
void setBackgroundColor(std::optional<UIColor> backbroundColor) { _layer->setBackgroundColor(backbroundColor); }
|
||||
void setBackgroundColor(const std::optional<UIColor>& backbroundColor) { _layer->setBackgroundColor(backbroundColor); }
|
||||
[[nodiscard]] std::optional<UIColor> backgroundColor() const { return _layer->backgroundColor(); }
|
||||
|
||||
void setTintColor(std::optional<UIColor> tintColor);
|
||||
@@ -62,8 +63,8 @@ public:
|
||||
void addGestureRecognizer(const std::shared_ptr<UIGestureRecognizer>& gestureRecognizer);
|
||||
[[nodiscard]] std::vector<std::shared_ptr<UIGestureRecognizer>>* gestureRecognizers() { return &_gestureRecognizers; }
|
||||
|
||||
void addSubview(std::shared_ptr<UIView> view);
|
||||
void insertSubviewAt(std::shared_ptr<UIView> view, int index);
|
||||
void addSubview(const std::shared_ptr<UIView>& view);
|
||||
void insertSubviewAt(const std::shared_ptr<UIView>& view, int index);
|
||||
void insertSubviewBelow(const std::shared_ptr<UIView>& view, const std::shared_ptr<UIView>& belowSubview);
|
||||
void removeFromSuperview();
|
||||
|
||||
@@ -77,6 +78,20 @@ public:
|
||||
void traitCollectionDidChange(std::shared_ptr<UITraitCollection> previousTraitCollection) override;
|
||||
|
||||
// Layout
|
||||
UIEdgeInsets layoutMargins();
|
||||
void setLayoutMargins(UIEdgeInsets layoutMargins);
|
||||
|
||||
UIEdgeInsets safeAreaInsets() { return _safeAreaInsets; }
|
||||
|
||||
virtual void safeAreaInsetsDidChange() {}
|
||||
virtual void layoutMarginsDidChange() {}
|
||||
|
||||
bool insetsLayoutMarginsFromSafeArea() const { return _insetsLayoutMarginsFromSafeArea; }
|
||||
void setInsetsLayoutMarginsFromSafeArea(bool insetsLayoutMarginsFromSafeArea);
|
||||
|
||||
bool preservesSuperviewLayoutMargins() const { return _preservesSuperviewLayoutMargins; }
|
||||
void setPreservesSuperviewLayoutMargins(bool preservesSuperviewLayoutMargins);
|
||||
|
||||
void setNeedsDisplay() { _needsDisplay = true; }
|
||||
void setNeedsLayout();// { setNeedsDisplay(); _needsLayout = true; }
|
||||
|
||||
@@ -135,7 +150,7 @@ public:
|
||||
void updateCurrentEnvironment() override;
|
||||
|
||||
virtual void draw() {}
|
||||
virtual void display(std::shared_ptr<CALayer> layer) override;
|
||||
void display(std::shared_ptr<CALayer> layer) override;
|
||||
private:
|
||||
friend class UIViewController;
|
||||
friend class UIFocusSystem;
|
||||
@@ -155,8 +170,25 @@ private:
|
||||
|
||||
std::optional<UIColor> _tintColor;
|
||||
|
||||
UIEdgeInsets _layoutMargins;
|
||||
UIEdgeInsets _calculatedLayoutMargins;
|
||||
UIEdgeInsets _safeAreaInsets;
|
||||
bool _insetsLayoutMarginsFromSafeArea = true;
|
||||
bool _preservesSuperviewLayoutMargins = false;
|
||||
|
||||
void setSafeAreaInsets(UIEdgeInsets safeAreaInsets);
|
||||
void updateSafeAreaInsetsInChilds();
|
||||
void updateSafeAreaInsetsIfNeeded();
|
||||
void updateSafeAreaInsets();
|
||||
void updateLayoutMarginIfNeeded();
|
||||
void updateLayoutMargin();
|
||||
void setNeedsUpdateSafeAreaInsets() { _needsUpdateSafeAreaInsets = true; }
|
||||
void setNeedsUpdateLayoutMargins() { _needsUpdateLayoutMargins = true; }
|
||||
|
||||
bool _needsLayout = true;
|
||||
bool _needsDisplay = true;
|
||||
bool _needsUpdateSafeAreaInsets = true;
|
||||
bool _needsUpdateLayoutMargins = true;
|
||||
|
||||
static void animateIfNeeded(Timer currentTime);
|
||||
|
||||
|
||||
@@ -34,10 +34,10 @@ public:
|
||||
virtual void didMoveToParent(std::shared_ptr<UIViewController> parent);
|
||||
void removeFromParent();
|
||||
|
||||
// UIEdgeInsets additionalSafeAreaInsets() { return _additionalSafeAreaInsets; }
|
||||
// void setAdditionalSafeAreaInsets(UIEdgeInsets additionalSafeAreaInsets);
|
||||
UIEdgeInsets additionalSafeAreaInsets() { return _additionalSafeAreaInsets; }
|
||||
void setAdditionalSafeAreaInsets(UIEdgeInsets additionalSafeAreaInsets);
|
||||
|
||||
// UIEdgeInsets systemMinimumLayoutMargins() { return _systemMinimumLayoutMargins; }
|
||||
UIEdgeInsets systemMinimumLayoutMargins() { return _systemMinimumLayoutMargins; }
|
||||
|
||||
bool viewRespectsSystemMinimumLayoutMargins() { return _viewRespectsSystemMinimumLayoutMargins; }
|
||||
void setViewRespectsSystemMinimumLayoutMargins(bool viewRespectsSystemMinimumLayoutMargins);
|
||||
@@ -59,8 +59,8 @@ private:
|
||||
std::shared_ptr<UIView> _view;
|
||||
std::weak_ptr<UIViewController> _parent;
|
||||
std::vector<std::shared_ptr<UIViewController>> _children;
|
||||
// UIEdgeInsets _additionalSafeAreaInsets;
|
||||
// UIEdgeInsets _systemMinimumLayoutMargins = UIEdgeInsets(0, 16, 0, 16);
|
||||
UIEdgeInsets _additionalSafeAreaInsets;
|
||||
UIEdgeInsets _systemMinimumLayoutMargins = UIEdgeInsets(0, 16, 0, 16);
|
||||
bool _viewRespectsSystemMinimumLayoutMargins = true;
|
||||
float _animationTime = 0.5;
|
||||
|
||||
|
||||
@@ -241,12 +241,19 @@ void CALayer::skiaRender(SkCanvas* canvas) {
|
||||
canvas->save();
|
||||
canvas->translate(x, y);
|
||||
|
||||
SkPaint imgPaint;
|
||||
imgPaint.setAntiAlias(true);
|
||||
|
||||
if (_isContentsTemplate) {
|
||||
imgPaint.setColorFilter(SkColorFilters::Blend(UIColor::tint.raw(), SkBlendMode::kSrcIn));
|
||||
}
|
||||
|
||||
canvas->drawImageRect(_contents->pointee, {
|
||||
float(0),
|
||||
float(0),
|
||||
float(width),
|
||||
float(height)
|
||||
}, SkSamplingOptions(), nullptr);
|
||||
}, SkSamplingOptions(), &imgPaint);
|
||||
|
||||
// Contents matrix save 4 // restore
|
||||
canvas->restore();
|
||||
|
||||
@@ -37,6 +37,10 @@ bool NXPoint::operator==(const NXPoint& rhs) const {
|
||||
return this->x == rhs.x && this->y == rhs.y;
|
||||
}
|
||||
|
||||
bool NXPoint::operator!=(const NXPoint& rhs) const {
|
||||
return !(*this == rhs);
|
||||
}
|
||||
|
||||
NXPoint NXPoint::operator+(const NXPoint& first) const {
|
||||
return {x + first.x, y + first.y};
|
||||
}
|
||||
@@ -57,14 +61,14 @@ NXPoint& NXPoint::operator-=(const NXPoint& rhs) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
NXPoint NXPoint::operator/(const NXFloat& rhs) {
|
||||
NXPoint NXPoint::operator/(const NXFloat& rhs) const {
|
||||
auto res = *this;
|
||||
res.x /= rhs;
|
||||
res.y /= rhs;
|
||||
return res;
|
||||
}
|
||||
|
||||
NXPoint NXPoint::operator*(const NXFloat& rhs) {
|
||||
NXPoint NXPoint::operator*(const NXFloat& rhs) const {
|
||||
auto res = *this;
|
||||
res.x *= rhs;
|
||||
res.y *= rhs;
|
||||
@@ -78,6 +82,36 @@ NXPoint NXPoint::applying(const NXAffineTransform& t) const {
|
||||
};
|
||||
}
|
||||
|
||||
float NXPoint::distanceToSegment(NXPoint v, NXPoint w) const {
|
||||
auto pv_dx = x - v.x;
|
||||
auto pv_dy = y - v.y;
|
||||
auto wv_dx = w.x - v.x;
|
||||
auto wv_dy = w.y - v.y;
|
||||
|
||||
auto dot = pv_dx * wv_dx + pv_dy * wv_dy;
|
||||
auto len_sq = wv_dx * wv_dx + wv_dy * wv_dy;
|
||||
auto param = dot / len_sq;
|
||||
|
||||
float int_x, int_y; /* intersection of normal to vw that goes through p */
|
||||
|
||||
if (param < 0 || (v.x == w.x && v.y == w.y)) {
|
||||
int_x = v.x;
|
||||
int_y = v.y;
|
||||
} else if (param > 1) {
|
||||
int_x = w.x;
|
||||
int_y = w.y;
|
||||
} else {
|
||||
int_x = v.x + param * wv_dx;
|
||||
int_y = v.y + param * wv_dy;
|
||||
}
|
||||
|
||||
/* Components of normal */
|
||||
auto dx = x - int_x;
|
||||
auto dy = y - int_y;
|
||||
|
||||
return sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
|
||||
bool NXPoint::valid() const {
|
||||
return !std::isnan(this->x) && !std::isnan(this->y);
|
||||
}
|
||||
@@ -288,3 +322,15 @@ bool NXRect::isNull() const {
|
||||
}
|
||||
|
||||
NXRect NXRect::null = NXRect(INFINITY, INFINITY, 0, 0);
|
||||
|
||||
NXFloat Geometry::rubberBandClamp(NXFloat x, NXFloat coeff, NXFloat dim) {
|
||||
return (1.0f - (1.0f / ((x * coeff / dim) + 1.0f))) * dim;
|
||||
}
|
||||
|
||||
NXFloat Geometry::rubberBandClamp(NXFloat x, NXFloat coeff, NXFloat dim, NXFloat limitStart, NXFloat limitEnd) {
|
||||
auto clampedX = fminf(fmaxf(x, limitStart), limitEnd);
|
||||
auto diff = abs(x - clampedX);
|
||||
float sign = clampedX > x ? -1 : 1;
|
||||
|
||||
return clampedX + sign * rubberBandClamp(diff, coeff, dim);
|
||||
}
|
||||
|
||||
@@ -68,18 +68,22 @@ void UIButton::applyStyle(UIButtonStyle style) {
|
||||
case UIButtonStyle::plain:
|
||||
setBackgroundColor(UIColor::clear);
|
||||
_titleLabel->setTextColor(UIColor::tint);
|
||||
_imageView->setTintColor(UIColor::tint);
|
||||
break;
|
||||
case UIButtonStyle::gray:
|
||||
setBackgroundColor(UIColor::systemGray);
|
||||
_titleLabel->setTextColor(UIColor::tint);
|
||||
_imageView->setTintColor(UIColor::tint);
|
||||
break;
|
||||
case UIButtonStyle::tinted:
|
||||
setBackgroundColor(UIColor::tint.withAlphaComponent(0.2f));
|
||||
_titleLabel->setTextColor(UIColor::tint);
|
||||
_imageView->setTintColor(UIColor::tint);
|
||||
break;
|
||||
case UIButtonStyle::filled:
|
||||
setBackgroundColor(UIColor::tint);
|
||||
_titleLabel->setTextColor(UIColor::white);
|
||||
_imageView->setTintColor(UIColor::white);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
//
|
||||
// UIEdgeInsets.cpp
|
||||
// SDLTest
|
||||
//
|
||||
// Created by Даниил Виноградов on 12.03.2023.
|
||||
//
|
||||
|
||||
#include <UIEdgeInsets.h>
|
||||
|
||||
namespace NXKit {
|
||||
|
||||
UIEdgeInsets::UIEdgeInsets(float top, float left, float bottom, float right):
|
||||
top(top), left(left), bottom(bottom), right(right)
|
||||
{ }
|
||||
|
||||
bool UIEdgeInsets::operator==(const UIEdgeInsets& rhs) const {
|
||||
return top == rhs.top &&
|
||||
left == rhs.left &&
|
||||
bottom == rhs.bottom &&
|
||||
right == rhs.right;
|
||||
}
|
||||
|
||||
bool UIEdgeInsets::operator!=(const UIEdgeInsets& rhs) const {
|
||||
return !(*this == rhs);
|
||||
}
|
||||
|
||||
UIEdgeInsets UIEdgeInsets::operator+(const UIEdgeInsets& rhs) const {
|
||||
return {top + rhs.top, left + rhs.left, bottom + rhs.bottom, right + rhs.right};
|
||||
}
|
||||
|
||||
UIEdgeInsets UIEdgeInsets::operator-(const UIEdgeInsets& rhs) const {
|
||||
return {top - rhs.top, left - rhs.left, bottom - rhs.bottom, right - rhs.right};
|
||||
}
|
||||
|
||||
UIEdgeInsets& UIEdgeInsets::operator+=(const UIEdgeInsets& rhs) {
|
||||
this->top += rhs.top;
|
||||
this->left += rhs.left;
|
||||
this->bottom += rhs.bottom;
|
||||
this->right += rhs.right;
|
||||
return *this;
|
||||
}
|
||||
|
||||
UIEdgeInsets& UIEdgeInsets::operator-=(const UIEdgeInsets& rhs) {
|
||||
this->top -= rhs.top;
|
||||
this->left -= rhs.left;
|
||||
this->bottom -= rhs.bottom;
|
||||
this->right -= rhs.right;
|
||||
return *this;
|
||||
}
|
||||
|
||||
UIEdgeInsets UIEdgeInsets::zero = UIEdgeInsets();
|
||||
|
||||
}
|
||||
@@ -25,6 +25,8 @@ void UIImageView::setImage(std::shared_ptr<UIImage> image) {
|
||||
|
||||
void UIImageView::updateTextureFromImage() {
|
||||
if (_image) {
|
||||
// _image->cgImage()
|
||||
layer()->setContentsTemplateMode(_image->_isTemplate);
|
||||
layer()->setContents(_image->cgImage());
|
||||
layer()->setContentsScale(_image->scale());
|
||||
|
||||
|
||||
@@ -0,0 +1,434 @@
|
||||
//
|
||||
// Created by Даниил Виноградов on 24.01.2025.
|
||||
//
|
||||
//
|
||||
// UIScrollView.cpp
|
||||
// SDLTest
|
||||
//
|
||||
// Created by Даниил Виноградов on 12.03.2023.
|
||||
//
|
||||
|
||||
#include <UIScrollView.h>
|
||||
#include <CATransaction.h>
|
||||
#include <UIScrollViewExtensions/SpringTimingParameters.h>
|
||||
#include <UIScrollViewExtensions/RubberBand.h>
|
||||
|
||||
namespace NXKit {
|
||||
|
||||
std::shared_ptr<UIScrollView> UIScrollView::init() {
|
||||
return new_shared<UIScrollView>();
|
||||
}
|
||||
|
||||
UIScrollView::UIScrollView(NXRect frame): UIView(frame) {
|
||||
_panGestureRecognizer = new_shared<UIPanGestureRecognizer>();
|
||||
_panGestureRecognizer->onStateChanged = [this](auto){ onPanGestureStateChanged(); };
|
||||
addGestureRecognizer(_panGestureRecognizer);
|
||||
setClipsToBounds(true);
|
||||
|
||||
// applyScrollIndicatorsStyle();
|
||||
// [horizontalScrollIndicator, verticalScrollIndicator].forEach {
|
||||
// $0.alpha = 0
|
||||
// addSubview($0)
|
||||
// }
|
||||
}
|
||||
|
||||
//void UIScrollView::addSubview(std::shared_ptr<UIView> view) {
|
||||
// UIView::addSubview(view);
|
||||
// view->yoga->setPositionType(YGPositionTypeAbsolute);
|
||||
//}
|
||||
|
||||
void UIScrollView::setContentOffset(NXPoint offset, bool animated) {
|
||||
if (offset == contentOffset()) return;
|
||||
|
||||
// Cancel deceleration animations only when contentOffset gets set without animations.
|
||||
// Otherwise we might cancel any "bounds" animations which are not iniated from velocity scrolling.
|
||||
if (_isDecelerating && UIView::currentAnimationPrototype == nullptr) {
|
||||
cancelDecelerationAnimations();
|
||||
}
|
||||
|
||||
setBounds(NXRect(offset, bounds().size));
|
||||
layoutScrollIndicatorsIfNeeded();
|
||||
|
||||
// otherwise everything subscribing to scrollViewDidScroll is implicitly animated from velocity scroll
|
||||
CATransaction::begin();
|
||||
CATransaction::setDisableActions(!animated);
|
||||
if (!delegate.expired()) delegate.lock()->scrollViewDidScroll(shared_from_base<UIScrollView>());
|
||||
CATransaction::commit();
|
||||
}
|
||||
|
||||
void UIScrollView::setContentInsetAdjustmentBehavior(UIScrollViewContentInsetAdjustmentBehavior contentInsetAdjustmentBehavior) {
|
||||
if (_contentInsetAdjustmentBehavior == contentInsetAdjustmentBehavior) return;
|
||||
_contentInsetAdjustmentBehavior = contentInsetAdjustmentBehavior;
|
||||
setNeedsLayout();
|
||||
}
|
||||
|
||||
void UIScrollView::setBounceHorizontally(bool bounceHorizontally) {
|
||||
if (_bounceHorizontally == bounceHorizontally) return;
|
||||
_bounceHorizontally = bounceHorizontally;
|
||||
setNeedsLayout();
|
||||
}
|
||||
|
||||
void UIScrollView::setBounceVertically(bool bounceVertically) {
|
||||
if (_bounceVertically == bounceVertically) return;
|
||||
_bounceVertically = bounceVertically;
|
||||
setNeedsLayout();
|
||||
}
|
||||
|
||||
void UIScrollView::layoutMarginsDidChange() {
|
||||
auto delta = _lastLayoutMargins - layoutMargins();
|
||||
_lastLayoutMargins = layoutMargins();
|
||||
auto target = getBoundsCheckedContentOffset(contentOffset() + NXPoint(delta.left, delta.top));
|
||||
setContentOffset(target, false);
|
||||
}
|
||||
|
||||
NXPoint UIScrollView::visibleContentOffset() {
|
||||
return (layer()->presentationOrSelf())->bounds().origin;
|
||||
}
|
||||
|
||||
NXSize UIScrollView::contentSize() {
|
||||
if (subviews().size() == 0) return NXSize();
|
||||
return subviews().front()->bounds().size;
|
||||
}
|
||||
|
||||
NXPoint UIScrollView::getBoundsCheckedContentOffset(NXPoint newContentOffset) {
|
||||
auto contentNXSize = this->contentSize();
|
||||
auto contentHeight = contentNXSize.height;// fmaxf(contentNXSize.height, bounds().height());
|
||||
auto contentWidth = contentNXSize.width;// fmaxf(contentNXSize.width, bounds().width());
|
||||
|
||||
auto allInsects = _contentInset;// + layoutMargins();
|
||||
|
||||
bool contentWidthGreaterThenScrollBounds = contentWidth > bounds().width() -_contentInset.left - _contentInset.right;
|
||||
bool contentHeightGreaterThenScrollBounds = contentHeight > bounds().height() - _contentInset.top - _contentInset.bottom;
|
||||
|
||||
switch (_contentInsetAdjustmentBehavior) {
|
||||
case UIScrollViewContentInsetAdjustmentBehavior::scrollableAxes: {
|
||||
if (contentWidthGreaterThenScrollBounds || _bounceHorizontally) {
|
||||
allInsects += UIEdgeInsets(0, layoutMargins().left, 0, layoutMargins().right);
|
||||
}
|
||||
if (contentHeightGreaterThenScrollBounds || _bounceVertically) {
|
||||
allInsects += UIEdgeInsets(layoutMargins().top, 0, layoutMargins().bottom, 0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case UIScrollViewContentInsetAdjustmentBehavior::always: {
|
||||
allInsects += layoutMargins();
|
||||
break;
|
||||
}
|
||||
case UIScrollViewContentInsetAdjustmentBehavior::never: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool contentWidthGreaterThenScrollSafeArea = contentWidth > bounds().width() -allInsects.left - allInsects.right;
|
||||
bool contentHeightGreaterThenScrollSafeArea = contentHeight > bounds().height() - allInsects.top - allInsects.bottom;
|
||||
|
||||
NXPoint target;
|
||||
if (!contentWidthGreaterThenScrollSafeArea) {
|
||||
target.x = - allInsects.left;
|
||||
} else {
|
||||
target.x = fminf(fmaxf(newContentOffset.x, -allInsects.left), (contentWidth + allInsects.right) - bounds().width());
|
||||
}
|
||||
|
||||
if (!contentHeightGreaterThenScrollSafeArea) {
|
||||
target.y = - allInsects.top;
|
||||
} else {
|
||||
target.y = fminf(fmaxf(newContentOffset.y, -allInsects.top), (contentHeight + allInsects.bottom) - bounds().height());
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
NXRect UIScrollView::contentOffsetBounds() {
|
||||
auto contentNXSize = this->contentSize();
|
||||
auto contentHeight = contentNXSize.height;// fmaxf(contentNXSize.height, bounds().height());
|
||||
auto contentWidth = contentNXSize.width;// fmaxf(contentNXSize.width, bounds().width());
|
||||
|
||||
auto allInsects = _contentInset;// + layoutMargins();
|
||||
|
||||
bool contentWidthGreaterThenScrollBounds = contentWidth > bounds().width() -_contentInset.left - _contentInset.right;
|
||||
bool contentHeightGreaterThenScrollBounds = contentHeight > bounds().height() - _contentInset.top - _contentInset.bottom;
|
||||
|
||||
switch (_contentInsetAdjustmentBehavior) {
|
||||
case UIScrollViewContentInsetAdjustmentBehavior::scrollableAxes: {
|
||||
if (contentWidthGreaterThenScrollBounds || _bounceHorizontally) {
|
||||
allInsects += UIEdgeInsets(0, layoutMargins().left, 0, layoutMargins().right);
|
||||
}
|
||||
if (contentHeightGreaterThenScrollBounds || _bounceVertically) {
|
||||
allInsects += UIEdgeInsets(layoutMargins().top, 0, layoutMargins().bottom, 0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case UIScrollViewContentInsetAdjustmentBehavior::always: {
|
||||
allInsects += layoutMargins();
|
||||
break;
|
||||
}
|
||||
case UIScrollViewContentInsetAdjustmentBehavior::never: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool contentWidthGreaterThenScrollSafeArea = contentWidth > bounds().width() -allInsects.left - allInsects.right;
|
||||
bool contentHeightGreaterThenScrollSafeArea = contentHeight > bounds().height() - allInsects.top - allInsects.bottom;
|
||||
|
||||
NXRect resBounds;
|
||||
if (!contentWidthGreaterThenScrollSafeArea) {
|
||||
resBounds.origin.x = - allInsects.left;
|
||||
resBounds.size.width = 0;
|
||||
} else {
|
||||
resBounds.origin.x = - allInsects.left;
|
||||
resBounds.size.width = (contentWidth + allInsects.left + allInsects.right) - bounds().width();
|
||||
}
|
||||
|
||||
if (!contentHeightGreaterThenScrollSafeArea) {
|
||||
resBounds.origin.y = - allInsects.top;
|
||||
resBounds.size.height = 0;
|
||||
} else {
|
||||
resBounds.origin.y = - allInsects.top;
|
||||
resBounds.size.height = (contentHeight + allInsects.top + allInsects.bottom) - bounds().height();
|
||||
}
|
||||
|
||||
return resBounds;
|
||||
}
|
||||
|
||||
bool UIScrollView::shouldBounceHorizontally() {
|
||||
auto contentNXSize = this->contentSize();
|
||||
bool contentWidthGreaterThenScrollBounds = contentNXSize.width > bounds().width() -_contentInset.left - _contentInset.right;
|
||||
|
||||
auto allInsects = _contentInset;// + layoutMargins();
|
||||
switch (_contentInsetAdjustmentBehavior) {
|
||||
case UIScrollViewContentInsetAdjustmentBehavior::scrollableAxes: {
|
||||
if (contentWidthGreaterThenScrollBounds || _bounceHorizontally) {
|
||||
allInsects += UIEdgeInsets(0, layoutMargins().left, 0, layoutMargins().right);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case UIScrollViewContentInsetAdjustmentBehavior::always: {
|
||||
allInsects += layoutMargins();
|
||||
break;
|
||||
}
|
||||
case UIScrollViewContentInsetAdjustmentBehavior::never: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool contentWidthGreaterThenScrollSafeArea = contentNXSize.width > bounds().width() -allInsects.left - allInsects.right;
|
||||
return contentWidthGreaterThenScrollSafeArea && _bounceHorizontally;
|
||||
}
|
||||
|
||||
bool UIScrollView::shouldBounceVertically() {
|
||||
auto contentNXSize = this->contentSize();
|
||||
bool contentHeightGreaterThenScrollBounds = contentNXSize.height > bounds().height() - _contentInset.top - _contentInset.bottom;
|
||||
|
||||
auto allInsects = _contentInset;// + layoutMargins();
|
||||
switch (_contentInsetAdjustmentBehavior) {
|
||||
case UIScrollViewContentInsetAdjustmentBehavior::scrollableAxes: {
|
||||
if (contentHeightGreaterThenScrollBounds || _bounceVertically) {
|
||||
allInsects += UIEdgeInsets(layoutMargins().top, 0, layoutMargins().bottom, 0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case UIScrollViewContentInsetAdjustmentBehavior::always: {
|
||||
allInsects += layoutMargins();
|
||||
break;
|
||||
}
|
||||
case UIScrollViewContentInsetAdjustmentBehavior::never: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
bool contentHeightGreaterThenScrollSafeArea = contentNXSize.height > bounds().height() - allInsects.top - allInsects.bottom;
|
||||
return contentHeightGreaterThenScrollSafeArea && _bounceVertically;
|
||||
}
|
||||
|
||||
void UIScrollView::onPan() {
|
||||
auto translation = _panGestureRecognizer->translationInView(shared_from_this());
|
||||
// _panGestureRecognizer->setTranslation(NXPoint(), shared_from_this());
|
||||
|
||||
auto panGestureVelocity = _panGestureRecognizer->velocityIn(shared_from_this());
|
||||
weightedAverageVelocity = weightedAverageVelocity * 0.2 + panGestureVelocity * 0.8;
|
||||
|
||||
auto offset = contentOffsetBounds();
|
||||
auto rubberBand = RubberBand(0.55f, frame().size, contentOffsetBounds());
|
||||
|
||||
NXPoint clamped = getBoundsCheckedContentOffset(_initialContentOffset - translation);
|
||||
NXPoint target = rubberBand.clamp(_initialContentOffset - translation);
|
||||
|
||||
if (!shouldBounceHorizontally()) {
|
||||
target.x = clamped.x;
|
||||
}
|
||||
if (!shouldBounceVertically()) {
|
||||
target.y = clamped.y;
|
||||
}
|
||||
|
||||
setContentOffset(target, false);
|
||||
|
||||
// auto newOffset = getBoundsCheckedContentOffset(_initialContentOffset - translation);
|
||||
// setContentOffset(newOffset, false);
|
||||
}
|
||||
|
||||
void UIScrollView::onPanGestureStateChanged() {
|
||||
switch (_panGestureRecognizer->state()) {
|
||||
case UIGestureRecognizerState::possible: {
|
||||
// showScrollIndicators();
|
||||
cancelDeceleratingIfNeccessary();
|
||||
break;
|
||||
}
|
||||
case UIGestureRecognizerState::began: {
|
||||
printf("Began\n");
|
||||
_initialContentOffset = contentOffset();
|
||||
break;
|
||||
}
|
||||
case UIGestureRecognizerState::changed: {
|
||||
if (!delegate.expired()) delegate.lock()->scrollViewWillBeginDragging(shared_from_base<UIScrollView>());
|
||||
onPan();
|
||||
break;
|
||||
}
|
||||
case UIGestureRecognizerState::ended: {
|
||||
startDeceleratingIfNecessary();
|
||||
weightedAverageVelocity = NXPoint();
|
||||
break;
|
||||
}
|
||||
// XXX: Spring back with animation:
|
||||
//case .ended, .cancelled:
|
||||
//if contentOffset.x < _contentInset.left {
|
||||
// setContentOffset(CGNXPoint(x: _contentInset.left, y: contentOffset.y), animated: true)
|
||||
//}
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
void UIScrollView::layoutScrollIndicatorsIfNeeded() {
|
||||
|
||||
}
|
||||
|
||||
void UIScrollView::showScrollIndicators() {
|
||||
|
||||
}
|
||||
|
||||
void UIScrollView::hideScrollIndicators() {
|
||||
|
||||
}
|
||||
|
||||
void UIScrollView::startDeceleratingIfNecessary() {
|
||||
// Only animate if instantaneous velocity is large enough
|
||||
// Otherwise we could animate after scrolling quickly, pausing for a few seconds, then letting go
|
||||
// TODO: Need to check velocity, it's too weak
|
||||
auto velocity = NXPoint(-weightedAverageVelocity.x * 10, -weightedAverageVelocity.y * 10);
|
||||
if (!shouldBounceVertically()) {
|
||||
velocity.y = 0;
|
||||
}
|
||||
if (!shouldBounceHorizontally()) {
|
||||
velocity.x = 0;
|
||||
}
|
||||
|
||||
auto decelerationRate = _decelerationRate.rawValue();
|
||||
auto threshold = 0.5f / layer()->contentsScale();
|
||||
|
||||
auto parameters = DecelerationTimingParameters(contentOffset(),velocity, decelerationRate, threshold);
|
||||
|
||||
auto destination = parameters.destination();
|
||||
auto clippedDestination = getBoundsCheckedContentOffset(destination);
|
||||
bool isClipped = destination != clippedDestination;
|
||||
|
||||
float duration;
|
||||
if (isClipped) {
|
||||
duration = parameters.durationTo(clippedDestination);
|
||||
} else {
|
||||
duration = parameters.duration();
|
||||
}
|
||||
|
||||
_isDecelerating = true;
|
||||
_timerAnimation = std::make_shared<TimerAnimation>(duration, [this, parameters](float, double time) {
|
||||
setContentOffset(parameters.valueAt(time), false);
|
||||
}, [this, parameters, duration, isClipped](bool) {
|
||||
_isDecelerating = isClipped;
|
||||
|
||||
if (isClipped) {
|
||||
auto velocity = parameters.velocityAt(duration);
|
||||
bounceWithVelocity(velocity);
|
||||
}
|
||||
});
|
||||
// auto nonBoundsCheckedScrollAnimationDistance = weightedAverageVelocity * dampingFactor; // hand-tuned
|
||||
// auto targetOffset = contentOffset() - nonBoundsCheckedScrollAnimationDistance;// getBoundsCheckedContentOffset(contentOffset() - nonBoundsCheckedScrollAnimationDistance);
|
||||
// auto distanceToBoundsCheckedTarget = contentOffset() - targetOffset;
|
||||
|
||||
// auto velocityIsLargeEnoughToDecelerate = (velocity.magnitude() > 10);
|
||||
// auto willDecelerate = (velocityIsLargeEnoughToDecelerate && distanceToBoundsCheckedTarget.magnitude() > 0.0);
|
||||
//
|
||||
// if (!delegate.expired()) delegate.lock()->scrollViewDidEndDragging(shared_from_base<UIScrollView>(), willDecelerate);
|
||||
// if (!willDecelerate) { hideScrollIndicators(); return; }
|
||||
//
|
||||
// // https://ariya.io/2011/10/flick-list-with-its-momentum-scrolling-and-deceleration
|
||||
// // TODO: This value should be calculated from `self.decelerationRate` instead
|
||||
// // But actually we want to redo this function to avoid `UIView.animate` entirely,
|
||||
// // in which case we wouldn't need an animationTime at all.
|
||||
// auto animationTimeConstant = 0.325f * dampingFactor;
|
||||
//
|
||||
// // This calculation is a weird approximation but it's close enough for now...
|
||||
// auto animationTime = logf(distanceToBoundsCheckedTarget.magnitude()) * animationTimeConstant;
|
||||
//
|
||||
// UIViewAnimationOptions options = UIViewAnimationOptions(UIViewAnimationOptions::beginFromCurrentState | UIViewAnimationOptions::customEaseOut | UIViewAnimationOptions::allowUserInteraction);
|
||||
// UIView::animate(
|
||||
// animationTime,
|
||||
// 0,
|
||||
// options,
|
||||
// [this, targetOffset]() {
|
||||
// _isDecelerating = true;
|
||||
// setContentOffset(targetOffset, false);
|
||||
// },
|
||||
// [this](bool) {
|
||||
// _isDecelerating = false;
|
||||
// }
|
||||
// );
|
||||
}
|
||||
|
||||
|
||||
void UIScrollView::bounceWithVelocity(NXPoint velocity) {
|
||||
auto restOffset = getBoundsCheckedContentOffset(contentOffset());
|
||||
auto displacement = contentOffset() - restOffset;
|
||||
auto threshold = 0.5f / layer()->contentsScale();
|
||||
auto spring = Spring(1, 100, 1);
|
||||
|
||||
auto parameters = SpringTimingParameters(spring, displacement,velocity, threshold);
|
||||
|
||||
auto duration = parameters.duration();
|
||||
_timerAnimation = std::make_shared<TimerAnimation>(duration, [this, duration, restOffset, parameters](auto, float time){
|
||||
setContentOffset(restOffset + parameters.valueAt(time), false);
|
||||
}, [this](bool) {
|
||||
_isDecelerating = false;
|
||||
});
|
||||
}
|
||||
|
||||
void UIScrollView::cancelDeceleratingIfNeccessary() {
|
||||
if (!_isDecelerating) { return; }
|
||||
|
||||
// Get the presentation value from the current animation
|
||||
if (_timerAnimation) _timerAnimation->invalidate();
|
||||
setContentOffset(visibleContentOffset(), false);
|
||||
cancelDecelerationAnimations();
|
||||
_isDecelerating = false;
|
||||
}
|
||||
|
||||
void UIScrollView::cancelDecelerationAnimations() {
|
||||
// if (!layer()->animations.isEmpty) {
|
||||
// layer.removeAnimation(forKey: "bounds")
|
||||
// horizontalScrollIndicator.layer.removeAnimation(forKey: "position")
|
||||
// verticalScrollIndicator.layer.removeAnimation(forKey: "position")
|
||||
// }
|
||||
layer()->removeAnimation("bounds");
|
||||
}
|
||||
|
||||
//bool UIScrollView::applyXMLAttribute(std::string name, std::string value) {
|
||||
// if (UIView::applyXMLAttribute(name, value)) { return true; }
|
||||
//
|
||||
// REGISTER_XIB_ATTRIBUTE(bounceVertically, valueToBool, setBounceVertically)
|
||||
// REGISTER_XIB_ATTRIBUTE(bounceHorizontally, valueToBool, setBounceHorizontally)
|
||||
//
|
||||
// return false;
|
||||
//}
|
||||
|
||||
void UIScrollView::layoutSubviews() {
|
||||
UIView::layoutSubviews();
|
||||
setContentOffset(getBoundsCheckedContentOffset(contentOffset()), false);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
//
|
||||
// Created by Daniil Vinogradov on 12/01/2024.
|
||||
//
|
||||
|
||||
#include <UIScrollViewExtensions/DecelerationTimingParameters.h>
|
||||
#include <cmath>
|
||||
|
||||
namespace NXKit {
|
||||
|
||||
NXPoint DecelerationTimingParameters::destination() const {
|
||||
auto dCoeff = 1000 * log(decelerationRate);
|
||||
auto res = initialValue - initialVelocity / dCoeff;
|
||||
return res;
|
||||
}
|
||||
|
||||
float DecelerationTimingParameters::duration() const {
|
||||
if (initialVelocity.magnitude() <= 0) { return 0; }
|
||||
|
||||
auto dCoeff = 1000 * log(decelerationRate);
|
||||
auto res = log(-dCoeff * threshold / initialVelocity.magnitude()) / dCoeff;
|
||||
return res;
|
||||
}
|
||||
|
||||
NXPoint DecelerationTimingParameters::valueAt(NXFloat time) const {
|
||||
auto dCoeff = 1000 * log(decelerationRate);
|
||||
auto res = initialValue + initialVelocity * (pow(decelerationRate, float(1000 * time)) - 1) / dCoeff;
|
||||
return res;
|
||||
}
|
||||
|
||||
float DecelerationTimingParameters::durationTo(NXPoint value) const {
|
||||
if (value.distanceToSegment(initialValue, destination()) >= threshold) { return 0; }
|
||||
|
||||
auto dCoeff = 1000 * log(decelerationRate);
|
||||
auto res = log(1.0f + dCoeff * (value - initialValue).magnitude() / initialVelocity.magnitude()) / dCoeff;
|
||||
return res;
|
||||
}
|
||||
|
||||
NXPoint DecelerationTimingParameters::velocityAt(NXFloat time) const {
|
||||
return initialVelocity * pow(decelerationRate, NXFloat(1000 * time));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
//
|
||||
// Created by Даниил Виноградов on 12.01.2024.
|
||||
//
|
||||
|
||||
#include <UIScrollViewExtensions/RubberBand.h>
|
||||
|
||||
namespace NXKit {
|
||||
|
||||
NXPoint RubberBand::clamp(NXPoint point) const{
|
||||
auto x = Geometry::rubberBandClamp(point.x, coeff, dims.width, bounds.minX(), bounds.maxX());
|
||||
auto y = Geometry::rubberBandClamp(point.y, coeff, dims.height, bounds.minY(), bounds.maxY());
|
||||
return {x, y};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
//
|
||||
// Created by Даниил Виноградов on 12.01.2024.
|
||||
//
|
||||
|
||||
#include <UIScrollViewExtensions/SpringTimingParameters.h>
|
||||
|
||||
namespace NXKit {
|
||||
struct UnderdampedSpringTimingParameters: public TimingParameters {
|
||||
Spring spring;
|
||||
NXPoint displacement;
|
||||
NXPoint initialVelocity;
|
||||
NXFloat threshold;
|
||||
|
||||
UnderdampedSpringTimingParameters(Spring spring, NXPoint displacement, NXPoint initialVelocity, NXFloat threshold):
|
||||
spring(spring),
|
||||
displacement(displacement),
|
||||
initialVelocity(initialVelocity),
|
||||
threshold(threshold) {}
|
||||
|
||||
[[nodiscard]] NXPoint c2() const {
|
||||
return (initialVelocity + displacement * spring.beta() ) / spring.dampedNaturalFrequency();
|
||||
}
|
||||
|
||||
[[nodiscard]] NXFloat duration() const override {
|
||||
if (displacement.magnitude() == 0 && initialVelocity.magnitude() == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return NXFloat(log((displacement.magnitude() + c2().magnitude()) / threshold) / spring.beta());
|
||||
}
|
||||
|
||||
NXPoint valueAt(NXFloat t) const override {
|
||||
auto wd = spring.dampedNaturalFrequency();
|
||||
return (displacement * cos(wd * t) + c2() * sin(wd * t)) * exp(-spring.beta() * t);
|
||||
}
|
||||
};
|
||||
|
||||
struct CriticallyDampedSpringTimingParameters: public TimingParameters {
|
||||
Spring spring;
|
||||
NXPoint displacement;
|
||||
NXPoint initialVelocity;
|
||||
NXFloat threshold;
|
||||
|
||||
CriticallyDampedSpringTimingParameters(Spring spring, NXPoint displacement, NXPoint initialVelocity, NXFloat threshold):
|
||||
spring(spring),
|
||||
displacement(displacement),
|
||||
initialVelocity(initialVelocity),
|
||||
threshold(threshold) {}
|
||||
|
||||
[[nodiscard]] NXPoint c2() const {
|
||||
return initialVelocity + displacement * spring.beta();
|
||||
}
|
||||
|
||||
[[nodiscard]] NXFloat duration() const override {
|
||||
if (displacement.magnitude() == 0 && initialVelocity.magnitude() == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto b = spring.beta();
|
||||
auto e = NXFloat(M_E);
|
||||
|
||||
auto t1 = 1 / b * log(2 * displacement.magnitude() / threshold);
|
||||
auto t2 = 2 / b * log(4 * c2().magnitude() / (e * b * threshold));
|
||||
|
||||
return fmaxf(t1, t2);
|
||||
}
|
||||
|
||||
NXPoint valueAt(NXFloat t) const override {
|
||||
return (displacement + c2() * t) * exp(-spring.beta() * t);
|
||||
}
|
||||
};
|
||||
|
||||
NXFloat Spring::damping() const {
|
||||
return 2 * dampingRatio * sqrt(mass * stiffness);
|
||||
}
|
||||
|
||||
NXFloat Spring::beta() const {
|
||||
return damping() / (2 * mass);
|
||||
}
|
||||
|
||||
NXFloat Spring::dampedNaturalFrequency() const {
|
||||
return sqrt(stiffness / mass) * sqrt(1 - dampingRatio * dampingRatio);
|
||||
}
|
||||
|
||||
SpringTimingParameters::SpringTimingParameters(Spring spring, NXPoint displacement, NXPoint initialVelocity, NXFloat threshold):
|
||||
spring(spring), displacement(displacement), initialVelocity(initialVelocity), threshold(threshold)
|
||||
{
|
||||
if (spring.dampingRatio == 1) {
|
||||
impl = std::make_shared<CriticallyDampedSpringTimingParameters>(spring,
|
||||
displacement,
|
||||
initialVelocity,
|
||||
threshold);
|
||||
} else if (spring.dampingRatio > 0 && spring.dampingRatio < 1) {
|
||||
impl = std::make_shared<UnderdampedSpringTimingParameters>(spring,
|
||||
displacement,
|
||||
initialVelocity,
|
||||
threshold);
|
||||
} else {
|
||||
printf("dampingRatio should be greater than 0 and less than or equal to 1");
|
||||
}
|
||||
}
|
||||
|
||||
NXFloat SpringTimingParameters::duration() const {
|
||||
auto res = impl->duration();
|
||||
return res;
|
||||
}
|
||||
|
||||
NXPoint SpringTimingParameters::valueAt(NXFloat time) const {
|
||||
auto res = impl->valueAt(time);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
//
|
||||
// Created by Daniil Vinogradov on 12/01/2024.
|
||||
//
|
||||
|
||||
#include <UIScrollViewExtensions/TimerAnimation.h>
|
||||
#include <utility>
|
||||
|
||||
namespace NXKit {
|
||||
|
||||
TimerAnimation::TimerAnimation(double duration, std::function<void(float, double)> animations, std::function<void(bool)> completion):
|
||||
displayLink(CADisplayLink([this]() {
|
||||
handleFrame();
|
||||
})),
|
||||
duration(duration),
|
||||
animations(std::move(animations)),
|
||||
completion(std::move(completion))
|
||||
{}
|
||||
|
||||
TimerAnimation::~TimerAnimation() {
|
||||
invalidate();
|
||||
}
|
||||
|
||||
void TimerAnimation::invalidate() {
|
||||
if (!_running) return;
|
||||
_running = false;
|
||||
displayLink.invalidate();
|
||||
}
|
||||
|
||||
void TimerAnimation::handleFrame() {
|
||||
if (!_running) return;
|
||||
auto elapsed = (Timer() - firstFrameTimestamp) / 1000;
|
||||
if (elapsed >= duration) {
|
||||
animations(1, duration);
|
||||
_running = false;
|
||||
completion(true);
|
||||
displayLink.invalidate();
|
||||
} else {
|
||||
animations(elapsed / duration, elapsed);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,11 @@ void UIView::setBounds(NXRect bounds) {
|
||||
_layer->setBounds(bounds);
|
||||
}
|
||||
|
||||
void UIView::setHidden(bool hidden) {
|
||||
_layer->setHidden(hidden);
|
||||
_yoga->setIncludedInLayout(!hidden);
|
||||
}
|
||||
|
||||
void UIView::setCenter(NXPoint position) {
|
||||
auto frame = this->frame();
|
||||
frame.setMidX(position.x);
|
||||
@@ -54,12 +59,124 @@ NXPoint UIView::center() const {
|
||||
return { frame.midX(), frame.midY() };
|
||||
}
|
||||
|
||||
void UIView::setInsetsLayoutMarginsFromSafeArea(bool insetsLayoutMarginsFromSafeArea) {
|
||||
if (_insetsLayoutMarginsFromSafeArea == insetsLayoutMarginsFromSafeArea) return;
|
||||
_insetsLayoutMarginsFromSafeArea = insetsLayoutMarginsFromSafeArea;
|
||||
setNeedsLayout();
|
||||
}
|
||||
|
||||
void UIView::setPreservesSuperviewLayoutMargins(bool preservesSuperviewLayoutMargins) {
|
||||
if (_preservesSuperviewLayoutMargins == preservesSuperviewLayoutMargins) return;
|
||||
_preservesSuperviewLayoutMargins = preservesSuperviewLayoutMargins;
|
||||
setNeedsLayout();
|
||||
}
|
||||
|
||||
UIEdgeInsets UIView::layoutMargins() {
|
||||
return _calculatedLayoutMargins;
|
||||
}
|
||||
|
||||
void UIView::setLayoutMargins(UIEdgeInsets layoutMargins) {
|
||||
if (_layoutMargins == layoutMargins) return;
|
||||
_layoutMargins = layoutMargins;
|
||||
setNeedsUpdateLayoutMargins();
|
||||
setNeedsLayout();
|
||||
}
|
||||
|
||||
void UIView::setSafeAreaInsets(UIEdgeInsets safeAreaInsets) {
|
||||
if (_safeAreaInsets == safeAreaInsets) return;
|
||||
_safeAreaInsets = safeAreaInsets;
|
||||
setNeedsUpdateLayoutMargins();
|
||||
updateSafeAreaInsetsInChilds();
|
||||
safeAreaInsetsDidChange();
|
||||
setNeedsLayout();
|
||||
}
|
||||
|
||||
void UIView::updateSafeAreaInsetsInChilds() {
|
||||
for (auto& subview: _subviews) {
|
||||
subview->setNeedsUpdateSafeAreaInsets();
|
||||
}
|
||||
}
|
||||
|
||||
void UIView::updateSafeAreaInsetsIfNeeded() {
|
||||
if (_needsUpdateSafeAreaInsets) {
|
||||
_needsUpdateSafeAreaInsets = false;
|
||||
updateSafeAreaInsets();
|
||||
}
|
||||
}
|
||||
|
||||
void UIView::updateSafeAreaInsets() {
|
||||
if (_superview.expired()) {
|
||||
if (shared_from_base<UIWindow>() != nullptr) return;
|
||||
else return setSafeAreaInsets(UIEdgeInsets::zero);
|
||||
}
|
||||
|
||||
auto parentSafeArea = _superview.lock()->_safeAreaInsets;
|
||||
auto parentSize = _superview.lock()->bounds().size;
|
||||
|
||||
layoutIfNeeded();
|
||||
auto frame = this->frame();
|
||||
|
||||
|
||||
auto newSafeArea = UIEdgeInsets(fmaxf(0, parentSafeArea.top - fmaxf(0, frame.minY())),
|
||||
fmaxf(0, parentSafeArea.left - fmaxf(0, frame.minX())),
|
||||
fmaxf(0, parentSafeArea.bottom - fmaxf(0, (parentSize.height - frame.maxY()))),
|
||||
fmaxf(0, parentSafeArea.right - fmaxf(0, (parentSize.width - frame.maxX()))));
|
||||
|
||||
if (!_parentController.expired()) {
|
||||
newSafeArea += _parentController.lock()->additionalSafeAreaInsets();
|
||||
}
|
||||
|
||||
setSafeAreaInsets(newSafeArea);
|
||||
}
|
||||
|
||||
void UIView::updateLayoutMarginIfNeeded() {
|
||||
if (_needsUpdateLayoutMargins) {
|
||||
_needsUpdateLayoutMargins = false;
|
||||
updateLayoutMargin();
|
||||
}
|
||||
}
|
||||
|
||||
void UIView::updateLayoutMargin() {
|
||||
auto margins = _layoutMargins;
|
||||
|
||||
bool needsSuperviewMargins = _preservesSuperviewLayoutMargins && !superview().expired();
|
||||
if (needsSuperviewMargins && _insetsLayoutMarginsFromSafeArea) {
|
||||
auto superviewMargins = superview().lock()->layoutMargins();
|
||||
auto maxCombination = UIEdgeInsets(fmaxf(_safeAreaInsets.top, superviewMargins.top),
|
||||
fmaxf(_safeAreaInsets.left, superviewMargins.left),
|
||||
fmaxf(_safeAreaInsets.bottom, superviewMargins.bottom),
|
||||
fmaxf(_safeAreaInsets.right, superviewMargins.right));
|
||||
margins += maxCombination;
|
||||
} else {
|
||||
if (_insetsLayoutMarginsFromSafeArea) {
|
||||
margins += _safeAreaInsets;
|
||||
}
|
||||
|
||||
if (needsSuperviewMargins) {
|
||||
margins += superview().lock()->layoutMargins();
|
||||
}
|
||||
}
|
||||
|
||||
if (!_parentController.expired() && _parentController.lock()->viewRespectsSystemMinimumLayoutMargins()) {
|
||||
auto minMargins = _parentController.lock()->systemMinimumLayoutMargins();
|
||||
margins = UIEdgeInsets(fmaxf(margins.top, minMargins.top),
|
||||
fmaxf(margins.left, minMargins.left),
|
||||
fmaxf(margins.bottom, minMargins.bottom),
|
||||
fmaxf(margins.right, minMargins.right));
|
||||
}
|
||||
|
||||
if (_calculatedLayoutMargins != margins) {
|
||||
_calculatedLayoutMargins = margins;
|
||||
layoutMarginsDidChange();
|
||||
}
|
||||
}
|
||||
|
||||
void UIView::addGestureRecognizer(const std::shared_ptr<UIGestureRecognizer>& gestureRecognizer) {
|
||||
gestureRecognizer->_view = weak_from_this();
|
||||
_gestureRecognizers.push_back(gestureRecognizer);
|
||||
}
|
||||
|
||||
void UIView::addSubview(std::shared_ptr<UIView> view) {
|
||||
void UIView::addSubview(const std::shared_ptr<UIView>& view) {
|
||||
bool needToNotifyViewController = false;
|
||||
if (!view->_parentController.expired()) {
|
||||
auto window = this->window();
|
||||
@@ -92,7 +209,7 @@ void UIView::setSuperview(const std::shared_ptr<UIView>& superview) {
|
||||
tintColorDidChange();
|
||||
}
|
||||
|
||||
void UIView::insertSubviewAt(std::shared_ptr<UIView> view, int index) {
|
||||
void UIView::insertSubviewAt(const std::shared_ptr<UIView>& view, int index) {
|
||||
// TODO: Need to implement
|
||||
}
|
||||
|
||||
|
||||
@@ -67,11 +67,12 @@ void YogaTestViewController::loadView() {
|
||||
|
||||
auto buttonsBox = new_shared<UIView>();
|
||||
|
||||
auto res = romfs::get("img/star.png");
|
||||
auto res = romfs::get("img/icons8-star-90.png");
|
||||
auto data = new_shared<NXData>(res.data(), res.size());
|
||||
auto image = UIImage::fromData(data, 3);
|
||||
image->setRenderModeAsTemplate(true);
|
||||
|
||||
auto control = new_shared<UIButton>(UIButtonStyle::tinted);
|
||||
auto control = new_shared<UIButton>(UIButtonStyle::filled);
|
||||
control->layer()->setCornerRadius(10);
|
||||
control->titleLabel()->setText("Buttogn!");
|
||||
control->setImage(image);
|
||||
@@ -82,6 +83,7 @@ void YogaTestViewController::loadView() {
|
||||
auto control2 = new_shared<UIButton>(UIButtonStyle::tinted);
|
||||
control2->layer()->setCornerRadius(10);
|
||||
control2->titleLabel()->setText("Button!");
|
||||
control2->setTintColor(UIColor::systemRed);
|
||||
|
||||
buttonsBox->addSubview(control2);
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
Reference in New Issue
Block a user