Run Iris examples on desktop and Android
This commit is contained in:
1 parent
37f956707e
commit
b5666cdef9
25 files changed
+563
-191
No files matched your search
+25
-2
@@ -12,7 +12,7 @@ pollster = { workspace = true }
|
|||||||
wgpu = { workspace = true }
|
wgpu = { workspace = true }
|
||||||
image = { workspace = true }
|
image = { workspace = true }
|
||||||
accesskit = { workspace = true }
|
accesskit = { workspace = true }
|
||||||
tokio = { workspace = true, features = ["sync", "rt", "rt-multi-thread"] }
|
tokio = { workspace = true, features = ["sync", "rt", "rt-multi-thread", "time"] }
|
||||||
# The embedding app installs the logger.
|
# The embedding app installs the logger.
|
||||||
log = "0.4.34"
|
log = "0.4.34"
|
||||||
|
|
||||||
@@ -35,9 +35,32 @@ send_wrapper = "0.6.0"
|
|||||||
force-gles = []
|
force-gles = []
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tokio = { workspace = true, features = ["sync", "rt", "rt-multi-thread", "time"] }
|
|
||||||
bytemuck = { workspace = true }
|
bytemuck = { workspace = true }
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "bench_images"
|
||||||
|
path = "examples/bench_images/desktop.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "message_list"
|
||||||
|
path = "examples/message_list/desktop.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "minimal"
|
||||||
|
path = "examples/minimal/desktop.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "tabs"
|
||||||
|
path = "examples/tabs/desktop.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "task"
|
||||||
|
path = "examples/task/desktop.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "view"
|
||||||
|
path = "examples/view/desktop.rs"
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "message_list"
|
name = "message_list"
|
||||||
harness = false
|
harness = false
|
||||||
|
|||||||
+168
-33
@@ -1,4 +1,4 @@
|
|||||||
use cargo_metadata::{CrateType, MetadataCommand, Package};
|
use cargo_metadata::{CrateType, MetadataCommand, Package, Target, TargetKind};
|
||||||
use std::{
|
use std::{
|
||||||
env,
|
env,
|
||||||
ffi::OsStr,
|
ffi::OsStr,
|
||||||
@@ -36,9 +36,9 @@ pub fn run(mut args: Vec<String>) -> Result<(), String> {
|
|||||||
|
|
||||||
fn print_help() {
|
fn print_help() {
|
||||||
println!(
|
println!(
|
||||||
"Build an Iris library as an installable Android APK.\n\n\
|
"Build an Iris application or example as an installable Android APK.\n\n\
|
||||||
Usage:\n cargo iris apk [OPTIONS]\n cargo iris run --device SERIAL [OPTIONS]\n\n\
|
Usage:\n cargo iris apk [OPTIONS]\n cargo iris run --device SERIAL [OPTIONS]\n\n\
|
||||||
Options:\n --manifest-path PATH\n --package NAME\n --abi arm64-v8a|x86_64\n --release\n\
|
Options:\n --manifest-path PATH\n --package NAME\n --example NAME\n --abi arm64-v8a|x86_64\n --release\n\
|
||||||
\x20 --application-id ID\n --label TEXT\n --keystore PATH --key-alias ALIAS\n\n\
|
\x20 --application-id ID\n --label TEXT\n --keystore PATH --key-alias ALIAS\n\n\
|
||||||
Release signing passwords come from IRIS_KEYSTORE_PASSWORD and, when different,\n\
|
Release signing passwords come from IRIS_KEYSTORE_PASSWORD and, when different,\n\
|
||||||
IRIS_KEY_PASSWORD. Iris uses the Android SDK and emulator/device supplied by you."
|
IRIS_KEY_PASSWORD. Iris uses the Android SDK and emulator/device supplied by you."
|
||||||
@@ -49,6 +49,7 @@ fn print_help() {
|
|||||||
struct Options {
|
struct Options {
|
||||||
manifest_path: Option<PathBuf>,
|
manifest_path: Option<PathBuf>,
|
||||||
package: Option<String>,
|
package: Option<String>,
|
||||||
|
example: Option<String>,
|
||||||
abi: String,
|
abi: String,
|
||||||
release: bool,
|
release: bool,
|
||||||
application_id: Option<String>,
|
application_id: Option<String>,
|
||||||
@@ -77,6 +78,7 @@ impl Options {
|
|||||||
options.manifest_path = Some(value("--manifest-path", &mut i)?.into())
|
options.manifest_path = Some(value("--manifest-path", &mut i)?.into())
|
||||||
}
|
}
|
||||||
"--package" => options.package = Some(value("--package", &mut i)?),
|
"--package" => options.package = Some(value("--package", &mut i)?),
|
||||||
|
"--example" => options.example = Some(value("--example", &mut i)?),
|
||||||
"--abi" => options.abi = value("--abi", &mut i)?,
|
"--abi" => options.abi = value("--abi", &mut i)?,
|
||||||
"--application-id" => {
|
"--application-id" => {
|
||||||
options.application_id = Some(value("--application-id", &mut i)?)
|
options.application_id = Some(value("--application-id", &mut i)?)
|
||||||
@@ -128,43 +130,64 @@ fn build(options: &Options) -> Result<Built, String> {
|
|||||||
metadata.root_package(),
|
metadata.root_package(),
|
||||||
options.package.as_deref(),
|
options.package.as_deref(),
|
||||||
)?;
|
)?;
|
||||||
let target = package
|
let target = select_target(package, options.example.as_deref())?;
|
||||||
.targets
|
|
||||||
.iter()
|
|
||||||
.find(|target| {
|
|
||||||
target
|
|
||||||
.crate_types
|
|
||||||
.iter()
|
|
||||||
.any(|kind| kind == &CrateType::CDyLib)
|
|
||||||
})
|
|
||||||
.ok_or_else(|| {
|
|
||||||
format!(
|
|
||||||
"package {} has no cdylib target; add `[lib] crate-type = [\"cdylib\", \"rlib\"]` to {}",
|
|
||||||
package.name, package.manifest_path
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
let sdk = Sdk::find()?;
|
let sdk = Sdk::find()?;
|
||||||
let application_id = options
|
let application_id = options
|
||||||
.application_id
|
.application_id
|
||||||
.clone()
|
.clone()
|
||||||
.or_else(|| metadata_string(package, "application-id"))
|
.or_else(|| {
|
||||||
.unwrap_or_else(|| default_application_id(&package.name));
|
options
|
||||||
|
.example
|
||||||
|
.is_none()
|
||||||
|
.then(|| metadata_string(package, "application-id"))
|
||||||
|
.flatten()
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| default_application_id(&package.name, options.example.as_deref()));
|
||||||
validate_application_id(&application_id)?;
|
validate_application_id(&application_id)?;
|
||||||
let label = options
|
let label = options
|
||||||
.label
|
.label
|
||||||
.clone()
|
.clone()
|
||||||
.or_else(|| metadata_string(package, "label"))
|
.or_else(|| {
|
||||||
.unwrap_or_else(|| package.name.to_string());
|
options
|
||||||
|
.example
|
||||||
|
.is_none()
|
||||||
|
.then(|| metadata_string(package, "label"))
|
||||||
|
.flatten()
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
options
|
||||||
|
.example
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| package.name.to_string())
|
||||||
|
});
|
||||||
let variant = if options.release { "release" } else { "debug" };
|
let variant = if options.release { "release" } else { "debug" };
|
||||||
|
let artifact = options.example.as_ref().map_or_else(
|
||||||
|
|| package.name.to_string(),
|
||||||
|
|example| format!("{}-{example}", package.name),
|
||||||
|
);
|
||||||
let output = metadata
|
let output = metadata
|
||||||
.target_directory
|
.target_directory
|
||||||
.as_std_path()
|
.as_std_path()
|
||||||
.join("iris-android")
|
.join("iris-android")
|
||||||
.join(package.name.as_str())
|
.join(&artifact)
|
||||||
.join(variant)
|
.join(variant)
|
||||||
.join(&options.abi);
|
.join(&options.abi);
|
||||||
recreate(&output)?;
|
recreate(&output)?;
|
||||||
let native = output.join("native");
|
let native = output.join("native");
|
||||||
|
let (build_manifest, library_name) = if options.example.is_some() {
|
||||||
|
let wrapper = metadata
|
||||||
|
.target_directory
|
||||||
|
.as_std_path()
|
||||||
|
.join("iris-android")
|
||||||
|
.join("example-wrappers")
|
||||||
|
.join(&artifact);
|
||||||
|
materialize_example_wrapper(&metadata.packages, package, target, &wrapper)?
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
package.manifest_path.as_std_path().to_path_buf(),
|
||||||
|
target.name.clone(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
let mut cargo = Command::new("cargo");
|
let mut cargo = Command::new("cargo");
|
||||||
cargo
|
cargo
|
||||||
@@ -173,7 +196,8 @@ fn build(options: &Options) -> Result<Built, String> {
|
|||||||
.arg("build")
|
.arg("build")
|
||||||
.arg("--lib")
|
.arg("--lib")
|
||||||
.arg("--manifest-path")
|
.arg("--manifest-path")
|
||||||
.arg(package.manifest_path.as_std_path());
|
.arg(&build_manifest)
|
||||||
|
.env("CARGO_TARGET_DIR", metadata.target_directory.as_std_path());
|
||||||
if options.release {
|
if options.release {
|
||||||
cargo.arg("--release");
|
cargo.arg("--release");
|
||||||
}
|
}
|
||||||
@@ -185,13 +209,13 @@ fn build(options: &Options) -> Result<Built, String> {
|
|||||||
|
|
||||||
let library = native
|
let library = native
|
||||||
.join(&options.abi)
|
.join(&options.abi)
|
||||||
.join(format!("lib{}.so", target.name));
|
.join(format!("lib{library_name}.so"));
|
||||||
if !library.is_file() {
|
if !library.is_file() {
|
||||||
return Err(format!("cargo-ndk did not produce {}", library.display()));
|
return Err(format!("cargo-ndk did not produce {}", library.display()));
|
||||||
}
|
}
|
||||||
let classes = output.join("classes");
|
let classes = output.join("classes");
|
||||||
fs::create_dir_all(&classes).map_err(io_error("create Java output", &classes))?;
|
fs::create_dir_all(&classes).map_err(io_error("create Java output", &classes))?;
|
||||||
let sources = materialize_host(&output, &target.name)?;
|
let sources = materialize_host(&output, &library_name)?;
|
||||||
let java_files = files_with_extension(&sources, "java")?;
|
let java_files = files_with_extension(&sources, "java")?;
|
||||||
let mut javac = Command::new("javac");
|
let mut javac = Command::new("javac");
|
||||||
javac
|
javac
|
||||||
@@ -229,7 +253,7 @@ fn build(options: &Options) -> Result<Built, String> {
|
|||||||
let manifest = output.join("AndroidManifest.xml");
|
let manifest = output.join("AndroidManifest.xml");
|
||||||
fs::write(
|
fs::write(
|
||||||
&manifest,
|
&manifest,
|
||||||
manifest_xml(&application_id, &label, &target.name, sdk.api),
|
manifest_xml(&application_id, &label, &library_name, sdk.api),
|
||||||
)
|
)
|
||||||
.map_err(io_error("write Android manifest", &manifest))?;
|
.map_err(io_error("write Android manifest", &manifest))?;
|
||||||
let unsigned = output.join("unsigned.apk");
|
let unsigned = output.join("unsigned.apk");
|
||||||
@@ -258,7 +282,7 @@ fn build(options: &Options) -> Result<Built, String> {
|
|||||||
&dex.join("classes.dex"),
|
&dex.join("classes.dex"),
|
||||||
&library,
|
&library,
|
||||||
&options.abi,
|
&options.abi,
|
||||||
&target.name,
|
&library_name,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let aligned = output.join("aligned.apk");
|
let aligned = output.join("aligned.apk");
|
||||||
@@ -272,7 +296,7 @@ fn build(options: &Options) -> Result<Built, String> {
|
|||||||
"APK alignment",
|
"APK alignment",
|
||||||
"install Android SDK Build Tools",
|
"install Android SDK Build Tools",
|
||||||
)?;
|
)?;
|
||||||
let apk = output.join(format!("{}-{variant}.apk", package.name));
|
let apk = output.join(format!("{artifact}-{variant}.apk"));
|
||||||
sign(&sdk, options, &aligned, &apk)?;
|
sign(&sdk, options, &aligned, &apk)?;
|
||||||
verify(&sdk, &apk)?;
|
verify(&sdk, &apk)?;
|
||||||
Ok(Built {
|
Ok(Built {
|
||||||
@@ -298,6 +322,106 @@ fn select_package<'a>(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn select_target<'a>(package: &'a Package, example: Option<&str>) -> Result<&'a Target, String> {
|
||||||
|
if let Some(example) = example {
|
||||||
|
return package
|
||||||
|
.targets
|
||||||
|
.iter()
|
||||||
|
.find(|target| {
|
||||||
|
target.name == example
|
||||||
|
&& target.kind.iter().any(|kind| kind == &TargetKind::Example)
|
||||||
|
})
|
||||||
|
.ok_or_else(|| {
|
||||||
|
format!(
|
||||||
|
"package {} has no example named {example:?}; use one of: {}",
|
||||||
|
package.name,
|
||||||
|
package
|
||||||
|
.targets
|
||||||
|
.iter()
|
||||||
|
.filter(|target| target
|
||||||
|
.kind
|
||||||
|
.iter()
|
||||||
|
.any(|kind| kind == &TargetKind::Example))
|
||||||
|
.map(|target| target.name.as_str())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", ")
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
package
|
||||||
|
.targets
|
||||||
|
.iter()
|
||||||
|
.find(|target| {
|
||||||
|
target
|
||||||
|
.crate_types
|
||||||
|
.iter()
|
||||||
|
.any(|kind| kind == &CrateType::CDyLib)
|
||||||
|
})
|
||||||
|
.ok_or_else(|| {
|
||||||
|
format!(
|
||||||
|
"package {} has no cdylib target; add `[lib] crate-type = [\"cdylib\", \"rlib\"]` to {}",
|
||||||
|
package.name, package.manifest_path
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn materialize_example_wrapper(
|
||||||
|
packages: &[Package],
|
||||||
|
package: &Package,
|
||||||
|
example: &Target,
|
||||||
|
wrapper: &Path,
|
||||||
|
) -> Result<(PathBuf, String), String> {
|
||||||
|
let android_source = example
|
||||||
|
.src_path
|
||||||
|
.as_std_path()
|
||||||
|
.parent()
|
||||||
|
.unwrap()
|
||||||
|
.join("android.rs");
|
||||||
|
if !android_source.is_file() {
|
||||||
|
return Err(format!(
|
||||||
|
"example {:?} has no Android entry point at {}; put shared code in lib.rs and add sibling desktop.rs and android.rs entries",
|
||||||
|
example.name,
|
||||||
|
android_source.display()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let iris = packages
|
||||||
|
.iter()
|
||||||
|
.find(|dependency| dependency.name == "iris")
|
||||||
|
.ok_or_else(|| {
|
||||||
|
format!(
|
||||||
|
"example {:?} does not depend on Iris; add `iris` to {}",
|
||||||
|
example.name, package.manifest_path
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let source_dir = wrapper.join("src");
|
||||||
|
fs::create_dir_all(&source_dir)
|
||||||
|
.map_err(io_error("create Android example wrapper", &source_dir))?;
|
||||||
|
let library_name = format!(
|
||||||
|
"iris_android_{}_{}",
|
||||||
|
identifier_segment(&package.name),
|
||||||
|
identifier_segment(&example.name)
|
||||||
|
);
|
||||||
|
let iris_root = iris.manifest_path.parent().unwrap();
|
||||||
|
let manifest = format!(
|
||||||
|
"[package]\nname = {name:?}\nversion = \"0.0.0\"\nedition = \"2024\"\n\n\
|
||||||
|
[lib]\nname = {library_name:?}\ncrate-type = [\"cdylib\", \"rlib\"]\n\n\
|
||||||
|
[dependencies]\niris = {{ path = {iris_root:?} }}\n\n[workspace]\n",
|
||||||
|
name = format!("iris-android-{}-{}", package.name, example.name),
|
||||||
|
iris_root = iris_root.as_str(),
|
||||||
|
);
|
||||||
|
let manifest_path = wrapper.join("Cargo.toml");
|
||||||
|
fs::write(&manifest_path, manifest)
|
||||||
|
.map_err(io_error("write Android example manifest", &manifest_path))?;
|
||||||
|
let source = format!(
|
||||||
|
"#[path = {:?}]\nmod example;\n",
|
||||||
|
android_source.to_string_lossy()
|
||||||
|
);
|
||||||
|
let source_path = source_dir.join("lib.rs");
|
||||||
|
fs::write(&source_path, source)
|
||||||
|
.map_err(io_error("write Android example wrapper", &source_path))?;
|
||||||
|
Ok((manifest_path, library_name))
|
||||||
|
}
|
||||||
|
|
||||||
fn metadata_string(package: &Package, key: &str) -> Option<String> {
|
fn metadata_string(package: &Package, key: &str) -> Option<String> {
|
||||||
package
|
package
|
||||||
.metadata
|
.metadata
|
||||||
@@ -308,8 +432,8 @@ fn metadata_string(package: &Package, key: &str) -> Option<String> {
|
|||||||
.map(str::to_owned)
|
.map(str::to_owned)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_application_id(package: &str) -> String {
|
fn identifier_segment(value: &str) -> String {
|
||||||
let segment: String = package
|
value
|
||||||
.chars()
|
.chars()
|
||||||
.map(|c| {
|
.map(|c| {
|
||||||
if c.is_ascii_alphanumeric() {
|
if c.is_ascii_alphanumeric() {
|
||||||
@@ -318,8 +442,15 @@ fn default_application_id(package: &str) -> String {
|
|||||||
'_'
|
'_'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect()
|
||||||
format!("dev.iris.app.{segment}")
|
}
|
||||||
|
|
||||||
|
fn default_application_id(package: &str, example: Option<&str>) -> String {
|
||||||
|
let package = identifier_segment(package);
|
||||||
|
match example {
|
||||||
|
Some(example) => format!("dev.iris.example.{package}.{}", identifier_segment(example)),
|
||||||
|
None => format!("dev.iris.app.{package}"),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_application_id(id: &str) -> Result<(), String> {
|
fn validate_application_id(id: &str) -> Result<(), String> {
|
||||||
@@ -699,6 +830,10 @@ mod tests {
|
|||||||
assert!(validate_application_id("one").is_err());
|
assert!(validate_application_id("one").is_err());
|
||||||
assert!(validate_application_id("dev.2demo").is_err());
|
assert!(validate_application_id("dev.2demo").is_err());
|
||||||
assert!(validate_application_id("dev.iris.bad-name").is_err());
|
assert!(validate_application_id("dev.iris.bad-name").is_err());
|
||||||
|
assert_eq!(
|
||||||
|
default_application_id("demo-app", Some("color-picker")),
|
||||||
|
"dev.iris.example.demo_app.color_picker"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
use iris::prelude::*;
|
||||||
|
|
||||||
|
#[path = "lib.rs"]
|
||||||
|
mod app;
|
||||||
|
use app::*;
|
||||||
|
|
||||||
|
#[derive(AndroidUiState)]
|
||||||
|
struct State {
|
||||||
|
ui_state: AndroidUiState,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AndroidAppState for State {
|
||||||
|
type Resources = StdRsc<Self>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[iris::app_init]
|
||||||
|
fn create(mut ui_state: AndroidUiState, rsc: &mut StdRsc<State>) -> State {
|
||||||
|
let _ = build(rsc, &mut ui_state);
|
||||||
|
State { ui_state }
|
||||||
|
}
|
||||||
@@ -1,6 +1,10 @@
|
|||||||
use iris::prelude::*;
|
use iris::prelude::*;
|
||||||
|
use winit::event::WindowEvent;
|
||||||
|
|
||||||
|
#[path = "lib.rs"]
|
||||||
|
mod app;
|
||||||
|
use app::*;
|
||||||
|
|
||||||
const ROWS: usize = 1000;
|
|
||||||
const SETTLE_FRAMES: usize = 4;
|
const SETTLE_FRAMES: usize = 4;
|
||||||
const FRAMES: usize = 6;
|
const FRAMES: usize = 6;
|
||||||
|
|
||||||
@@ -14,30 +18,17 @@ struct State {
|
|||||||
|
|
||||||
impl DesktopAppState for State {
|
impl DesktopAppState for State {
|
||||||
fn new(mut ui_state: DesktopUiState, rsc: &mut StdRsc<Self>, _: Proxy<Self::Event>) -> Self {
|
fn new(mut ui_state: DesktopUiState, rsc: &mut StdRsc<Self>, _: Proxy<Self::Event>) -> Self {
|
||||||
let mut span = Span::empty(Dir::DOWN);
|
let span = build(rsc, &mut ui_state);
|
||||||
for _ in 0..ROWS {
|
|
||||||
let img = image::DynamicImage::new_rgba8(32, 32);
|
|
||||||
let widget = image::<StdRsc<Self>>(img)(rsc);
|
|
||||||
let widget = rsc.ui.widgets.add_strong(widget);
|
|
||||||
span.push(widget.any());
|
|
||||||
}
|
|
||||||
let span = rsc.ui.widgets.add_strong(span);
|
|
||||||
let span_weak = span.weak();
|
|
||||||
let root = rsc
|
|
||||||
.ui
|
|
||||||
.widgets
|
|
||||||
.add_strong(ScrollArea::new(span.any(), Axis::Y, Pin::End));
|
|
||||||
ui_state.set_root(rsc, root.any());
|
|
||||||
Self {
|
Self {
|
||||||
ui_state,
|
ui_state,
|
||||||
span: span_weak,
|
span,
|
||||||
frame: 0,
|
frame: 0,
|
||||||
appended: false,
|
appended: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn window_event(&mut self, event: winit::event::WindowEvent, rsc: &mut StdRsc<Self>) {
|
fn window_event(&mut self, event: WindowEvent, rsc: &mut StdRsc<Self>) {
|
||||||
if !matches!(event, winit::event::WindowEvent::RedrawRequested) {
|
if !matches!(event, WindowEvent::RedrawRequested) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self.frame += 1;
|
self.frame += 1;
|
||||||
@@ -48,7 +39,7 @@ impl DesktopAppState for State {
|
|||||||
);
|
);
|
||||||
if self.frame == SETTLE_FRAMES && !self.appended {
|
if self.frame == SETTLE_FRAMES && !self.appended {
|
||||||
self.appended = true;
|
self.appended = true;
|
||||||
let img = image::DynamicImage::new_rgba8(32, 32);
|
let img = iris::image::DynamicImage::new_rgba8(32, 32);
|
||||||
let widget = image::<StdRsc<Self>>(img)(rsc);
|
let widget = image::<StdRsc<Self>>(img)(rsc);
|
||||||
let widget = rsc.ui.widgets.add_strong(widget);
|
let widget = rsc.ui.widgets.add_strong(widget);
|
||||||
rsc.ui
|
rsc.ui
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
use iris::prelude::*;
|
||||||
|
|
||||||
|
const ROWS: usize = 1000;
|
||||||
|
|
||||||
|
pub(crate) fn build<Rsc: UiRsc>(
|
||||||
|
rsc: &mut Rsc,
|
||||||
|
ui_state: &mut impl HasRoot<Rsc>,
|
||||||
|
) -> WeakWidget<Span> {
|
||||||
|
let mut span = Span::empty(Dir::DOWN);
|
||||||
|
for _ in 0..ROWS {
|
||||||
|
let img = iris::image::DynamicImage::new_rgba8(32, 32);
|
||||||
|
let widget = image::<Rsc>(img)(rsc);
|
||||||
|
let widget = rsc.ui_mut().widgets.add_strong(widget);
|
||||||
|
span.push(widget.any());
|
||||||
|
}
|
||||||
|
let span = rsc.ui_mut().widgets.add_strong(span);
|
||||||
|
let span_weak = span.weak();
|
||||||
|
let root = rsc
|
||||||
|
.ui_mut()
|
||||||
|
.widgets
|
||||||
|
.add_strong(ScrollArea::new(span.any(), Axis::Y, Pin::End));
|
||||||
|
ui_state.set_root(rsc, root.any());
|
||||||
|
span_weak
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
use iris::prelude::*;
|
||||||
|
|
||||||
|
#[path = "lib.rs"]
|
||||||
|
mod app;
|
||||||
|
use app::*;
|
||||||
|
|
||||||
|
#[derive(AndroidUiState)]
|
||||||
|
struct State {
|
||||||
|
ui_state: AndroidUiState,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AndroidAppState for State {
|
||||||
|
type Resources = StdRsc<Self>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[iris::app_init]
|
||||||
|
fn create(mut ui_state: AndroidUiState, rsc: &mut StdRsc<State>) -> State {
|
||||||
|
build(rsc, &mut ui_state);
|
||||||
|
State { ui_state }
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
use iris::prelude::*;
|
||||||
|
use winit::{dpi::LogicalSize, window::WindowAttributes};
|
||||||
|
|
||||||
|
#[path = "lib.rs"]
|
||||||
|
mod app;
|
||||||
|
use app::*;
|
||||||
|
|
||||||
|
#[derive(DesktopUiState)]
|
||||||
|
struct State {
|
||||||
|
ui_state: DesktopUiState,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DesktopAppState for State {
|
||||||
|
fn window_attributes() -> WindowAttributes {
|
||||||
|
WindowAttributes::default().with_inner_size(LogicalSize::new(420.0, 900.0))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new(mut ui_state: DesktopUiState, rsc: &mut StdRsc<Self>, _: Proxy<Self::Event>) -> Self {
|
||||||
|
build(rsc, &mut ui_state);
|
||||||
|
Self { ui_state }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
DesktopApp::<State>::run();
|
||||||
|
}
|
||||||
@@ -1,14 +1,4 @@
|
|||||||
use iris::prelude::*;
|
use iris::prelude::*;
|
||||||
use winit::{dpi::LogicalSize, window::WindowAttributes};
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
DesktopApp::<State>::run();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(DesktopUiState)]
|
|
||||||
struct State {
|
|
||||||
ui_state: DesktopUiState,
|
|
||||||
}
|
|
||||||
|
|
||||||
const ROWS: usize = 800;
|
const ROWS: usize = 800;
|
||||||
const IMAGE_EVERY: usize = 12;
|
const IMAGE_EVERY: usize = 12;
|
||||||
@@ -20,9 +10,9 @@ fn row_text(i: usize) -> String {
|
|||||||
format!("Message {i}: {}", SENTENCE.repeat(repeats))
|
format!("Message {i}: {}", SENTENCE.repeat(repeats))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn row_image(i: usize) -> image::DynamicImage {
|
fn row_image(i: usize) -> iris::image::DynamicImage {
|
||||||
let hue = ((i * 47) % 255) as u8;
|
let hue = ((i * 47) % 255) as u8;
|
||||||
image::RgbaImage::from_pixel(48, 48, image::Rgba([hue, 128, 255 - hue, 255])).into()
|
iris::image::RgbaImage::from_pixel(48, 48, iris::image::Rgba([hue, 128, 255 - hue, 255])).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_row<Rsc: UiRsc + 'static>(rsc: &mut Rsc, i: usize) -> StrongWidget {
|
fn build_row<Rsc: UiRsc + 'static>(rsc: &mut Rsc, i: usize) -> StrongWidget {
|
||||||
@@ -58,12 +48,7 @@ fn build_row<Rsc: UiRsc + 'static>(rsc: &mut Rsc, i: usize) -> StrongWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DesktopAppState for State {
|
pub(crate) fn build<Rsc: HasEvents>(rsc: &mut Rsc, ui_state: &mut impl HasRoot<Rsc>) {
|
||||||
fn window_attributes() -> WindowAttributes {
|
|
||||||
WindowAttributes::default().with_inner_size(LogicalSize::new(420.0, 900.0))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new(mut ui_state: DesktopUiState, rsc: &mut StdRsc<Self>, _: Proxy<Self::Event>) -> Self {
|
|
||||||
let mut list = LazySpan::new(Dir::DOWN, Pin::End);
|
let mut list = LazySpan::new(Dir::DOWN, Pin::End);
|
||||||
for i in 0..ROWS {
|
for i in 0..ROWS {
|
||||||
let row = build_row(rsc, i);
|
let row = build_row(rsc, i);
|
||||||
@@ -76,7 +61,4 @@ impl DesktopAppState for State {
|
|||||||
.background(rect(PaintId::WHITE))
|
.background(rect(PaintId::WHITE))
|
||||||
.add_strong(rsc);
|
.add_strong(rsc);
|
||||||
ui_state.set_root(rsc, root.any());
|
ui_state.set_root(rsc, root.any());
|
||||||
|
|
||||||
Self { ui_state }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
use iris::prelude::*;
|
||||||
|
|
||||||
|
#[path = "lib.rs"]
|
||||||
|
mod app;
|
||||||
|
use app::*;
|
||||||
|
|
||||||
|
#[derive(AndroidUiState)]
|
||||||
|
struct State {
|
||||||
|
ui_state: AndroidUiState,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AndroidAppState for State {
|
||||||
|
type Resources = StdRsc<Self>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[iris::app_init]
|
||||||
|
fn create(mut ui_state: AndroidUiState, rsc: &mut StdRsc<State>) -> State {
|
||||||
|
build(rsc, &mut ui_state);
|
||||||
|
State { ui_state }
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
use iris::prelude::*;
|
use iris::prelude::*;
|
||||||
|
|
||||||
fn main() {
|
#[path = "lib.rs"]
|
||||||
DesktopApp::<State>::run();
|
mod app;
|
||||||
}
|
use app::*;
|
||||||
|
|
||||||
#[derive(DesktopUiState)]
|
#[derive(DesktopUiState)]
|
||||||
struct State {
|
struct State {
|
||||||
@@ -11,7 +11,11 @@ struct State {
|
|||||||
|
|
||||||
impl DesktopAppState for State {
|
impl DesktopAppState for State {
|
||||||
fn new(mut ui_state: DesktopUiState, rsc: &mut StdRsc<Self>, _: Proxy<Self::Event>) -> Self {
|
fn new(mut ui_state: DesktopUiState, rsc: &mut StdRsc<Self>, _: Proxy<Self::Event>) -> Self {
|
||||||
rect(PaintId::RED).set_root(rsc, &mut ui_state);
|
build(rsc, &mut ui_state);
|
||||||
Self { ui_state }
|
Self { ui_state }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
DesktopApp::<State>::run();
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
use iris::prelude::*;
|
||||||
|
|
||||||
|
pub(crate) fn build<Rsc: UiRsc>(rsc: &mut Rsc, ui_state: &mut impl HasRoot<Rsc>) {
|
||||||
|
rect(PaintId::RED).set_root(rsc, ui_state);
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
use iris::prelude::*;
|
||||||
|
|
||||||
|
#[path = "lib.rs"]
|
||||||
|
mod app;
|
||||||
|
use app::*;
|
||||||
|
|
||||||
|
#[derive(AndroidUiState)]
|
||||||
|
struct Client {
|
||||||
|
ui_state: AndroidUiState,
|
||||||
|
info: WeakWidget<Text>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AndroidAppState for Client {
|
||||||
|
type Resources = StdRsc<Self>;
|
||||||
|
|
||||||
|
fn on_insets_changed(&mut self, rsc: &mut Self::Resources, _: WindowInsets) {
|
||||||
|
let views = self
|
||||||
|
.ui_state
|
||||||
|
.renderer
|
||||||
|
.as_ref()
|
||||||
|
.map_or(0, |renderer| renderer.ui.view_count());
|
||||||
|
update_info(rsc, self.info, views);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[iris::app_init]
|
||||||
|
fn create(mut ui_state: AndroidUiState, rsc: &mut StdRsc<Client>) -> Client {
|
||||||
|
let widgets = build(rsc, &mut ui_state);
|
||||||
|
update_info(rsc, widgets.info, 0);
|
||||||
|
Client {
|
||||||
|
ui_state,
|
||||||
|
info: widgets.info,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
use iris::prelude::*;
|
||||||
|
use winit::event::WindowEvent;
|
||||||
|
|
||||||
|
#[path = "lib.rs"]
|
||||||
|
mod app;
|
||||||
|
use app::*;
|
||||||
|
|
||||||
|
#[derive(DesktopUiState)]
|
||||||
|
struct Client {
|
||||||
|
ui_state: DesktopUiState,
|
||||||
|
info: WeakWidget<Text>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DesktopAppState for Client {
|
||||||
|
fn new(mut ui_state: DesktopUiState, rsc: &mut StdRsc<Self>, _: Proxy<Self::Event>) -> Self {
|
||||||
|
let widgets = build(rsc, &mut ui_state);
|
||||||
|
update_info(rsc, widgets.info, 0);
|
||||||
|
Self {
|
||||||
|
ui_state,
|
||||||
|
info: widgets.info,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn window_event(&mut self, _: WindowEvent, rsc: &mut StdRsc<Self>) {
|
||||||
|
update_info(rsc, self.info, self.ui_state.renderer.ui.view_count());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
DesktopApp::<Client>::run();
|
||||||
|
}
|
||||||
@@ -1,45 +1,26 @@
|
|||||||
use iris::prelude::*;
|
use iris::prelude::*;
|
||||||
use std::{cell::RefCell, rc::Rc};
|
use std::{cell::RefCell, rc::Rc};
|
||||||
use winit::event::WindowEvent;
|
|
||||||
|
|
||||||
fn main() {
|
pub(crate) fn update_info<Rsc: UiRsc>(rsc: &mut Rsc, info: WeakWidget<Text>, views: usize) {
|
||||||
DesktopApp::<Client>::run();
|
let render_state = rsc.ui().render_state();
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(DesktopUiState)]
|
|
||||||
struct Client {
|
|
||||||
ui_state: DesktopUiState,
|
|
||||||
info: WeakWidget<Text>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DesktopAppState for Client {
|
|
||||||
fn new(mut ui_state: DesktopUiState, rsc: &mut StdRsc<Self>, _: Proxy<Self::Event>) -> Self {
|
|
||||||
let widgets = build(rsc, &mut ui_state);
|
|
||||||
Self {
|
|
||||||
ui_state,
|
|
||||||
info: widgets.info,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn window_event(&mut self, _: WindowEvent, rsc: &mut StdRsc<Self>) {
|
|
||||||
let render_state = rsc.ui.render_state();
|
|
||||||
let new = format!(
|
let new = format!(
|
||||||
"widgets: {}\nactive: {}\nviews: {}",
|
"widgets: {}\nactive: {}\nviews: {views}",
|
||||||
rsc.widgets().len(),
|
rsc.widgets().len(),
|
||||||
render_state.get().active_widgets(),
|
render_state.get().active_widgets(),
|
||||||
self.ui_state.renderer.ui.view_count(),
|
|
||||||
);
|
);
|
||||||
if new != *rsc.widgets()[self.info].content {
|
if new != *rsc.widgets()[info].content {
|
||||||
*rsc.widgets_mut()[self.info].content = new;
|
*rsc.widgets_mut()[info].content = new;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ClientWidgets {
|
pub(crate) struct ClientWidgets {
|
||||||
info: WeakWidget<Text>,
|
pub(crate) info: WeakWidget<Text>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build<Rsc: HasEvents>(rsc: &mut Rsc, ui_state: &mut impl HasRoot<Rsc>) -> ClientWidgets
|
pub(crate) fn build<Rsc: HasEvents>(
|
||||||
|
rsc: &mut Rsc,
|
||||||
|
ui_state: &mut impl HasRoot<Rsc>,
|
||||||
|
) -> ClientWidgets
|
||||||
where
|
where
|
||||||
Rsc::State: FocusHost,
|
Rsc::State: FocusHost,
|
||||||
{
|
{
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
use iris::prelude::*;
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
DesktopApp::<State>::run();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(DesktopUiState)]
|
|
||||||
struct State {
|
|
||||||
ui_state: DesktopUiState,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DesktopAppState for State {
|
|
||||||
fn new(mut ui_state: DesktopUiState, rsc: &mut StdRsc<Self>, _: Proxy<Self::Event>) -> Self {
|
|
||||||
let rect = rect(PaintId::RED).add(rsc);
|
|
||||||
rect.task_on(CursorSense::click(), async move |mut ctx| {
|
|
||||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
|
||||||
ctx.update(move |_, rsc| {
|
|
||||||
let rect = rect(rsc);
|
|
||||||
if rect.is_paint(&PaintId::RED) {
|
|
||||||
rect.set_paint(PaintId::BLUE);
|
|
||||||
} else {
|
|
||||||
rect.set_paint(PaintId::RED);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.set_root(rsc, &mut ui_state);
|
|
||||||
Self { ui_state }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
use iris::prelude::*;
|
||||||
|
|
||||||
|
#[path = "lib.rs"]
|
||||||
|
mod app;
|
||||||
|
use app::*;
|
||||||
|
|
||||||
|
#[derive(AndroidUiState)]
|
||||||
|
struct State {
|
||||||
|
ui_state: AndroidUiState,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AndroidAppState for State {
|
||||||
|
type Resources = StdRsc<Self>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[iris::app_init]
|
||||||
|
fn create(mut ui_state: AndroidUiState, rsc: &mut StdRsc<State>) -> State {
|
||||||
|
build(rsc, &mut ui_state);
|
||||||
|
State { ui_state }
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
use iris::prelude::*;
|
||||||
|
|
||||||
|
#[path = "lib.rs"]
|
||||||
|
mod app;
|
||||||
|
use app::*;
|
||||||
|
|
||||||
|
#[derive(DesktopUiState)]
|
||||||
|
struct State {
|
||||||
|
ui_state: DesktopUiState,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DesktopAppState for State {
|
||||||
|
fn new(mut ui_state: DesktopUiState, rsc: &mut StdRsc<Self>, _: Proxy<Self::Event>) -> Self {
|
||||||
|
build(rsc, &mut ui_state);
|
||||||
|
Self { ui_state }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
DesktopApp::<State>::run();
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
use iris::prelude::*;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
pub(crate) fn build<Rsc: HasEvents + HasTasks>(rsc: &mut Rsc, ui_state: &mut impl HasRoot<Rsc>)
|
||||||
|
where
|
||||||
|
Rsc::State: FocusHost,
|
||||||
|
{
|
||||||
|
let rect = rect(PaintId::RED).add(rsc);
|
||||||
|
rect.task_on(CursorSense::click(), async move |mut ctx| {
|
||||||
|
iris::task::sleep(Duration::from_secs(1)).await;
|
||||||
|
ctx.update(move |_, rsc| {
|
||||||
|
let rect = rect(rsc);
|
||||||
|
if rect.is_paint(&PaintId::RED) {
|
||||||
|
rect.set_paint(PaintId::BLUE);
|
||||||
|
} else {
|
||||||
|
rect.set_paint(PaintId::RED);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.set_root(rsc, ui_state);
|
||||||
|
}
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
use iris::prelude::*;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
DesktopApp::<State>::run();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(DesktopUiState)]
|
|
||||||
struct State {
|
|
||||||
ui_state: DesktopUiState,
|
|
||||||
}
|
|
||||||
|
|
||||||
type Rsc = StdRsc<State>;
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, WidgetView)]
|
|
||||||
struct Test {
|
|
||||||
#[root]
|
|
||||||
root: WeakWidget<Rect>,
|
|
||||||
cur: WeakState<bool>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Test {
|
|
||||||
pub fn new(rsc: &mut Rsc) -> Self {
|
|
||||||
let root = rect(PaintId::RED).add(rsc);
|
|
||||||
let cur = rsc.create_state(root, false);
|
|
||||||
Self { root, cur }
|
|
||||||
}
|
|
||||||
pub fn toggle(&self, rsc: &mut Rsc) {
|
|
||||||
let cur = &mut rsc[self.cur];
|
|
||||||
*cur = !*cur;
|
|
||||||
if *cur {
|
|
||||||
rsc[self.root].set_paint(PaintId::BLUE);
|
|
||||||
} else {
|
|
||||||
rsc[self.root].set_paint(PaintId::RED);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DesktopAppState for State {
|
|
||||||
fn new(mut ui_state: DesktopUiState, rsc: &mut StdRsc<Self>, _: Proxy<Self::Event>) -> Self {
|
|
||||||
let test = Test::new(rsc);
|
|
||||||
|
|
||||||
test.on(CursorSense::click(), move |_, rsc| {
|
|
||||||
test.toggle(rsc);
|
|
||||||
})
|
|
||||||
.set_root(rsc, &mut ui_state);
|
|
||||||
|
|
||||||
Self { ui_state }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
use iris::prelude::*;
|
||||||
|
|
||||||
|
#[path = "lib.rs"]
|
||||||
|
mod app;
|
||||||
|
use app::*;
|
||||||
|
|
||||||
|
#[derive(AndroidUiState)]
|
||||||
|
struct State {
|
||||||
|
ui_state: AndroidUiState,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AndroidAppState for State {
|
||||||
|
type Resources = StdRsc<Self>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[iris::app_init]
|
||||||
|
fn create(mut ui_state: AndroidUiState, rsc: &mut StdRsc<State>) -> State {
|
||||||
|
build(rsc, &mut ui_state);
|
||||||
|
State { ui_state }
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
use iris::prelude::*;
|
||||||
|
|
||||||
|
#[path = "lib.rs"]
|
||||||
|
mod app;
|
||||||
|
use app::*;
|
||||||
|
|
||||||
|
#[derive(DesktopUiState)]
|
||||||
|
struct State {
|
||||||
|
ui_state: DesktopUiState,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DesktopAppState for State {
|
||||||
|
fn new(mut ui_state: DesktopUiState, rsc: &mut StdRsc<Self>, _: Proxy<Self::Event>) -> Self {
|
||||||
|
build(rsc, &mut ui_state);
|
||||||
|
Self { ui_state }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
DesktopApp::<State>::run();
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
use iris::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, WidgetView)]
|
||||||
|
struct Test {
|
||||||
|
#[root]
|
||||||
|
root: WeakWidget<Rect>,
|
||||||
|
cur: WeakState<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Test {
|
||||||
|
pub fn new<State>(rsc: &mut StdRsc<State>) -> Self {
|
||||||
|
let root = rect(PaintId::RED).add(rsc);
|
||||||
|
let cur = rsc.create_state(root, false);
|
||||||
|
Self { root, cur }
|
||||||
|
}
|
||||||
|
pub fn toggle<State>(&self, rsc: &mut StdRsc<State>) {
|
||||||
|
let cur = &mut rsc[self.cur];
|
||||||
|
*cur = !*cur;
|
||||||
|
if *cur {
|
||||||
|
rsc[self.root].set_paint(PaintId::BLUE);
|
||||||
|
} else {
|
||||||
|
rsc[self.root].set_paint(PaintId::RED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn build<State>(rsc: &mut StdRsc<State>, ui_state: &mut impl HasRoot<StdRsc<State>>)
|
||||||
|
where
|
||||||
|
State: FocusHost,
|
||||||
|
{
|
||||||
|
let test = Test::new(rsc);
|
||||||
|
test.on(CursorSense::click(), move |_, rsc| {
|
||||||
|
test.toggle(rsc);
|
||||||
|
})
|
||||||
|
.set_root(rsc, ui_state);
|
||||||
|
}
|
||||||
@@ -4,7 +4,9 @@ My experimental attempt at a rust ui library (also my first ui library).
|
|||||||
|
|
||||||
It's currently designed around using retained data structures (widgets), rather than diffing generated trees from data like xilem or iced. This is an experiment and I'm not sure if it's a good idea or not.
|
It's currently designed around using retained data structures (widgets), rather than diffing generated trees from data like xilem or iced. This is an experiment and I'm not sure if it's a good idea or not.
|
||||||
|
|
||||||
Examples are in `examples`, eg. `cargo run --example tabs`.
|
Examples are in `examples`, eg. `cargo run --example tabs`. Each example keeps
|
||||||
|
its widget tree in `lib.rs` and its small desktop and Android hosts in
|
||||||
|
`desktop.rs` and `android.rs`.
|
||||||
|
|
||||||
## Android applications
|
## Android applications
|
||||||
|
|
||||||
@@ -57,6 +59,14 @@ cargo iris apk --abi arm64-v8a
|
|||||||
cargo iris run --abi x86_64 --device emulator-5554
|
cargo iris run --abi x86_64 --device emulator-5554
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Package examples use the same command with `--example`:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cargo run --example tabs
|
||||||
|
cargo iris apk --example tabs --abi arm64-v8a
|
||||||
|
cargo iris run --example tabs --abi x86_64 --device emulator-5554
|
||||||
|
```
|
||||||
|
|
||||||
`cargo iris` packages directly with `cargo-ndk`, `javac`, `d8`, `aapt2`,
|
`cargo iris` packages directly with `cargo-ndk`, `javac`, `d8`, `aapt2`,
|
||||||
`jar`, `zipalign`, and `apksigner`; it does not require Gradle. The caller
|
`jar`, `zipalign`, and `apksigner`; it does not require Gradle. The caller
|
||||||
provides a JDK, Android SDK and NDK, and any emulator or physical device. Set
|
provides a JDK, Android SDK and NDK, and any emulator or physical device. Set
|
||||||
@@ -73,8 +83,8 @@ IRIS_KEYSTORE_PASSWORD=... IRIS_KEY_PASSWORD=... \
|
|||||||
```
|
```
|
||||||
|
|
||||||
APK staging and output live under
|
APK staging and output live under
|
||||||
`target/iris-android/<package>/<debug|release>/<abi>/`; the command's final
|
`target/iris-android/<package>[-<example>]/<debug|release>/<abi>/`; the
|
||||||
line is the verified APK's absolute path.
|
command's final line is the verified APK's absolute path.
|
||||||
|
|
||||||
Goals, in general order:
|
Goals, in general order:
|
||||||
1. does what I want it to (text, images, video, animations)
|
1. does what I want it to (text, images, video, animations)
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ mod access_tests;
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod layout_tests;
|
mod layout_tests;
|
||||||
|
|
||||||
|
pub use image;
|
||||||
pub use iris_core as core;
|
pub use iris_core as core;
|
||||||
pub use iris_macro as macros;
|
pub use iris_macro as macros;
|
||||||
pub use iris_macro::app_init;
|
pub use iris_macro::app_init;
|
||||||
|
|||||||
@@ -14,6 +14,11 @@ use tokio::{
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Waits without blocking the task runtime's worker thread.
|
||||||
|
pub async fn sleep(duration: std::time::Duration) {
|
||||||
|
tokio::time::sleep(duration).await;
|
||||||
|
}
|
||||||
|
|
||||||
/// What a completed task nudges when it wants its result drawn. Shared
|
/// What a completed task nudges when it wants its result drawn. Shared
|
||||||
/// between backends rather than typed as `winit::window::Window` directly:
|
/// between backends rather than typed as `winit::window::Window` directly:
|
||||||
/// android-view has no `Window` at all, and the redraw request there is a
|
/// android-view has no `Window` at all, and the redraw request there is a
|
||||||
|
|||||||
Reference in new issue
Block a user