@react-native-gpui/screens 0.0.0-stage → 4.28.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/LICENSE +21 -0
- package/README.md +22 -2
- package/desktop/CMakeLists.txt +30 -0
- package/desktop/Cargo.lock +7781 -0
- package/desktop/Cargo.toml +54 -0
- package/desktop/build.rs +38 -0
- package/desktop/cpp/GpuiScreensModule.cpp +77 -0
- package/desktop/src/lib.rs +29 -0
- package/desktop/src/lifecycle.rs +80 -0
- package/desktop/src/props.rs +177 -0
- package/desktop/src/render.rs +829 -0
- package/desktop/src/stack.rs +396 -0
- package/desktop/src/state.rs +162 -0
- package/desktop/src/transition.rs +193 -0
- package/desktop/src/unsupported.rs +39 -0
- package/package.json +31 -4
- package/react-native.config.js +36 -0
- package/src/FullWindowOverlay.desktop.tsx +19 -0
- package/src/core.desktop.ts +28 -0
- package/src/customDirectEventTypes.desktop.ts +16 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "rn-gpui-screens"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
edition = "2021"
|
|
5
|
+
|
|
6
|
+
[dependencies]
|
|
7
|
+
react-native-gpui = "0.1"
|
|
8
|
+
rn-gpui-sys = "0.1"
|
|
9
|
+
gpui = "0.2"
|
|
10
|
+
serde_json = "1"
|
|
11
|
+
log = "0.4"
|
|
12
|
+
|
|
13
|
+
[dev-dependencies]
|
|
14
|
+
# `TestNode` and `TreeView::for_test` are gated behind `test-driver`, and a
|
|
15
|
+
# dependency cannot enable another package's feature. It is a dev-dependency
|
|
16
|
+
# rather than a normal one so a build of the module never pays for it, and it only
|
|
17
|
+
# becomes usable because this crate is its own workspace root below.
|
|
18
|
+
react-native-gpui = { version = "0.1", features = ["test-driver"] }
|
|
19
|
+
|
|
20
|
+
# Its own workspace root, deliberately. Cargo refuses to test a package that has
|
|
21
|
+
# dev-dependencies unless it is a workspace member, and core's workspace cannot
|
|
22
|
+
# hold it: a module resolves core by version and must not be a member of the
|
|
23
|
+
# checkout's workspace. A root with itself as the only member satisfies the rule
|
|
24
|
+
# without making the module part of anything else.
|
|
25
|
+
[workspace]
|
|
26
|
+
|
|
27
|
+
# The checkout's shared dev profile (see core's `ReactGpui/Cargo.toml`): line tables
|
|
28
|
+
# only, no debug info for dependencies, which are optimized. Every workspace root
|
|
29
|
+
# builds into the one root `target/`, so a matching profile is also what lets them
|
|
30
|
+
# reuse each other's dependency builds instead of storing one copy each.
|
|
31
|
+
[profile.dev]
|
|
32
|
+
debug = "line-tables-only"
|
|
33
|
+
split-debuginfo = "packed"
|
|
34
|
+
|
|
35
|
+
[profile.dev.package."*"]
|
|
36
|
+
debug = false
|
|
37
|
+
opt-level = 3
|
|
38
|
+
|
|
39
|
+
[profile.dev.package.rn-gpui-sys]
|
|
40
|
+
opt-level = 0
|
|
41
|
+
|
|
42
|
+
# This block has to live here because Cargo honours `[patch.crates-io]` only at a
|
|
43
|
+
# workspace root, and this crate is one. `npm run test:rust` regenerates it from
|
|
44
|
+
# core's manifest before running these tests and fails if it changed, so the gpui
|
|
45
|
+
# revision is never written down by hand — commit whatever the run rewrites.
|
|
46
|
+
[patch.crates-io]
|
|
47
|
+
# Managed by @react-native-gpui/cli autolink; do not edit between these markers.
|
|
48
|
+
react-native-gpui = { path = "../../../packages/core/ReactGpui/rn-gpui" }
|
|
49
|
+
rn-gpui-sys = { path = "../../../packages/core/ReactGpui/rn-gpui-sys" }
|
|
50
|
+
rn-gpui-module = { path = "../../../packages/core/ReactGpui/rn-gpui-module" }
|
|
51
|
+
rn-gpui-module-build = { path = "../../../packages/core/ReactGpui/rn-gpui-module-build" }
|
|
52
|
+
gpui = { git = "https://github.com/streetwriters/gpui-fast", rev = "48ef745" }
|
|
53
|
+
gpui_platform = { git = "https://github.com/streetwriters/gpui-fast", rev = "48ef745" }
|
|
54
|
+
# End managed by autolink.
|
package/desktop/build.rs
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
//! Puts the C++ runtime's shared libraries on this crate's test binary rpath.
|
|
2
|
+
//!
|
|
3
|
+
//! `rn-gpui-screens`'s tests build gpui trees, which link `libjsi.dylib` and the
|
|
4
|
+
//! rest of the Node-API runtime. Core's `build.rs` emits
|
|
5
|
+
//! `cargo:rustc-link-arg=-Wl,-rpath,<dir>` for its own binary — the app — and that
|
|
6
|
+
//! flag is not inherited by a dependency's test binary, so the test process
|
|
7
|
+
//! aborted in dyld with `Library not loaded: @rpath/libjsi.dylib` before running a
|
|
8
|
+
//! single test.
|
|
9
|
+
//!
|
|
10
|
+
//! Core exports the directories it was given as `DEP_RN_GPUI_CORE_RPATHS`, which
|
|
11
|
+
//! is the same variable core's own `build.rs` reads. Re-emitting it here scopes
|
|
12
|
+
//! the flag to this crate's test binary, since `cargo:rustc-link-arg` without a
|
|
13
|
+
//! target suffix applies to the crate's own targets and not to the app that
|
|
14
|
+
//! depends on it.
|
|
15
|
+
|
|
16
|
+
fn main() {
|
|
17
|
+
println!("cargo:rerun-if-env-changed=DEP_RN_GPUI_CORE_RPATHS");
|
|
18
|
+
println!("cargo:rerun-if-changed=build.rs");
|
|
19
|
+
|
|
20
|
+
let Ok(dirs) = std::env::var("DEP_RN_GPUI_CORE_RPATHS") else {
|
|
21
|
+
return;
|
|
22
|
+
};
|
|
23
|
+
let dirs = dirs
|
|
24
|
+
.split('|')
|
|
25
|
+
.filter(|dir| !dir.is_empty())
|
|
26
|
+
.collect::<Vec<_>>();
|
|
27
|
+
if dirs.is_empty() {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
// MSVC and MinGW have no rpath, and the flag is rejected outright; Windows
|
|
31
|
+
// finds the DLLs through PATH, which the test runner already arranges.
|
|
32
|
+
if std::env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("windows") {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
for dir in dirs {
|
|
36
|
+
println!("cargo:rustc-link-arg=-Wl,-rpath,{dir}");
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
|
|
2
|
+
#include "GpuiModules.h"
|
|
3
|
+
|
|
4
|
+
#include <folly/json.h>
|
|
5
|
+
#include <react/renderer/components/rnscreens/Props.h>
|
|
6
|
+
|
|
7
|
+
#include <string>
|
|
8
|
+
|
|
9
|
+
namespace rngpui {
|
|
10
|
+
namespace {
|
|
11
|
+
|
|
12
|
+
using namespace facebook::react;
|
|
13
|
+
|
|
14
|
+
std::string screenPropsJson(
|
|
15
|
+
const std::string& /*componentName*/,
|
|
16
|
+
const ShadowView& view) {
|
|
17
|
+
if (!view.props) {
|
|
18
|
+
return std::string("{}");
|
|
19
|
+
}
|
|
20
|
+
auto props = std::dynamic_pointer_cast<const RNSScreenProps>(view.props);
|
|
21
|
+
if (!props) {
|
|
22
|
+
return std::string("{}");
|
|
23
|
+
}
|
|
24
|
+
folly::dynamic object = folly::dynamic::object;
|
|
25
|
+
object["activityState"] = props->activityState;
|
|
26
|
+
object["stackAnimation"] = toString(props->stackAnimation);
|
|
27
|
+
object["stackPresentation"] = toString(props->stackPresentation);
|
|
28
|
+
return folly::toJson(object);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
std::string headerConfigPropsJson(
|
|
32
|
+
const std::string& /*componentName*/,
|
|
33
|
+
const ShadowView& view) {
|
|
34
|
+
if (!view.props) {
|
|
35
|
+
return std::string("{}");
|
|
36
|
+
}
|
|
37
|
+
auto props = std::dynamic_pointer_cast<const RNSScreenStackHeaderConfigProps>(
|
|
38
|
+
view.props);
|
|
39
|
+
if (!props) {
|
|
40
|
+
return std::string("{}");
|
|
41
|
+
}
|
|
42
|
+
folly::dynamic object = folly::dynamic::object;
|
|
43
|
+
object["title"] = props->title;
|
|
44
|
+
object["backgroundColor"] = argb(props->backgroundColor);
|
|
45
|
+
object["titleColor"] = argb(props->titleColor);
|
|
46
|
+
object["tintColor"] = argb(props->color);
|
|
47
|
+
object["hidden"] = props->hidden;
|
|
48
|
+
object["hideBackButton"] = props->hideBackButton;
|
|
49
|
+
return folly::toJson(object);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
std::string headerSubviewPropsJson(
|
|
53
|
+
const std::string& /*componentName*/,
|
|
54
|
+
const ShadowView& view) {
|
|
55
|
+
if (!view.props) {
|
|
56
|
+
return std::string("{}");
|
|
57
|
+
}
|
|
58
|
+
auto props =
|
|
59
|
+
std::dynamic_pointer_cast<const RNSScreenStackHeaderSubviewProps>(
|
|
60
|
+
view.props);
|
|
61
|
+
if (!props) {
|
|
62
|
+
return std::string("{}");
|
|
63
|
+
}
|
|
64
|
+
folly::dynamic object = folly::dynamic::object;
|
|
65
|
+
object["type"] = toString(props->type);
|
|
66
|
+
return folly::toJson(object);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
} // namespace
|
|
70
|
+
|
|
71
|
+
void rn_gpui_install_screens_module() {
|
|
72
|
+
registerComponentProps("RNSScreen", screenPropsJson);
|
|
73
|
+
registerComponentProps("RNSScreenStackHeaderConfig", headerConfigPropsJson);
|
|
74
|
+
registerComponentProps("RNSScreenStackHeaderSubview", headerSubviewPropsJson);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
} // namespace rngpui
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
//! The screens module.
|
|
2
|
+
//!
|
|
3
|
+
//! Everything react-native-screens needs lives here: its props extraction in
|
|
4
|
+
//! `cpp/`, and its layout, painting and lifecycle in the modules below. Core is
|
|
5
|
+
//! told only what any library can say — a component name, a props reader, and a
|
|
6
|
+
//! set of generic hooks.
|
|
7
|
+
pub mod lifecycle;
|
|
8
|
+
pub mod props;
|
|
9
|
+
pub mod render;
|
|
10
|
+
pub mod stack;
|
|
11
|
+
pub mod state;
|
|
12
|
+
pub mod transition;
|
|
13
|
+
pub mod unsupported;
|
|
14
|
+
|
|
15
|
+
/// Registers this module's renderers and observer.
|
|
16
|
+
///
|
|
17
|
+
/// Autolink calls this from `ensure_linked()` while `main` starts, before any
|
|
18
|
+
/// `gpui::App` exists, so nothing here may touch the app.
|
|
19
|
+
pub fn rn_gpui_register() {
|
|
20
|
+
react_native_gpui::extensions::register_store_observer(std::sync::Arc::new(
|
|
21
|
+
stack::StackObserver,
|
|
22
|
+
));
|
|
23
|
+
for (component, build) in render::RENDERED_COMPONENTS {
|
|
24
|
+
react_native_gpui::extensions::register_component_renderer(
|
|
25
|
+
*component,
|
|
26
|
+
std::sync::Arc::from(build()),
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
//! The lifecycle events a stack sends to its screens as they appear and leave.
|
|
2
|
+
//!
|
|
3
|
+
//! React Native exposes these as `topWillAppear`, `topAppear`,
|
|
4
|
+
//! `topWillDisappear` and `topDisappear`, in that order around a transition.
|
|
5
|
+
use gpui::App;
|
|
6
|
+
use react_native_gpui::extensions::StoreHandle;
|
|
7
|
+
|
|
8
|
+
/// The events an activity-state change produces, in the order they fire.
|
|
9
|
+
pub fn lifecycle_events(previous: f32, next: f32) -> &'static [&'static str] {
|
|
10
|
+
match (previous < 1.0, previous >= 2.0, next < 1.0, next >= 2.0) {
|
|
11
|
+
(true, _, false, true) => &["topWillAppear", "topAppear"],
|
|
12
|
+
(true, _, false, false) => &["topWillAppear"],
|
|
13
|
+
(_, true, false, false) => &["topWillDisappear"],
|
|
14
|
+
(false, false, true, _) => &["topDisappear"],
|
|
15
|
+
(false, true, true, _) => &["topWillDisappear", "topDisappear"],
|
|
16
|
+
(false, false, false, true) => &["topAppear"],
|
|
17
|
+
_ => &[],
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/// Sends the "will" half of a transition: the outgoing screen then the incoming.
|
|
22
|
+
pub fn dispatch_will(
|
|
23
|
+
handle: &StoreHandle,
|
|
24
|
+
outgoing: Option<i32>,
|
|
25
|
+
incoming: Option<i32>,
|
|
26
|
+
cx: &mut App,
|
|
27
|
+
) {
|
|
28
|
+
for (tag, event) in [
|
|
29
|
+
outgoing.map(|tag| (tag, "topWillDisappear")),
|
|
30
|
+
incoming.map(|tag| (tag, "topWillAppear")),
|
|
31
|
+
]
|
|
32
|
+
.into_iter()
|
|
33
|
+
.flatten()
|
|
34
|
+
{
|
|
35
|
+
log::info!("[screens] tag={tag} lifecycle={event}");
|
|
36
|
+
handle.dispatch_event(tag, event, &serde_json::json!({}), cx);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/// Sends the "did" half: the outgoing screen then the incoming, and wakes
|
|
41
|
+
/// Fabric so the callbacks arrive before a later pop unmounts them.
|
|
42
|
+
pub fn dispatch_did(
|
|
43
|
+
handle: &StoreHandle,
|
|
44
|
+
outgoing: Option<i32>,
|
|
45
|
+
incoming: Option<i32>,
|
|
46
|
+
cx: &mut App,
|
|
47
|
+
) {
|
|
48
|
+
for (tag, event) in [
|
|
49
|
+
outgoing.map(|tag| (tag, "topDisappear")),
|
|
50
|
+
incoming.map(|tag| (tag, "topAppear")),
|
|
51
|
+
]
|
|
52
|
+
.into_iter()
|
|
53
|
+
.flatten()
|
|
54
|
+
{
|
|
55
|
+
log::info!("[screens] tag={tag} lifecycle={event}");
|
|
56
|
+
handle.dispatch_event(tag, event, &serde_json::json!({}), cx);
|
|
57
|
+
}
|
|
58
|
+
// A timer can complete after the display-frame chain has gone idle, so the
|
|
59
|
+
// JavaScript that handles these events needs a render to arrive in.
|
|
60
|
+
handle.wake(cx);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
#[cfg(test)]
|
|
64
|
+
mod tests {
|
|
65
|
+
use super::*;
|
|
66
|
+
|
|
67
|
+
#[test]
|
|
68
|
+
fn activity_state_edges_emit_lifecycle_in_order() {
|
|
69
|
+
assert_eq!(lifecycle_events(0.0, 1.0), &["topWillAppear"]);
|
|
70
|
+
assert_eq!(lifecycle_events(1.0, 2.0), &["topAppear"]);
|
|
71
|
+
assert_eq!(lifecycle_events(2.0, 1.0), &["topWillDisappear"]);
|
|
72
|
+
assert_eq!(lifecycle_events(1.0, 0.0), &["topDisappear"]);
|
|
73
|
+
assert_eq!(lifecycle_events(0.0, 2.0), &["topWillAppear", "topAppear"]);
|
|
74
|
+
assert_eq!(
|
|
75
|
+
lifecycle_events(2.0, 0.0),
|
|
76
|
+
&["topWillDisappear", "topDisappear"]
|
|
77
|
+
);
|
|
78
|
+
assert!(lifecycle_events(1.0, 1.0).is_empty());
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
//! Typed readers over the JSON this module's C++ half produced.
|
|
2
|
+
//!
|
|
3
|
+
//! A missing key reads as today's zero value, so a component that has not been
|
|
4
|
+
//! given the prop yet behaves the way it did before it arrived.
|
|
5
|
+
use react_native_gpui::extensions::ComponentNode;
|
|
6
|
+
|
|
7
|
+
/// A number, or 0 when absent.
|
|
8
|
+
fn number(node: &ComponentNode, key: &str) -> f64 {
|
|
9
|
+
node.props
|
|
10
|
+
.get(key)
|
|
11
|
+
.and_then(|value| value.as_f64())
|
|
12
|
+
.unwrap_or(0.0)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/// A number as `f32`, or 0 when absent.
|
|
16
|
+
fn number_f32(node: &ComponentNode, key: &str) -> f32 {
|
|
17
|
+
number(node, key) as f32
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/// A string, borrowed from the props.
|
|
21
|
+
fn string<'a>(node: &'a ComponentNode, key: &str) -> &'a str {
|
|
22
|
+
node.props
|
|
23
|
+
.get(key)
|
|
24
|
+
.and_then(|value| value.as_str())
|
|
25
|
+
.unwrap_or("")
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/// A boolean, false when absent.
|
|
29
|
+
fn boolean(node: &ComponentNode, key: &str) -> bool {
|
|
30
|
+
node.props
|
|
31
|
+
.get(key)
|
|
32
|
+
.and_then(|value| value.as_bool())
|
|
33
|
+
.unwrap_or(false)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/// A colour as 0xAARRGGBB, 0 when unset.
|
|
37
|
+
fn color(node: &ComponentNode, key: &str) -> u32 {
|
|
38
|
+
number(node, key) as u32
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/// An `RNSScreen`'s activity state. 0 is detached, 1 is transitioning, 2 is
|
|
42
|
+
/// fully on screen.
|
|
43
|
+
pub fn activity_state(node: &ComponentNode) -> f32 {
|
|
44
|
+
number_f32(node, "activityState")
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/// An `RNSScreen`'s `stackAnimation`.
|
|
48
|
+
pub fn stack_animation(node: &ComponentNode) -> &str {
|
|
49
|
+
string(node, "stackAnimation")
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/// An `RNSScreen`'s `stackPresentation`.
|
|
53
|
+
pub fn stack_presentation(node: &ComponentNode) -> &str {
|
|
54
|
+
string(node, "stackPresentation")
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/// Whether a presentation paints its own card over the one underneath.
|
|
58
|
+
pub fn is_modal_presentation(node: &ComponentNode) -> bool {
|
|
59
|
+
matches!(
|
|
60
|
+
stack_presentation(node),
|
|
61
|
+
"modal" | "transparentModal" | "containedModal" | "formSheet"
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/// Whether that card lets the screen underneath show through.
|
|
66
|
+
pub fn is_transparent_presentation(node: &ComponentNode) -> bool {
|
|
67
|
+
stack_presentation(node) == "transparentModal"
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/// A header config's title.
|
|
71
|
+
pub fn header_title(node: &ComponentNode) -> &str {
|
|
72
|
+
string(node, "title")
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/// A header config's background colour.
|
|
76
|
+
pub fn header_background_color(node: &ComponentNode) -> u32 {
|
|
77
|
+
color(node, "backgroundColor")
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/// A header config's title colour.
|
|
81
|
+
pub fn header_title_color(node: &ComponentNode) -> u32 {
|
|
82
|
+
color(node, "titleColor")
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/// A header config's tint colour, used for the back button.
|
|
86
|
+
pub fn header_tint_color(node: &ComponentNode) -> u32 {
|
|
87
|
+
color(node, "tintColor")
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/// Whether a header config hides the whole bar.
|
|
91
|
+
pub fn header_hidden(node: &ComponentNode) -> bool {
|
|
92
|
+
boolean(node, "hidden")
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/// Whether a header config hides only the back button.
|
|
96
|
+
pub fn header_hide_back(node: &ComponentNode) -> bool {
|
|
97
|
+
boolean(node, "hideBackButton")
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/// A header subview's placement: `left`, `right`, `center`, `title` or a Yoga x.
|
|
101
|
+
pub fn subview_type(node: &ComponentNode) -> &str {
|
|
102
|
+
string(node, "type")
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
#[cfg(test)]
|
|
106
|
+
mod tests {
|
|
107
|
+
use super::*;
|
|
108
|
+
use std::sync::Arc;
|
|
109
|
+
|
|
110
|
+
fn node(json: serde_json::Value) -> ComponentNode {
|
|
111
|
+
ComponentNode {
|
|
112
|
+
component: "RNSScreen".to_string(),
|
|
113
|
+
tag: 1,
|
|
114
|
+
frame: rn_gpui_sys::ffi::RectF::default(),
|
|
115
|
+
scale: (1.0, 1.0),
|
|
116
|
+
props: Arc::new(json),
|
|
117
|
+
parent: None,
|
|
118
|
+
children: Vec::new(),
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
#[test]
|
|
123
|
+
fn a_missing_key_reads_as_the_zero_value() {
|
|
124
|
+
let empty = node(serde_json::json!({}));
|
|
125
|
+
assert_eq!(activity_state(&empty), 0.0);
|
|
126
|
+
assert_eq!(stack_animation(&empty), "");
|
|
127
|
+
assert_eq!(stack_presentation(&empty), "");
|
|
128
|
+
assert_eq!(header_title(&empty), "");
|
|
129
|
+
assert_eq!(header_background_color(&empty), 0);
|
|
130
|
+
assert!(!header_hidden(&empty));
|
|
131
|
+
assert!(!header_hide_back(&empty));
|
|
132
|
+
assert!(!is_modal_presentation(&empty));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
#[test]
|
|
136
|
+
fn reads_the_fields_the_props_half_writes() {
|
|
137
|
+
let screen = node(serde_json::json!({
|
|
138
|
+
"activityState": 2,
|
|
139
|
+
"stackAnimation": "slide_from_right",
|
|
140
|
+
"stackPresentation": "transparentModal",
|
|
141
|
+
}));
|
|
142
|
+
assert_eq!(activity_state(&screen), 2.0);
|
|
143
|
+
assert_eq!(stack_animation(&screen), "slide_from_right");
|
|
144
|
+
assert!(is_modal_presentation(&screen));
|
|
145
|
+
assert!(is_transparent_presentation(&screen));
|
|
146
|
+
|
|
147
|
+
let header = node(serde_json::json!({
|
|
148
|
+
"title": "Home",
|
|
149
|
+
"backgroundColor": 0xff112233u32,
|
|
150
|
+
"titleColor": 0xffaabbccu32,
|
|
151
|
+
"tintColor": 0xff445566u32,
|
|
152
|
+
"hidden": true,
|
|
153
|
+
"hideBackButton": true,
|
|
154
|
+
}));
|
|
155
|
+
assert_eq!(header_title(&header), "Home");
|
|
156
|
+
assert_eq!(header_background_color(&header), 0xff112233);
|
|
157
|
+
assert_eq!(header_title_color(&header), 0xffaabbcc);
|
|
158
|
+
assert_eq!(header_tint_color(&header), 0xff445566);
|
|
159
|
+
assert!(header_hidden(&header));
|
|
160
|
+
assert!(header_hide_back(&header));
|
|
161
|
+
|
|
162
|
+
let subview = node(serde_json::json!({"type": "right"}));
|
|
163
|
+
assert_eq!(subview_type(&subview), "right");
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
#[test]
|
|
167
|
+
fn only_the_modal_presentations_count_as_modal() {
|
|
168
|
+
for presentation in ["modal", "transparentModal", "containedModal", "formSheet"] {
|
|
169
|
+
let node = node(serde_json::json!({"stackPresentation": presentation}));
|
|
170
|
+
assert!(is_modal_presentation(&node), "{presentation}");
|
|
171
|
+
}
|
|
172
|
+
for presentation in ["push", "card", ""] {
|
|
173
|
+
let node = node(serde_json::json!({"stackPresentation": presentation}));
|
|
174
|
+
assert!(!is_modal_presentation(&node), "{presentation}");
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|