Skip to content

Commit 87ed8ff

Browse files
committed
feat: add MediaQuery
1 parent 9e22df4 commit 87ed8ff

10 files changed

Lines changed: 184 additions & 158 deletions

File tree

cpp/HybridStyleRegistry.cpp

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ namespace margelo::nitro::cssnitro {
2828
struct HybridStyleRegistry::Impl {
2929
Impl();
3030

31-
void set(const std::string &className, const std::vector<StyleRule> &styleRule);
31+
void set(const std::string &className, const std::vector<HybridStyleRule> &styleRule);
3232

3333
Declarations
3434
getDeclarations(const std::string &componentId, const std::string &classNames,
@@ -77,7 +77,7 @@ namespace margelo::nitro::cssnitro {
7777
private:
7878
std::unique_ptr<ShadowTreeUpdateManager> shadowUpdates_;
7979
std::unordered_map<std::string, std::shared_ptr<reactnativecss::Computed<Styled>>> computedMap_;
80-
std::unordered_map<std::string, std::shared_ptr<reactnativecss::Observable<std::vector<StyleRule>>>> styleRuleMap_;
80+
std::unordered_map<std::string, std::shared_ptr<reactnativecss::Observable<std::vector<HybridStyleRule>>>> styleRuleMap_;
8181
};
8282

8383

@@ -97,7 +97,7 @@ namespace margelo::nitro::cssnitro {
9797
HybridStyleRegistry::~HybridStyleRegistry() = default;
9898

9999
void HybridStyleRegistry::set(const std::string &className,
100-
const std::vector<StyleRule> &styleRule) {
100+
const std::vector<HybridStyleRule> &styleRule) {
101101
impl_->set(className, styleRule);
102102
}
103103

@@ -169,10 +169,11 @@ namespace margelo::nitro::cssnitro {
169169
}
170170

171171
void HybridStyleRegistry::Impl::set(const std::string &className,
172-
const std::vector<StyleRule> &styleRule) {
172+
const std::vector<HybridStyleRule> &styleRule) {
173173
auto it = styleRuleMap_.find(className);
174174
if (it == styleRuleMap_.end()) {
175-
auto observable = reactnativecss::Observable<std::vector<StyleRule>>::create(styleRule);
175+
auto observable = reactnativecss::Observable<std::vector<HybridStyleRule>>::create(
176+
styleRule);
176177
styleRuleMap_.emplace(className, std::move(observable));
177178
} else if (it->second) {
178179
it->second->set(styleRule);
@@ -203,7 +204,7 @@ namespace margelo::nitro::cssnitro {
203204
continue;
204205
}
205206

206-
const std::vector<StyleRule> &styleRules = styleIt->second->get();
207+
const std::vector<HybridStyleRule> &styleRules = styleIt->second->get();
207208
bool hasVars = false;
208209
for (const auto &sr: styleRules) {
209210
if (sr.v.has_value()) {

cpp/HybridStyleRegistry.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
#include "HybridStyleRegistrySpec.hpp"
44
#include "Observable.hpp"
5-
#include "StyleRule+Equality.hpp"
5+
#include "HybridStyleRule+Equality.hpp"
66
#include "Styled+Equality.hpp"
77

88
#include <cstddef>
@@ -37,7 +37,7 @@ namespace margelo::nitro::cssnitro {
3737

3838
// ----- public API forwarded to Impl -----
3939
void set(const std::string &className,
40-
const std::vector<StyleRule> &styleRule) override;
40+
const std::vector<HybridStyleRule> &styleRule) override;
4141

4242
Declarations getDeclarations(const std::string &componentId, const std::string &classNames,
4343
const std::string &variableScope,
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
#pragma once
22

3-
#include "StyleRule.hpp"
3+
#include "HybridStyleRule.hpp"
44

55
namespace margelo::nitro::cssnitro {
6-
inline bool operator==(const StyleRule &lhs, const StyleRule &rhs) {
6+
inline bool operator==(const HybridStyleRule &lhs, const HybridStyleRule &rhs) {
77
return false;
88
}
99
} // namespace margelo::nitro::cssnitro

cpp/Rules.hpp

Lines changed: 152 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -11,145 +11,195 @@
1111
#include <cmath>
1212

1313
#include "Effect.hpp"
14-
#include "StyleRule.hpp"
14+
#include "HybridStyleRule.hpp"
1515
#include "Environment.hpp"
1616
#include "Helpers.hpp"
17+
#include <NitroModules/AnyMap.hpp>
1718

1819
namespace margelo::nitro::cssnitro {
1920

20-
// Trait to detect std::vector<T, Alloc>
21-
template<typename T>
22-
struct is_std_vector : std::false_type {
23-
};
24-
template<typename T, typename Alloc>
25-
struct is_std_vector<std::vector<T, Alloc>> : std::true_type {
26-
};
21+
using AnyMap = ::margelo::nitro::AnyMap;
22+
using AnyArray = ::margelo::nitro::AnyArray;
23+
using AnyValue = ::margelo::nitro::AnyValue;
2724

2825
class Rules {
2926
public:
30-
static bool testRule(const StyleRule &rule, reactnativecss::Effect::GetProxy &get) {
31-
if (rule.m && !rule.m->empty() && !testMediaQuery(*rule.m, get)) {
32-
return false;
27+
static bool testRule(const HybridStyleRule &rule, reactnativecss::Effect::GetProxy &get) {
28+
// If m is not defined, return true
29+
if (!rule.m.has_value() || !rule.m.value()) {
30+
return true;
3331
}
34-
return true;
35-
}
3632

37-
template<class Cond>
38-
static bool
39-
testMediaQuery(const std::vector<Cond> &media, reactnativecss::Effect::GetProxy &get) {
40-
for (const auto &cond: media) {
41-
if (!testMediaConditionDispatch(cond, get)) {
42-
return false;
43-
}
33+
auto &mediaMap = *rule.m.value();
34+
35+
// Get all keys to check if empty
36+
auto keys = mediaMap.getAllKeys();
37+
if (keys.empty()) {
38+
return true;
4439
}
45-
return true;
46-
}
4740

48-
template<class L, class R>
49-
static bool testMediaComparison(const std::string &op, const L &left, const R &right,
50-
reactnativecss::Effect::GetProxy &get) {
51-
using helpers::lower;
52-
using helpers::toNumber;
53-
using helpers::toString;
41+
// Check for $$op to determine logic mode
42+
std::string logicOp = "and"; // default is "and"
43+
bool negate = false;
5444

55-
// Accessors for environment
56-
auto getWidth = [&]() -> double { return get(reactnativecss::env::windowWidth()); };
57-
auto getHeight = [&]() -> double { return get(reactnativecss::env::windowHeight()); };
45+
if (mediaMap.contains("$$op")) {
46+
if (mediaMap.isString("$$op")) {
47+
logicOp = mediaMap.getString("$$op");
48+
if (logicOp == "not") {
49+
negate = true;
50+
logicOp = "and"; // "not" just negates the result, logic is still "and"
51+
}
52+
}
53+
}
5854

59-
// Special cases when left is a string keyword
60-
if (auto leftStr = toString(left)) {
61-
const std::string key = lower(*leftStr);
55+
// Track test results
56+
std::vector<bool> results;
6257

63-
if (key == "min-width") {
64-
auto v = toNumber(right);
65-
return v.has_value() && getWidth() >= *v;
66-
}
67-
if (key == "max-width") {
68-
auto v = toNumber(right);
69-
return v.has_value() && getWidth() <= *v;
58+
// Loop over all keys
59+
for (const auto &key: keys) {
60+
// Skip the $$op key
61+
if (key == "$$op") {
62+
continue;
7063
}
71-
if (key == "min-height") {
72-
auto v = toNumber(right);
73-
return v.has_value() && getHeight() >= *v;
64+
65+
// Value should be an array with [operator, expectedValue]
66+
if (!mediaMap.isArray(key)) {
67+
continue;
7468
}
75-
if (key == "max-height") {
76-
auto v = toNumber(right);
77-
return v.has_value() && getHeight() <= *v;
69+
70+
AnyArray valueArray = mediaMap.getArray(key);
71+
if (valueArray.size() < 2) {
72+
continue;
7873
}
79-
if (key == "orientation") {
80-
auto vStr = toString(right);
81-
const double w = getWidth();
82-
const double h = getHeight();
83-
return vStr.has_value() && lower(*vStr) == "landscape" ? (h < w) : (h >= w);
74+
75+
// Extract operator and expected value
76+
std::string op;
77+
if (std::holds_alternative<std::string>(valueArray[0])) {
78+
op = std::get<std::string>(valueArray[0]);
8479
}
80+
81+
bool testResult = testMediaQuery(key, op, valueArray[1], get);
82+
results.push_back(testResult);
8583
}
8684

87-
// For general comparisons, right must be numeric
88-
auto rightValOpt = toNumber(right);
89-
if (!rightValOpt.has_value()) {
90-
return false;
85+
// If no tests were run, return true
86+
if (results.empty()) {
87+
return true;
9188
}
9289

93-
// Left can be numeric or a string referring to width/height
94-
std::optional<double> leftValOpt = toNumber(left);
95-
if (!leftValOpt.has_value()) {
96-
if (auto leftStr = toString(left)) {
97-
const std::string key = lower(*leftStr);
98-
if (key == "width") {
99-
leftValOpt = getWidth();
100-
} else if (key == "height") {
101-
leftValOpt = getHeight();
102-
} else {
103-
return false;
90+
// Apply logic
91+
bool finalResult;
92+
if (logicOp == "or") {
93+
// "or" - at least one must pass
94+
finalResult = false;
95+
for (bool result: results) {
96+
if (result) {
97+
finalResult = true;
98+
break;
99+
}
100+
}
101+
} else {
102+
// "and" - all must pass (default)
103+
finalResult = true;
104+
for (bool result: results) {
105+
if (!result) {
106+
finalResult = false;
107+
break;
104108
}
105-
} else {
106-
return false;
107109
}
108110
}
109111

110-
const double a = *leftValOpt;
111-
const double b = *rightValOpt;
112+
// Apply negation if needed
113+
if (negate) {
114+
finalResult = !finalResult;
115+
}
112116

113-
if (op == "=") return nearlyEqual(a, b);
114-
if (op == ">") return a > b;
115-
if (op == ">=") return a > b || nearlyEqual(a, b);
116-
if (op == "<") return a < b;
117-
if (op == "<=") return a < b || nearlyEqual(a, b);
118-
return false;
117+
return finalResult;
119118
}
120119

121-
template<class Elem>
122-
static bool testMediaCondition(const std::vector<Elem> &condition,
123-
reactnativecss::Effect::GetProxy &get) {
124-
if (condition.empty()) return true;
125-
const std::string &op = condition[0];
126-
if (op == "[]" || op == "!!") return false;
127-
if (op == "=" || op == ">" || op == ">=" || op == "<" || op == "<=") {
128-
if (condition.size() >= 3) {
129-
const auto &left = condition[1];
130-
const auto &right = condition[2];
131-
return testMediaComparison(op, left, right, get);
120+
private:
121+
static bool
122+
testMediaQuery(const std::string &key, const std::string &op, const AnyValue &value,
123+
reactnativecss::Effect::GetProxy &get) {
124+
if (op == "=") {
125+
if (key == "min-width") {
126+
if (std::holds_alternative<double>(value)) {
127+
double vw = get(reactnativecss::env::windowWidth());
128+
return vw >= std::get<double>(value);
129+
}
130+
return false;
131+
}
132+
if (key == "max-width") {
133+
if (std::holds_alternative<double>(value)) {
134+
double vw = get(reactnativecss::env::windowWidth());
135+
return vw <= std::get<double>(value);
136+
}
137+
return false;
138+
}
139+
if (key == "min-height") {
140+
if (std::holds_alternative<double>(value)) {
141+
double vh = get(reactnativecss::env::windowHeight());
142+
return vh >= std::get<double>(value);
143+
}
144+
return false;
145+
}
146+
if (key == "max-height") {
147+
if (std::holds_alternative<double>(value)) {
148+
double vh = get(reactnativecss::env::windowHeight());
149+
return vh <= std::get<double>(value);
150+
}
151+
return false;
152+
}
153+
if (key == "orientation") {
154+
if (std::holds_alternative<std::string>(value)) {
155+
std::string orientation = std::get<std::string>(value);
156+
double vw = get(reactnativecss::env::windowWidth());
157+
double vh = get(reactnativecss::env::windowHeight());
158+
if (orientation == "landscape") {
159+
return vh < vw;
160+
} else {
161+
return vh >= vw;
162+
}
163+
}
164+
return false;
132165
}
133-
return false;
134166
}
135-
return false;
136-
}
137167

138-
private:
139-
static bool nearlyEqual(double a, double b, double eps = 1e-9) {
140-
return std::abs(a - b) <= eps * std::max(1.0, std::max(std::abs(a), std::abs(b)));
141-
}
168+
// For other operators, value must be a number
169+
if (!std::holds_alternative<double>(value)) {
170+
return false;
171+
}
142172

143-
template<class Cond>
144-
static bool
145-
testMediaConditionDispatch(const Cond &cond, reactnativecss::Effect::GetProxy &get) {
146-
if constexpr (is_std_vector<std::decay_t<Cond>>::value) {
147-
return testMediaCondition(cond, get);
173+
double right = std::get<double>(value);
174+
double left = 0.0;
175+
176+
// Determine left value based on key and fetch only what's needed
177+
if (key == "width") {
178+
left = get(reactnativecss::env::windowWidth());
179+
} else if (key == "height") {
180+
left = get(reactnativecss::env::windowHeight());
181+
} else if (key == "resolution") {
182+
// TODO: Need to get PixelRatio - for now return 1.0
183+
left = 1.0; // PixelRatio.get()
148184
} else {
149-
return true;
185+
return false;
150186
}
151-
}
152187

188+
// Apply operator
189+
if (op == "=") {
190+
return left == right;
191+
} else if (op == ">") {
192+
return left > right;
193+
} else if (op == ">=") {
194+
return left >= right;
195+
} else if (op == "<") {
196+
return left < right;
197+
} else if (op == "<=") {
198+
return left <= right;
199+
}
200+
201+
return false;
202+
}
153203
};
154204

155205
} // namespace margelo::nitro::cssnitro

cpp/StyledComputedFactory.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ namespace margelo::nitro::cssnitro {
1515
using AnyMap = ::margelo::nitro::AnyMap;
1616

1717
std::shared_ptr<reactnativecss::Computed<Styled>> makeStyledComputed(
18-
const std::unordered_map<std::string, std::shared_ptr<reactnativecss::Observable<std::vector<StyleRule>>>> &styleRuleMap,
18+
const std::unordered_map<std::string, std::shared_ptr<reactnativecss::Observable<std::vector<HybridStyleRule>>>> &styleRuleMap,
1919
const std::string &classNames,
2020
const std::string &componentId,
2121
ShadowTreeUpdateManager &shadowUpdates) {
@@ -43,9 +43,9 @@ namespace margelo::nitro::cssnitro {
4343
continue;
4444
}
4545

46-
const std::vector<StyleRule> &styleRules = get(*styleIt->second);
46+
const std::vector<HybridStyleRule> &styleRules = get(*styleIt->second);
4747

48-
for (const StyleRule &styleRule: styleRules) {
48+
for (const HybridStyleRule &styleRule: styleRules) {
4949
// Skip rule if its media conditions don't pass
5050
if (!Rules::testRule(styleRule, get)) {
5151
continue;

0 commit comments

Comments
 (0)