Skip to content

Commit

Permalink
WIP: Yoga auto layout
Browse files Browse the repository at this point in the history
  • Loading branch information
XITRIX committed Dec 19, 2024
1 parent fb64b36 commit 8a48d4f
Show file tree
Hide file tree
Showing 14 changed files with 602 additions and 11 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ add_executable(${PROJECT_NAME}
app/main.cpp
app/AppDelegate.cpp
app/Screens/TestViewController/TestViewController.cpp
app/Screens/YogaTestViewController/YogaTestViewController.cpp
)

target_include_directories(${PROJECT_NAME} PUBLIC
Expand Down
4 changes: 4 additions & 0 deletions Submodules/UIKit/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ add_library(UIKit
lib/CGImage.cpp
lib/CADisplayLink.cpp
lib/CALayer.cpp
lib/YGLayout.cpp
lib/UIApplication.cpp
lib/UIApplicationDelegateImpl.cpp
lib/UIApplicationMain.cpp
Expand Down Expand Up @@ -51,6 +52,9 @@ add_library(UIKit
lib/NXData.cpp
)

list(APPEND platform_libs yogacore )
target_include_directories(UIKit PUBLIC ${EXTERN_PATH}/yoga)

# APPLE
if (APPLE)
target_sources(UIKit PRIVATE
Expand Down
3 changes: 3 additions & 0 deletions Submodules/UIKit/include/UIKit.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,6 @@
#include <UIViewController.h>
#include <UITapGestureRecognizer.h>
#include <UIPanGestureRecognizer.h>
#include <yoga/Yoga.h>

using namespace NXKit::yoga::literals;
12 changes: 12 additions & 0 deletions Submodules/UIKit/include/UIView.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#pragma once

#include "CALayer.h"
#include <YGLayout.h>
#include <UITraitEnvironment.h>
#include <UIViewContentMode.h>
#include <UIResponder.h>
Expand All @@ -16,6 +17,8 @@ class UIView: public UIResponder, public UITraitEnvironment, public CALayerDeleg
public:
UIView(NXRect frame = NXRect(), std::shared_ptr<CALayer> layer = new_shared<CALayer>());

std::string tag;

std::shared_ptr<UIResponder> next() override;

void setFrame(NXRect frame);
Expand Down Expand Up @@ -78,6 +81,11 @@ class UIView: public UIResponder, public UITraitEnvironment, public CALayerDeleg
virtual NXSize sizeThatFits(NXSize size);
void sizeToFit();

// FlexLayout
std::shared_ptr<YGLayout> yoga() const { return _yoga; }
void configureLayout(std::function<void(std::shared_ptr<YGLayout>)> block);
void setAutolayoutEnabled(bool enabled) { _yoga->setEnabled(enabled); }

// Render
void drawAndLayoutTreeIfNeeded();

Expand Down Expand Up @@ -117,7 +125,9 @@ class UIView: public UIResponder, public UITraitEnvironment, public CALayerDeleg
virtual void display(std::shared_ptr<CALayer> layer) override;
private:
friend class UIViewController;
friend class YGLayout;

std::shared_ptr<YGLayout> _yoga;
std::vector<std::shared_ptr<UIGestureRecognizer>> _gestureRecognizers;
std::vector<std::shared_ptr<UIView>> _subviews;
std::weak_ptr<UIView> _superview;
Expand All @@ -132,6 +142,8 @@ class UIView: public UIResponder, public UITraitEnvironment, public CALayerDeleg

void setSuperview(std::shared_ptr<UIView> superview);
bool anyCurrentlyRunningAnimationsAllowUserInteraction();

std::shared_ptr<UIView> layoutRoot();
};

}
239 changes: 239 additions & 0 deletions Submodules/UIKit/include/YGLayout.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
//
// YGLayout.hpp
// SDLTest
//
// Created by Даниил Виноградов on 10.03.2023.
//

#pragma once

#include <yoga/Yoga.h>
#include <Geometry.h>
#include <memory>
#include <vector>
#include <cassert>

namespace NXKit {

#define YG_PROPERTY(type, lowercased_name, capitalized_name) \
type lowercased_name() \
{ \
return YGNodeStyleGet##capitalized_name(_node); \
} \
\
void set##capitalized_name(type lowercased_name) \
{ \
YGNodeStyleSet##capitalized_name(_node, lowercased_name); \
}

#define YG_VALUE_PROPERTY(lowercased_name, capitalized_name) \
YGValue lowercased_name() \
{ \
return YGNodeStyleGet##capitalized_name(_node); \
}

#define YG_AUTO_VALUE_PROPERTY(lowercased_name, capitalized_name) \
YGValue lowercased_name() \
{ \
return YGNodeStyleGet##capitalized_name(_node); \
} \
\
void set##capitalized_name(YGValue lowercased_name) \
{ \
switch (lowercased_name.unit) { \
case YGUnitPoint: \
YGNodeStyleSet##capitalized_name(_node, lowercased_name.value); \
break; \
case YGUnitPercent: \
YGNodeStyleSet##capitalized_name##Percent(_node, lowercased_name.value); \
break; \
case YGUnitAuto: \
YGNodeStyleSet##capitalized_name##Auto(_node); \
break; \
default: \
assert(false && "Not implemented"); \
} \
}

#define YG_EDGE_PROPERTY_GETTER(type, lowercased_name, capitalized_name, property, edge) \
type lowercased_name() \
{ \
return YGNodeStyleGet##property(_node, edge); \
}

#define YG_EDGE_PROPERTY_SETTER(lowercased_name, capitalized_name, property, edge) \
void set##capitalized_name(float lowercased_name) \
{ \
YGNodeStyleSet##property(_node, edge, lowercased_name); \
}

#define YG_EDGE_PROPERTY(lowercased_name, capitalized_name, property, edge) \
YG_EDGE_PROPERTY_GETTER(float, lowercased_name, capitalized_name, property, edge) \
YG_EDGE_PROPERTY_SETTER(lowercased_name, capitalized_name, property, edge)

#define YG_VALUE_EDGE_PROPERTY_SETTER(objc_lowercased_name, objc_capitalized_name, c_name, edge) \
void set##objc_capitalized_name(YGValue objc_lowercased_name) \
{ \
switch (objc_lowercased_name.unit) { \
case YGUnitUndefined: \
YGNodeStyleSet##c_name(_node, edge, objc_lowercased_name.value); \
break; \
case YGUnitPoint: \
YGNodeStyleSet##c_name(_node, edge, objc_lowercased_name.value); \
break; \
case YGUnitPercent: \
YGNodeStyleSet##c_name##Percent(_node, edge, objc_lowercased_name.value); \
break; \
default: \
assert(false && "Not implemented"); \
} \
}

#define YG_VALUE_EDGE_PROPERTY(lowercased_name, capitalized_name, property, edge) \
YG_EDGE_PROPERTY_GETTER(YGValue, lowercased_name, capitalized_name, property, edge) \
YG_VALUE_EDGE_PROPERTY_SETTER(lowercased_name, capitalized_name, property, edge)

#define YG_VALUE_EDGES_PROPERTIES(lowercased_name, capitalized_name) \
YG_VALUE_EDGE_PROPERTY(lowercased_name##Left, capitalized_name##Left, capitalized_name, YGEdgeLeft) \
YG_VALUE_EDGE_PROPERTY(lowercased_name##Top, capitalized_name##Top, capitalized_name, YGEdgeTop) \
YG_VALUE_EDGE_PROPERTY(lowercased_name##Right, capitalized_name##Right, capitalized_name, YGEdgeRight) \
YG_VALUE_EDGE_PROPERTY(lowercased_name##Bottom, capitalized_name##Bottom, capitalized_name, YGEdgeBottom) \
YG_VALUE_EDGE_PROPERTY(lowercased_name##Start, capitalized_name##Start, capitalized_name, YGEdgeStart) \
YG_VALUE_EDGE_PROPERTY(lowercased_name##End, capitalized_name##End, capitalized_name, YGEdgeEnd) \
YG_VALUE_EDGE_PROPERTY(lowercased_name##Horizontal, capitalized_name##Horizontal, capitalized_name, YGEdgeHorizontal) \
YG_VALUE_EDGE_PROPERTY(lowercased_name##Vertical, capitalized_name##Vertical, capitalized_name, YGEdgeVertical) \
YG_VALUE_EDGE_PROPERTY(lowercased_name, capitalized_name, capitalized_name, YGEdgeAll)

#define YG_GUTTER_PROPERTY_GETTER(type, lowercased_name, capitalized_name, property, gutter) \
type lowercased_name() \
{ \
return YGNodeStyleGet##property(_node, gutter); \
}

#define YG_VALUE_GUTTER_PROPERTY_SETTER(objc_lowercased_name, objc_capitalized_name, c_name, gutter) \
void set##objc_capitalized_name(float objc_lowercased_name) \
{ \
YGNodeStyleSet##c_name(_node, gutter, objc_lowercased_name); \
}

#define YG_VALUE_GUTTER_PROPERTY(lowercased_name, capitalized_name, property, gutter) \
YG_GUTTER_PROPERTY_GETTER(YGValue, lowercased_name, capitalized_name, property, gutter) \
YG_VALUE_GUTTER_PROPERTY_SETTER(lowercased_name, capitalized_name, property, gutter)

class UIView;
class YGLayout {
public:
bool isEnabled() { return _isEnabled; }
void setEnabled(bool enabled) {
_isEnabled = enabled;
}

bool isIncludedInLayout() { return _isIncludedInLayout; }
void setIncludedInLayout(bool isIncludedInLayout) { _isIncludedInLayout = isIncludedInLayout; }

YG_PROPERTY(YGDirection, direction, Direction)
YG_PROPERTY(YGFlexDirection, flexDirection, FlexDirection)
YG_PROPERTY(YGJustify, justifyContent, JustifyContent)
YG_PROPERTY(YGAlign, alignContent, AlignContent)
YG_PROPERTY(YGAlign, alignItems, AlignItems)
YG_PROPERTY(YGAlign, alignSelf, AlignSelf)
YG_PROPERTY(YGPositionType, position, PositionType)
YG_PROPERTY(YGWrap, flexWrap, FlexWrap)
YG_PROPERTY(YGOverflow, overflow, Overflow)
YG_PROPERTY(YGDisplay, display, Display)

YG_PROPERTY(float, flex, Flex)
YG_PROPERTY(float, flexGrow, FlexGrow)
YG_PROPERTY(float, flexShrink, FlexShrink)
YG_AUTO_VALUE_PROPERTY(flexBasis, FlexBasis)

YG_VALUE_GUTTER_PROPERTY(columnGap, ColumnGap, Gap, YGGutterColumn)
YG_VALUE_GUTTER_PROPERTY(rowGap, RowGap, Gap, YGGutterRow)
YG_VALUE_GUTTER_PROPERTY(allGap, AllGap, Gap, YGGutterAll)

YG_VALUE_EDGE_PROPERTY(left, Left, Position, YGEdgeLeft)
YG_VALUE_EDGE_PROPERTY(top, Top, Position, YGEdgeTop)
YG_VALUE_EDGE_PROPERTY(right, Right, Position, YGEdgeRight)
YG_VALUE_EDGE_PROPERTY(bottom, Bottom, Position, YGEdgeBottom)
YG_VALUE_EDGE_PROPERTY(start, Start, Position, YGEdgeStart)
YG_VALUE_EDGE_PROPERTY(end, End, Position, YGEdgeEnd)
YG_VALUE_EDGES_PROPERTIES(margin, Margin)
YG_VALUE_EDGES_PROPERTIES(padding, Padding)

YG_EDGE_PROPERTY(borderLeftWidth, BorderLeftWidth, Border, YGEdgeLeft)
YG_EDGE_PROPERTY(borderTopWidth, BorderTopWidth, Border, YGEdgeTop)
YG_EDGE_PROPERTY(borderRightWidth, BorderRightWidth, Border, YGEdgeRight)
YG_EDGE_PROPERTY(borderBottomWidth, BorderBottomWidth, Border, YGEdgeBottom)
YG_EDGE_PROPERTY(borderStartWidth, BorderStartWidth, Border, YGEdgeStart)
YG_EDGE_PROPERTY(borderEndWidth, BorderEndWidth, Border, YGEdgeEnd)
YG_EDGE_PROPERTY(borderWidth, BorderWidth, Border, YGEdgeAll)

YG_AUTO_VALUE_PROPERTY(width, Width)
YG_AUTO_VALUE_PROPERTY(height, Height)
YG_VALUE_PROPERTY(minWidth, MinWidth)
YG_VALUE_PROPERTY(minHeight, MinHeight)
YG_VALUE_PROPERTY(maxWidth, MaxWidth)
YG_VALUE_PROPERTY(maxHeight, MaxHeight)
YG_PROPERTY(float, aspectRatio, AspectRatio)

void setPosition(NXPoint position) {
setLeft(YGValue{static_cast<float>(position.x), YGUnitPoint});
setTop(YGValue{static_cast<float>(position.y), YGUnitPoint});
}

void setSize(NXSize size) {
setWidth(YGValue{static_cast<float>(size.width), YGUnitPoint});
setHeight(YGValue{static_cast<float>(size.height), YGUnitPoint});
}

YGLayout(std::shared_ptr<UIView> view);
~YGLayout();

void layoutIfNeeded();
void applyLayoutPreservingOrigin(bool preserveOrigin);
NXSize calculateLayoutWithSize(NXSize size);

bool isLeaf();
bool isRoot();
bool isDirty();

private:
YGNodeRef _node = nullptr;
std::weak_ptr<UIView> _view;
bool _isEnabled = false;
bool _isIncludedInLayout = true;

bool isUIView;

static void YGAttachNodesFromViewHierachy(std::shared_ptr<UIView> view);
static void YGRemoveAllChildren(const YGNodeRef node);
static YGSize YGMeasureView(YGNodeConstRef node, float width, YGMeasureMode widthMode, float height, YGMeasureMode heightMode);
static float YGSanitizeMeasurement(float constrainedSize, float measuredSize, YGMeasureMode measureMode);
static bool YGNodeHasExactSameChildren(const YGNodeRef node, std::vector<std::shared_ptr<UIView>> subviews);
static void YGApplyLayoutToViewHierarchy(std::shared_ptr<UIView>view, bool preserveOrigin);
static float YGRoundPixelValue(float value);

friend class UIView;
};

namespace yoga {
namespace literals {

inline YGValue operator"" _pt(long double value) {
return YGValue{static_cast<float>(value), YGUnitPoint};
}
inline YGValue operator"" _pt(unsigned long long value) {
return operator"" _pt(static_cast<long double>(value));
}

inline YGValue operator"" _percent(long double value) {
return YGValue{static_cast<float>(value), YGUnitPercent};
}
inline YGValue operator"" _percent(unsigned long long value) {
return operator"" _percent(static_cast<long double>(value));
}

} // namespace literals
} // namespace yoga

}
2 changes: 1 addition & 1 deletion Submodules/UIKit/lib/CALayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ void CALayer::skiaRender(SkCanvas* canvas) {
}

// Contents
if (_contents) {
if (_contents && _bounds.size.valid() && _bounds.size != NXSize()) {
auto contentsGravity = ContentsGravityTransformation(this);
auto width = _contents->size().width * contentsGravity.scale.width / _contentsScale;
auto height = _contents->size().height * contentsGravity.scale.height / _contentsScale;
Expand Down
1 change: 1 addition & 0 deletions Submodules/UIKit/lib/CGImage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,6 @@ CGImage::~CGImage() {
}

NXSize CGImage::size() const {
if (!pointee) return NXSize();
return NXSize(pointee->width(), pointee->height());
}
4 changes: 2 additions & 2 deletions Submodules/UIKit/lib/UIApplicationMain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ bool applicationRunLoop() {
canvas->save();
canvas->scale(scale, scale);

UIView::animate(0.3, [keyWindow]() {
// UIView::animate(0.3, [keyWindow]() {
keyWindow->setFrame({ NXPoint::zero, SkiaCtx::_main->getSize() } );
});
// });

keyWindow->layer()->presentationOrSelf()->skiaRender(canvas);
canvas->restore();
Expand Down
Loading

0 comments on commit 8a48d4f

Please sign in to comment.