Skip to content

Commit 41e7cda

Browse files
committed
feat: add environment
1 parent ee15cf4 commit 41e7cda

7 files changed

Lines changed: 260 additions & 13 deletions

File tree

cpp/Environment.cpp

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// filepath: /Users/mark/Developer/react-native-css-nitro/cpp/Environment.cpp
2+
#include "Environment.hpp"
3+
4+
namespace reactnativecss {
5+
namespace env {
6+
7+
static std::shared_ptr<reactnativecss::Observable<double>> &widthRef() {
8+
static auto inst = reactnativecss::Observable<double>::create(0.0);
9+
return inst;
10+
}
11+
12+
static std::shared_ptr<reactnativecss::Observable<double>> &heightRef() {
13+
static auto inst = reactnativecss::Observable<double>::create(0.0);
14+
return inst;
15+
}
16+
17+
static std::shared_ptr<reactnativecss::Observable<double>> &scaleRef() {
18+
static auto inst = reactnativecss::Observable<double>::create(0.0);
19+
return inst;
20+
}
21+
22+
static std::shared_ptr<reactnativecss::Observable<double>> &fontScaleRef() {
23+
static auto inst = reactnativecss::Observable<double>::create(0.0);
24+
return inst;
25+
}
26+
27+
reactnativecss::Observable<double> &windowWidth() { return *widthRef(); }
28+
29+
reactnativecss::Observable<double> &windowHeight() { return *heightRef(); }
30+
31+
reactnativecss::Observable<double> &windowScale() { return *scaleRef(); }
32+
33+
reactnativecss::Observable<double> &windowFontScale() { return *fontScaleRef(); }
34+
35+
void setWindowDimensions(double width, double height, double scale, double fontScale) {
36+
widthRef()->set(width);
37+
heightRef()->set(height);
38+
scaleRef()->set(scale);
39+
fontScaleRef()->set(fontScale);
40+
}
41+
42+
} // namespace env
43+
} // namespace reactnativecss
44+

cpp/Environment.hpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#pragma once
2+
3+
#include "Observable.hpp"
4+
5+
namespace reactnativecss {
6+
namespace env {
7+
8+
// Accessors to global environment observables. These are lightweight and
9+
// can be used with Effect::GetProxy for reactive reads from anywhere.
10+
11+
reactnativecss::Observable<double> &windowWidth();
12+
13+
reactnativecss::Observable<double> &windowHeight();
14+
15+
reactnativecss::Observable<double> &windowScale();
16+
17+
reactnativecss::Observable<double> &windowFontScale();
18+
19+
// Convenience API to update all four metrics in one shot.
20+
void setWindowDimensions(double width, double height, double scale, double fontScale);
21+
22+
} // namespace env
23+
} // namespace reactnativecss
24+

cpp/HybridStyleRegistry.cpp

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include "Helpers.hpp"
55
#include "ShadowTreeUpdateManager.hpp"
66
#include "StyledComputedFactory.hpp"
7+
#include "Environment.hpp"
78

89
#include <regex>
910
#include <string>
@@ -52,20 +53,19 @@ namespace margelo::nitro::cssnitro {
5253
const jsi::Value &thisValue,
5354
const jsi::Value *args, size_t count);
5455

56+
void setWindowDimensions(double width, double height, double scale, double fontScale);
57+
5558
private:
5659
// Per-component link info and update source
5760
struct ComponentLink {
5861
facebook::react::Tag tag{0};
5962
jsi::Runtime *runtime{nullptr};
6063
std::shared_ptr<reactnativecss::Observable<folly::dynamic>> updates;
6164
};
62-
// componentId -> {tag, runtime*, updates-observable}
63-
std::unordered_map<std::string, ComponentLink> component_linking_;
6465

65-
// Per-runtime updates observable: Tag -> payload
66+
std::unordered_map<std::string, ComponentLink> component_linking_;
6667
using UpdatesMap = std::unordered_map<facebook::react::Tag, folly::dynamic>;
6768
std::unordered_map<jsi::Runtime *, std::shared_ptr<reactnativecss::Observable<UpdatesMap>>> runtime_updates_;
68-
// Per-runtime effect (kept alive)
6969
std::unordered_map<jsi::Runtime *, std::shared_ptr<reactnativecss::Computed<bool>>> runtime_effects_;
7070

7171
public:
@@ -75,9 +75,7 @@ namespace margelo::nitro::cssnitro {
7575
static std::shared_ptr<Impl> get();
7676

7777
private:
78-
// Manager that owns per-runtime effects and updates batching
7978
std::unique_ptr<ShadowTreeUpdateManager> shadowUpdates_;
80-
8179
std::unordered_map<std::string, std::shared_ptr<reactnativecss::Computed<Styled>>> computedMap_;
8280
std::unordered_map<std::string, std::shared_ptr<reactnativecss::Observable<StyleRule>>> styleRuleMap_;
8381
};
@@ -131,6 +129,11 @@ namespace margelo::nitro::cssnitro {
131129
impl_->unlinkComponent(componentId);
132130
}
133131

132+
void HybridStyleRegistry::setWindowDimensions(double width, double height, double scale,
133+
double fontScale) {
134+
impl_->setWindowDimensions(width, height, scale, fontScale);
135+
}
136+
134137
jsi::Value
135138
HybridStyleRegistry::linkComponent(jsi::Runtime &runtime, const jsi::Value &thisValue,
136139
const jsi::Value *args, size_t count) {
@@ -271,6 +274,11 @@ namespace margelo::nitro::cssnitro {
271274
shadowUpdates_->unlinkComponent(componentId);
272275
}
273276

277+
void HybridStyleRegistry::Impl::setWindowDimensions(double width, double height, double scale,
278+
double fontScale) {
279+
reactnativecss::env::setWindowDimensions(width, height, scale, fontScale);
280+
}
281+
274282
jsi::Value HybridStyleRegistry::Impl::registerExternalMethods(
275283
jsi::Runtime &runtime, const jsi::Value &thisValue, const jsi::Value *args,
276284
size_t count) {

cpp/HybridStyleRegistry.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ namespace margelo::nitro::cssnitro {
5858
void updateComponentInlineStyleKeys(const std::string &componentId,
5959
const std::vector<std::string> &inlineStyleKeys) override;
6060

61+
void
62+
setWindowDimensions(double width, double height, double scale, double fontScale) override;
63+
6164
protected:
6265
void loadHybridMethods() override;
6366

cpp/Rules.hpp

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
#pragma once
2+
3+
#include <memory>
4+
#include <utility>
5+
#include <type_traits>
6+
#include <vector>
7+
#include <string>
8+
#include <optional>
9+
#include <algorithm>
10+
#include <cctype>
11+
12+
#include "Effect.hpp"
13+
#include "StyleRule.hpp"
14+
#include "Environment.hpp"
15+
16+
namespace margelo::nitro::cssnitro {
17+
18+
// Trait to detect std::vector<T, Alloc>
19+
template<typename T>
20+
struct is_std_vector : std::false_type {
21+
};
22+
template<typename T, typename Alloc>
23+
struct is_std_vector<std::vector<T, Alloc>> : std::true_type {
24+
};
25+
26+
class Rules {
27+
public:
28+
static bool testRule(const StyleRule &rule, reactnativecss::Effect::GetProxy &get) {
29+
if (rule.m && !rule.m->empty() && !testMediaQuery(*rule.m, get)) {
30+
return false;
31+
}
32+
return true;
33+
}
34+
35+
template<class Cond>
36+
static bool
37+
testMediaQuery(const std::vector<Cond> &media, reactnativecss::Effect::GetProxy &get) {
38+
for (const auto &cond: media) {
39+
if (!testMediaConditionDispatch(cond, get)) {
40+
return false;
41+
}
42+
}
43+
return true;
44+
}
45+
46+
template<class L, class R>
47+
static bool testMediaComparison(const std::string &op, const L &left, const R &right,
48+
reactnativecss::Effect::GetProxy &get) {
49+
auto lhs = toNumber(left, get);
50+
auto rhs = toNumber(right, get);
51+
if (!lhs.has_value() || !rhs.has_value()) {
52+
return false;
53+
}
54+
const double a = *lhs;
55+
const double b = *rhs;
56+
if (op == "=") return nearlyEqual(a, b);
57+
if (op == ">") return a > b;
58+
if (op == ">=") return a >= b || nearlyEqual(a, b);
59+
if (op == "<") return a < b;
60+
if (op == "<=") return a <= b || nearlyEqual(a, b);
61+
return false;
62+
}
63+
64+
template<class Elem>
65+
static bool testMediaCondition(const std::vector<Elem> &condition,
66+
reactnativecss::Effect::GetProxy &get) {
67+
if (condition.empty()) return true;
68+
const std::string &op = condition[0];
69+
if (op == "[]" || op == "!!") return false;
70+
if (op == "=" || op == ">" || op == ">=" || op == "<" || op == "<=") {
71+
if (condition.size() >= 3) {
72+
const auto &left = condition[1];
73+
const auto &right = condition[2];
74+
return testMediaComparison(op, left, right, get);
75+
}
76+
return false;
77+
}
78+
return false;
79+
}
80+
81+
private:
82+
static bool nearlyEqual(double a, double b, double eps = 1e-9) {
83+
return std::abs(a - b) <= eps * std::max(1.0, std::max(std::abs(a), std::abs(b)));
84+
}
85+
86+
static std::string lower(std::string s) {
87+
std::transform(s.begin(), s.end(), s.begin(),
88+
[](unsigned char c) { return (char) std::tolower(c); });
89+
return s;
90+
}
91+
92+
static std::optional<double>
93+
tokenToNumberFromEnv(const std::string &tok, reactnativecss::Effect::GetProxy &get) {
94+
const auto t = lower(tok);
95+
if (t == "w" || t == "width" || t == "screenwidth") {
96+
return get(reactnativecss::env::windowWidth());
97+
}
98+
if (t == "h" || t == "height" || t == "screenheight") {
99+
return get(reactnativecss::env::windowHeight());
100+
}
101+
if (t == "scale" || t == "dpr" || t == "pixelratio") {
102+
return get(reactnativecss::env::windowScale());
103+
}
104+
if (t == "fontscale" || t == "fs") {
105+
return get(reactnativecss::env::windowFontScale());
106+
}
107+
return std::nullopt;
108+
}
109+
110+
static std::optional<double> parseNumberString(const std::string &s) {
111+
size_t start = 0, end = s.size();
112+
while (start < end && std::isspace((unsigned char) s[start])) ++start;
113+
while (end > start && std::isspace((unsigned char) s[end - 1])) --end;
114+
if (start >= end) return std::nullopt;
115+
size_t i = start;
116+
if (s[i] == '+' || s[i] == '-') ++i;
117+
bool dot = false;
118+
while (i < end) {
119+
char c = s[i];
120+
if (std::isdigit((unsigned char) c)) ++i;
121+
else if (c == '.' && !dot) {
122+
dot = true;
123+
++i;
124+
}
125+
else break;
126+
}
127+
if (i == start || (i == start + 1 && (s[start] == '+' || s[start] == '-')))
128+
return std::nullopt;
129+
try { return std::stod(std::string{s.begin() + (long) start, s.begin() + (long) i}); }
130+
catch (...) { return std::nullopt; }
131+
}
132+
133+
template<class T>
134+
static std::optional<double>
135+
toNumber(const T &value, reactnativecss::Effect::GetProxy &get) {
136+
using Decayed = std::decay_t<T>;
137+
if constexpr (std::is_arithmetic_v<Decayed>) {
138+
return static_cast<double>(value);
139+
} else if constexpr (std::is_same_v<Decayed, bool>) {
140+
return value ? 1.0 : 0.0;
141+
} else if constexpr (std::is_same_v<Decayed, std::string>) {
142+
if (auto env = tokenToNumberFromEnv(value, get)) return env;
143+
return parseNumberString(value);
144+
} else if constexpr (std::is_same_v<Decayed, const char *>) {
145+
return toNumber(std::string(value), get);
146+
} else {
147+
return std::nullopt;
148+
}
149+
}
150+
151+
template<class Cond>
152+
static bool
153+
testMediaConditionDispatch(const Cond &cond, reactnativecss::Effect::GetProxy &get) {
154+
if constexpr (is_std_vector<std::decay_t<Cond>>::value) {
155+
return testMediaCondition(cond, get);
156+
} else {
157+
return true;
158+
}
159+
}
160+
161+
};
162+
163+
} // namespace margelo::nitro::cssnitro

cpp/StyledComputedFactory.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "StyledComputedFactory.hpp"
22
#include "Styled+Equality.hpp"
33
#include "ShadowTreeUpdateManager.hpp"
4+
#include "Rules.hpp"
45

56
#include <regex>
67
#include <variant>
@@ -44,6 +45,11 @@ namespace margelo::nitro::cssnitro {
4445

4546
const StyleRule &styleRule = get(*styleIt->second);
4647

48+
// Skip rule if its media conditions don't pass
49+
if (!Rules::testRule(styleRule, get)) {
50+
continue;
51+
}
52+
4753
if (styleRule.d.has_value()) {
4854
std::vector<std::variant<std::shared_ptr<AnyMap>, std::vector<std::shared_ptr<AnyMap>>>> stack(
4955
styleRule.d->begin(), styleRule.d->end());

src/specs/StyleRegistry/HybridStyleRegistry.nitro.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,12 @@ export interface StyleRegistry
3434
): void;
3535
unlinkComponent(componentId: string): void;
3636

37-
/** Computed values */
38-
// setWindowDimensions(
39-
// width: number,
40-
// height: number,
41-
// scale: number,
42-
// fontScale: number
43-
// ): void;
37+
setWindowDimensions(
38+
width: number,
39+
height: number,
40+
scale: number,
41+
fontScale: number
42+
): void;
4443
}
4544

4645
/**

0 commit comments

Comments
 (0)