Skip to content

Commit

Permalink
feat: make cxx core more extensible
Browse files Browse the repository at this point in the history
  • Loading branch information
jpudysz committed May 24, 2024
1 parent c79b53d commit b514171
Show file tree
Hide file tree
Showing 9 changed files with 534 additions and 486 deletions.
9 changes: 9 additions & 0 deletions cxx/Macros.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#define HOST_FN(name, args, fn_body) \
jsi::Function::createFromHostFunction(rt, \
jsi::PropNameID::forAscii(rt, name), \
args, \
[this, &fnName](jsi::Runtime &rt, const jsi::Value &thisVal, const jsi::Value *arguments, size_t count) -> jsi::Value \
fn_body \
); \

#define BIND_FN(fn) std::bind(&UnistylesRuntime::fn, this, std::placeholders::_1, std::placeholders::_2)
241 changes: 241 additions & 0 deletions cxx/UnistylesImpl.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
#include "UnistylesRuntime.h"
#include <jsi/jsi.h>

using namespace facebook;

jsi::Value UnistylesRuntime::getScreenWidth(jsi::Runtime& rt, std::string fnName) {
return jsi::Value(this->screen.width);
}

jsi::Value UnistylesRuntime::getScreenHeight(jsi::Runtime& rt, std::string fnName) {
return jsi::Value(this->screen.height);
}

jsi::Value UnistylesRuntime::getContentSizeCategory(jsi::Runtime & rt, std::string fnName) {
return jsi::Value(jsi::String::createFromUtf8(rt, this->contentSizeCategory));
}

jsi::Value UnistylesRuntime::hasEnabledAdaptiveThemes(jsi::Runtime& rt, std::string fnName) {
return jsi::Value(this->hasAdaptiveThemes);
}

jsi::Value UnistylesRuntime::getThemeName(jsi::Runtime& rt, std::string fnName) {
return !this->themeName.empty()
? jsi::Value(jsi::String::createFromUtf8(rt, this->themeName))
: this->getThemeOrFail(rt);
}

jsi::Value UnistylesRuntime::getCurrentBreakpoint(jsi::Runtime& rt, std::string fnName) {
return !this->breakpoint.empty()
? jsi::Value(jsi::String::createFromUtf8(rt, this->breakpoint))
: jsi::Value::undefined();
}

jsi::Value UnistylesRuntime::getColorScheme(jsi::Runtime& rt, std::string fnName) {
return jsi::Value(jsi::String::createFromUtf8(rt, this->colorScheme));
}

jsi::Value UnistylesRuntime::getSortedBreakpointPairs(jsi::Runtime& rt, std::string fnName) {
std::unique_ptr<jsi::Array> sortedBreakpointEntriesArray = std::make_unique<jsi::Array>(rt, this->sortedBreakpointPairs.size());

for (size_t i = 0; i < this->sortedBreakpointPairs.size(); ++i) {
std::unique_ptr<jsi::Array> pairArray = std::make_unique<jsi::Array>(rt, 2);
jsi::String nameValue = jsi::String::createFromUtf8(rt, this->sortedBreakpointPairs[i].first);

pairArray->setValueAtIndex(rt, 0, nameValue);
pairArray->setValueAtIndex(rt, 1, jsi::Value(this->sortedBreakpointPairs[i].second));
sortedBreakpointEntriesArray->setValueAtIndex(rt, i, *pairArray);
}

return jsi::Value(rt, *sortedBreakpointEntriesArray);
}

jsi::Value UnistylesRuntime::setBreakpoints(jsi::Runtime& rt, std::string fnName) {
return HOST_FN(fnName, 1, {
jsi::Object breakpointsObj = arguments[0].asObject(rt);
auto sortedBreakpoints = this->toSortedBreakpointPairs(rt, breakpointsObj);

if (sortedBreakpoints.size() == 0) {
throw jsi::JSError(rt, UnistylesErrorBreakpointsCannotBeEmpty);
}

if (sortedBreakpoints.at(0).second != 0) {
throw jsi::JSError(rt, UnistylesErrorBreakpointsMustStartFromZero);
}

this->sortedBreakpointPairs = sortedBreakpoints;

std::string breakpoint = this->getBreakpointFromScreenWidth(this->screen.width, sortedBreakpoints);

this->breakpoint = breakpoint;

return jsi::Value::undefined();
});
}

jsi::Value UnistylesRuntime::setActiveTheme(jsi::Runtime& rt, std::string fnName) {
return HOST_FN(fnName, 1, {
std::string themeName = arguments[0].asString(rt).utf8(rt);

if (this->themeName != themeName) {
this->themeName = themeName;
this->onThemeChangeCallback(themeName);
}

return jsi::Value::undefined();
});
}

jsi::Value UnistylesRuntime::updateTheme(jsi::Runtime& rt, std::string fnName) {
return HOST_FN(fnName, 1, {
std::string themeName = arguments[0].asString(rt).utf8(rt);

if (this->themeName == themeName) {
this->onThemeChangeCallback(themeName);
}

return jsi::Value::undefined();
});
}

jsi::Value UnistylesRuntime::useAdaptiveThemes(jsi::Runtime& rt, std::string fnName) {
return HOST_FN(fnName, 1, {
bool enableAdaptiveThemes = arguments[0].asBool();

if (enableAdaptiveThemes && this->colorScheme == UnistylesUnspecifiedScheme) {
throw jsi::JSError(rt, UnistylesErrorAdaptiveThemesNotSupported);
}

this->hasAdaptiveThemes = enableAdaptiveThemes;

if (!enableAdaptiveThemes || !this->supportsAutomaticColorScheme) {
return jsi::Value::undefined();
}

if (this->themeName != this->colorScheme) {
this->themeName = this->colorScheme;
this->onThemeChangeCallback(this->themeName);
}

return jsi::Value::undefined();
});
}

jsi::Value UnistylesRuntime::addPlugin(jsi::Runtime& rt, std::string fnName) {
return HOST_FN(fnName, 1, {
std::string pluginName = arguments[0].asString(rt).utf8(rt);
bool notify = arguments[1].asBool();

this->pluginNames.push_back(pluginName);

// registry enabled plugins won't notify listeners
if (notify) {
this->onPluginChangeCallback();
}

return jsi::Value::undefined();
});
}

jsi::Value UnistylesRuntime::removePlugin(jsi::Runtime& rt, std::string fnName) {
return HOST_FN(fnName, 1, {
std::string pluginName = arguments[0].asString(rt).utf8(rt);

auto it = std::find(this->pluginNames.begin(), this->pluginNames.end(), pluginName);

if (it != this->pluginNames.end()) {
this->pluginNames.erase(it);
this->onPluginChangeCallback();
}

return jsi::Value::undefined();
});
}

jsi::Value UnistylesRuntime::getEnabledPlugins(jsi::Runtime& rt, std::string fnName) {
auto jsiArray = facebook::jsi::Array(rt, this->pluginNames.size());

for (size_t i = 0; i < this->pluginNames.size(); i++) {
jsiArray.setValueAtIndex(rt, i, facebook::jsi::String::createFromUtf8(rt, this->pluginNames[i]));
}

return jsiArray;
}

jsi::Value UnistylesRuntime::getInsets(jsi::Runtime& rt, std::string fnName) {
auto insets = jsi::Object(rt);

insets.setProperty(rt, "top", this->insets.top);
insets.setProperty(rt, "bottom", this->insets.bottom);
insets.setProperty(rt, "left", this->insets.left);
insets.setProperty(rt, "right", this->insets.right);

return insets;
}

jsi::Value UnistylesRuntime::getStatusBar(jsi::Runtime& rt, std::string fnName) {
auto statusBar = jsi::Object(rt);
auto setStatusBarColorFunction = HOST_FN("setColor", 1, {
std::string color = arguments[0].asString(rt).utf8(rt);

if (this->onSetStatusBarColorCallback.has_value()) {
this->onSetStatusBarColorCallback.value()(color);
}

return jsi::Value::undefined();
});

statusBar.setProperty(rt, "width", this->statusBar.width);
statusBar.setProperty(rt, "height", this->statusBar.height);
statusBar.setProperty(rt, "setColor", setStatusBarColorFunction);

return statusBar;
}

jsi::Value UnistylesRuntime::getNavigationBar(jsi::Runtime& rt, std::string fnName) {
auto navigationBarValue = jsi::Object(rt);
auto setNavigationBarColorFunction = HOST_FN("setColor", 1, {
std::string color = arguments[0].asString(rt).utf8(rt);

if (this->onSetStatusBarColorCallback.has_value()) {
this->onSetNavigationBarColorCallback.value()(color);
}

return jsi::Value::undefined();
});

navigationBarValue.setProperty(rt, "width", this->navigationBar.width);
navigationBarValue.setProperty(rt, "height", this->navigationBar.height);
navigationBarValue.setProperty(rt, "setColor", setNavigationBarColorFunction);

return navigationBarValue;
}

std::optional<jsi::Value> UnistylesRuntime::setThemes(jsi::Runtime& rt, const jsi::Value& value) {
jsi::Array themes = value.asObject(rt).asArray(rt);
std::vector<std::string> themesVector;
size_t length = themes.size(rt);

for (size_t i = 0; i < length; ++i) {
jsi::Value element = themes.getValueAtIndex(rt, i);

if (element.isString()) {
std::string theme = element.asString(rt).utf8(rt);
themesVector.push_back(theme);
}
}

if (themesVector.size() == 0) {
throw jsi::JSError(rt, UnistylesErrorThemesCannotBeEmpty);
}

this->themes = themesVector;
this->themeName = "";

bool hasLightTheme = std::find(themesVector.begin(), themesVector.end(), "light") != themesVector.end();
bool hasDarkTheme = std::find(themesVector.begin(), themesVector.end(), "dark") != themesVector.end();

this->supportsAutomaticColorScheme = hasLightTheme && hasDarkTheme;

return std::nullopt;
}
92 changes: 92 additions & 0 deletions cxx/UnistylesModel.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#include "UnistylesModel.h"

std::string UnistylesModel::getBreakpointFromScreenWidth(int width, const std::vector<std::pair<std::string, double>>& sortedBreakpointPairs) {
for (size_t i = 0; i < sortedBreakpointPairs.size(); ++i) {
const auto& [key, value] = sortedBreakpointPairs[i];
const double maxVal = (i + 1 < sortedBreakpointPairs.size()) ? sortedBreakpointPairs[i + 1].second : std::numeric_limits<double>::infinity();

if (width >= value && width < maxVal) {
return key;
}
}

return sortedBreakpointPairs.empty() ? "" : sortedBreakpointPairs.back().first;
}

void UnistylesModel::handleScreenSizeChange(Dimensions& screen, Insets& insets, Dimensions& statusBar, Dimensions& navigationBar) {
std::string breakpoint = this->getBreakpointFromScreenWidth(screen.width, this->sortedBreakpointPairs);
bool hasDifferentBreakpoint = this->breakpoint != breakpoint;
bool hasDifferentScreenDimensions = this->screen.width != screen.width || this->screen.height != screen.height;
bool hasDifferentInsets = this->insets.top != insets.top || this->insets.bottom != insets.bottom || this->insets.left != insets.left || this->insets.right != insets.right;

// we don't need to check statusBar/navigationBar as they will only change on orientation change witch is equal to hasDifferentScreenDimensions
bool shouldNotify = hasDifferentBreakpoint || hasDifferentScreenDimensions || hasDifferentInsets;

this->breakpoint = breakpoint;
this->screen = {screen.width, screen.height};
this->insets = {insets.top, insets.bottom, insets.left, insets.right};
this->statusBar = {statusBar.width, statusBar.height};
this->navigationBar = {navigationBar.width, navigationBar.height};

std::string orientation = screen.width > screen.height
? UnistylesOrientationLandscape
: UnistylesOrientationPortrait;

if (shouldNotify) {
this->onLayoutChangeCallback(breakpoint, orientation, screen, statusBar, insets, navigationBar);
}
}

void UnistylesModel::handleAppearanceChange(std::string colorScheme) {
this->colorScheme = colorScheme;

if (!this->supportsAutomaticColorScheme || !this->hasAdaptiveThemes) {
return;
}

if (this->themeName != this->colorScheme) {
this->onThemeChangeCallback(this->colorScheme);
this->themeName = this->colorScheme;
}
}

void UnistylesModel::handleContentSizeCategoryChange(std::string contentSizeCategory) {
this->contentSizeCategory = contentSizeCategory;
this->onContentSizeCategoryChangeCallback(contentSizeCategory);
}

jsi::Value UnistylesModel::getThemeOrFail(jsi::Runtime& runtime) {
if (this->themes.size() == 1) {
std::string themeName = this->themes.at(0);

this->themeName = themeName;

return jsi::String::createFromUtf8(runtime, themeName);
}

return jsi::Value().undefined();
}

std::vector<std::pair<std::string, double>> UnistylesModel::toSortedBreakpointPairs(jsi::Runtime& rt, jsi::Object& breakpointsObj) {
jsi::Array propertyNames = breakpointsObj.getPropertyNames(rt);
std::vector<std::pair<std::string, double>> sortedBreakpointEntriesVec;

for (size_t i = 0; i < propertyNames.size(rt); ++i) {
jsi::Value propNameValue = propertyNames.getValueAtIndex(rt, i);
std::string name = propNameValue.asString(rt).utf8(rt);
jsi::PropNameID propNameID = jsi::PropNameID::forUtf8(rt, name);
jsi::Value value = breakpointsObj.getProperty(rt, propNameID);

if (value.isNumber()) {
double breakpointValue = value.asNumber();

sortedBreakpointEntriesVec.push_back(std::make_pair(name, breakpointValue));
}
}

std::sort(sortedBreakpointEntriesVec.begin(), sortedBreakpointEntriesVec.end(), [](const std::pair<std::string, double>& a, const std::pair<std::string, double>& b) {
return a.second < b.second;
});

return sortedBreakpointEntriesVec;
}
Loading

0 comments on commit b514171

Please sign in to comment.