Return the tabs demo to one example
This commit is contained in:
1 parent
213a0debb7
commit
e137f38a5d
14 files changed
+218
-326
No files matched your search
@@ -63,7 +63,7 @@ The release signing key lives at `~/.config/ai-app/release.jks`, never in the
|
|||||||
checkout. `build-apk.sh` creates it once. Normal builds use application id
|
checkout. `build-apk.sh` creates it once. Normal builds use application id
|
||||||
`com.example.aiapp`; benchmark builds add `.bench` and are built explicitly:
|
`com.example.aiapp`; benchmark builds add `.bench` and are built explicitly:
|
||||||
|
|
||||||
./build-apk.sh release --features "transcript-screen bench"
|
./build-apk.sh release --features "screens bench"
|
||||||
|
|
||||||
## Running the server
|
## Running the server
|
||||||
|
|
||||||
|
|||||||
Generated
-8
@@ -180,7 +180,6 @@ dependencies = [
|
|||||||
"pulldown-cmark",
|
"pulldown-cmark",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tabs-ui",
|
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"tokio",
|
"tokio",
|
||||||
"ureq",
|
"ureq",
|
||||||
@@ -3611,13 +3610,6 @@ dependencies = [
|
|||||||
"syn 2.0.119",
|
"syn 2.0.119",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tabs-ui"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"iris",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tempfile"
|
name = "tempfile"
|
||||||
version = "3.27.0"
|
version = "3.27.0"
|
||||||
|
|||||||
+1
-4
@@ -34,7 +34,6 @@ log = { version = "0.4.34", features = ["std"] }
|
|||||||
|
|
||||||
# UI dependencies stay optional so client-only tests do not link the renderer.
|
# UI dependencies stay optional so client-only tests do not link the renderer.
|
||||||
iris = { path = "../iris", optional = true }
|
iris = { path = "../iris", optional = true }
|
||||||
tabs-ui = { path = "../iris/tabs-ui", optional = true }
|
|
||||||
libc = { version = "0.2.189", optional = true }
|
libc = { version = "0.2.189", optional = true }
|
||||||
tokio = { version = "1.53.1", features = ["rt", "time"], optional = true }
|
tokio = { version = "1.53.1", features = ["rt", "time"], optional = true }
|
||||||
|
|
||||||
@@ -51,9 +50,7 @@ default = ["screens", "fixture"]
|
|||||||
screens = ["dep:iris"]
|
screens = ["dep:iris"]
|
||||||
# Default-on for tests; APK builds opt in so ordinary APKs omit the 1.9 MB fixture.
|
# Default-on for tests; APK builds opt in so ordinary APKs omit the 1.9 MB fixture.
|
||||||
fixture = ["screens"]
|
fixture = ["screens"]
|
||||||
transcript-screen = ["screens"]
|
bench = ["screens", "fixture", "dep:libc", "dep:tokio"]
|
||||||
tabs-screen = ["screens", "dep:tabs-ui"]
|
|
||||||
bench = ["transcript-screen", "fixture", "dep:libc", "dep:tokio"]
|
|
||||||
force-gles = ["screens", "iris/force-gles"]
|
force-gles = ["screens", "iris/force-gles"]
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|||||||
+3
-3
@@ -7,15 +7,15 @@
|
|||||||
# emulator stays on debug" rule -- pass `release` explicitly for a phone
|
# emulator stays on debug" rule -- pass `release` explicitly for a phone
|
||||||
# build). --abi defaults to arm64-v8a (a phone/real device); pass
|
# build). --abi defaults to arm64-v8a (a phone/real device); pass
|
||||||
# x86_64 for this checkout's own AVD. --features defaults to
|
# x86_64 for this checkout's own AVD. --features defaults to
|
||||||
# "transcript-screen". Pass "transcript-screen bench" for the retained
|
# "screens". Pass "screens bench" for the retained benchmark app. Never
|
||||||
# benchmark app. Never force GLES for a phone or emulator build; Iris's
|
# force GLES for a phone or emulator build; Iris's
|
||||||
# runtime selects the available hardware backend.
|
# runtime selects the available hardware backend.
|
||||||
set -eu
|
set -eu
|
||||||
cd "$(dirname "$0")"
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
BUILD_TYPE="debug"
|
BUILD_TYPE="debug"
|
||||||
ABI="arm64-v8a"
|
ABI="arm64-v8a"
|
||||||
FEATURES="transcript-screen"
|
FEATURES="screens"
|
||||||
case "${1:-}" in
|
case "${1:-}" in
|
||||||
debug|release) BUILD_TYPE="$1"; shift ;;
|
debug|release) BUILD_TYPE="$1"; shift ;;
|
||||||
esac
|
esac
|
||||||
|
|||||||
+1
-1
@@ -9,7 +9,7 @@
|
|||||||
# (android-project/app/build/outputs/apk/release/app-release.apk) if it
|
# (android-project/app/build/outputs/apk/release/app-release.apk) if it
|
||||||
# exists, else the
|
# exists, else the
|
||||||
# debug one. Build it first with:
|
# debug one. Build it first with:
|
||||||
# ./build-apk.sh release --features "transcript-screen bench"
|
# ./build-apk.sh release --features "screens bench"
|
||||||
set -eu
|
set -eu
|
||||||
cd "$(dirname "$0")"
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
|
|||||||
@@ -4,14 +4,9 @@
|
|||||||
use android_view::jni::JNIEnv;
|
use android_view::jni::JNIEnv;
|
||||||
use android_view::jni::objects::{JClass, JObject, JString};
|
use android_view::jni::objects::{JClass, JObject, JString};
|
||||||
use android_view::jni::sys::{jlong, jobjectArray};
|
use android_view::jni::sys::{jlong, jobjectArray};
|
||||||
#[cfg(feature = "transcript-screen")]
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::{ptr, sync::OnceLock};
|
use std::{ptr, sync::OnceLock};
|
||||||
|
|
||||||
/// Gated with its one reader: the tabs demo links no `client-core` and so
|
|
||||||
/// has no ring to lay out, and an ungated constant is a warning in that
|
|
||||||
/// build (`iris-android-app` without `transcript-screen`).
|
|
||||||
#[cfg(feature = "transcript-screen")]
|
|
||||||
const FIELDS_PER_LINE: usize = 5;
|
const FIELDS_PER_LINE: usize = 5;
|
||||||
|
|
||||||
/// The authority the provider registered itself under, once it has been
|
/// The authority the provider registered itself under, once it has been
|
||||||
@@ -43,12 +38,9 @@ pub extern "system" fn Java_dev_iris_android_demo_DevLogProvider_nativeReady(
|
|||||||
authority: JString,
|
authority: JString,
|
||||||
files_dir: JString,
|
files_dir: JString,
|
||||||
) {
|
) {
|
||||||
#[cfg(feature = "transcript-screen")]
|
|
||||||
if let Some(dir) = string_arg(&mut env, &files_dir) {
|
if let Some(dir) = string_arg(&mut env, &files_dir) {
|
||||||
crate::android::app_log::set_crash_dir(Path::new(&dir));
|
crate::android::app_log::set_crash_dir(Path::new(&dir));
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "transcript-screen"))]
|
|
||||||
let _ = &files_dir;
|
|
||||||
let Some(authority) = string_arg(&mut env, &authority) else {
|
let Some(authority) = string_arg(&mut env, &authority) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@@ -95,7 +87,6 @@ pub extern "system" fn Java_dev_iris_android_demo_DevLogProvider_nativeLinesSinc
|
|||||||
string_array(&mut env, &line_fields(since.max(0) as u64))
|
string_array(&mut env, &line_fields(since.max(0) as u64))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "transcript-screen")]
|
|
||||||
fn status_fields() -> Vec<String> {
|
fn status_fields() -> Vec<String> {
|
||||||
let ring = crate::client::log_ring::process_ring();
|
let ring = crate::client::log_ring::process_ring();
|
||||||
vec![
|
vec![
|
||||||
@@ -105,17 +96,6 @@ fn status_fields() -> Vec<String> {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The tabs demo links no `client-core` and keeps no ring, so it holds
|
|
||||||
/// nothing and has never dropped anything -- which is the truth, not a
|
|
||||||
/// stand-in. The natives are still exported there, because a `native`
|
|
||||||
/// method Java declares and the library does not is an
|
|
||||||
/// `UnsatisfiedLinkError` the moment the class loads.
|
|
||||||
#[cfg(not(feature = "transcript-screen"))]
|
|
||||||
fn status_fields() -> Vec<String> {
|
|
||||||
vec!["0".to_string(), "0".to_string(), "-1".to_string()]
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "transcript-screen")]
|
|
||||||
fn line_fields(since: u64) -> Vec<String> {
|
fn line_fields(since: u64) -> Vec<String> {
|
||||||
let (lines, _next) = crate::client::log_ring::process_ring().since(since);
|
let (lines, _next) = crate::client::log_ring::process_ring().since(since);
|
||||||
let mut fields = Vec::with_capacity(lines.len() * FIELDS_PER_LINE);
|
let mut fields = Vec::with_capacity(lines.len() * FIELDS_PER_LINE);
|
||||||
@@ -129,11 +109,6 @@ fn line_fields(since: u64) -> Vec<String> {
|
|||||||
fields
|
fields
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "transcript-screen"))]
|
|
||||||
fn line_fields(_since: u64) -> Vec<String> {
|
|
||||||
Vec::new()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Null rather than a panic across the JNI boundary: `DevLogProvider`
|
/// Null rather than a panic across the JNI boundary: `DevLogProvider`
|
||||||
/// reads it as "the provider could not answer" and returns no cursor,
|
/// reads it as "the provider could not answer" and returns no cursor,
|
||||||
/// which Dev Updater already draws as a distinct state. Taking the app
|
/// which Dev Updater already draws as a distinct state. Taking the app
|
||||||
|
|||||||
+3
-55
@@ -7,61 +7,26 @@ use android_view::{
|
|||||||
},
|
},
|
||||||
register_view_class,
|
register_view_class,
|
||||||
};
|
};
|
||||||
#[cfg(not(feature = "transcript-screen"))]
|
|
||||||
use iris::{
|
|
||||||
android::{AndroidAppState, AndroidUiState},
|
|
||||||
macros::AndroidUiState,
|
|
||||||
rsc::StdRsc,
|
|
||||||
};
|
|
||||||
use log::LevelFilter;
|
use log::LevelFilter;
|
||||||
use std::ffi::c_void;
|
use std::ffi::c_void;
|
||||||
#[cfg(feature = "transcript-screen")]
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
/// The app's own log ring and its upload -- only where `client-core` is
|
/// The app's own log ring and its upload.
|
||||||
/// linked, which is every build that has a server to send to. The plain
|
|
||||||
/// tabs demo keeps `android_logger` alone, as it always had.
|
|
||||||
#[cfg(feature = "transcript-screen")]
|
|
||||||
mod app_log;
|
mod app_log;
|
||||||
#[cfg(feature = "bench")]
|
#[cfg(feature = "bench")]
|
||||||
mod bench_client;
|
mod bench_client;
|
||||||
#[cfg(feature = "bench")]
|
#[cfg(feature = "bench")]
|
||||||
mod bench_jni;
|
mod bench_jni;
|
||||||
mod devlog;
|
mod devlog;
|
||||||
#[cfg(feature = "transcript-screen")]
|
|
||||||
mod enrollment;
|
mod enrollment;
|
||||||
#[cfg(all(feature = "transcript-screen", not(feature = "bench")))]
|
#[cfg(not(feature = "bench"))]
|
||||||
mod transcript_client;
|
mod transcript_client;
|
||||||
|
|
||||||
/// The app's `View` subclass, matching the Java side's package --
|
/// The app's `View` subclass, matching the Java side's package --
|
||||||
/// `app/src/main/java/dev/iris/android/demo/IrisView.java`.
|
/// `app/src/main/java/dev/iris/android/demo/IrisView.java`.
|
||||||
const VIEW_CLASS: &str = "dev/iris/android/demo/IrisView";
|
const VIEW_CLASS: &str = "dev/iris/android/demo/IrisView";
|
||||||
|
|
||||||
#[cfg(not(feature = "transcript-screen"))]
|
#[cfg(not(feature = "bench"))]
|
||||||
#[derive(AndroidUiState)]
|
|
||||||
pub struct Client {
|
|
||||||
ui_state: AndroidUiState,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "transcript-screen"))]
|
|
||||||
impl AndroidAppState for Client {
|
|
||||||
fn new(mut ui_state: AndroidUiState, rsc: &mut StdRsc<Self>) -> Self {
|
|
||||||
// `widgets.info` is the winit example's frame-debug readout, kept
|
|
||||||
// current from `DesktopAppState::window_event` -- android-view has
|
|
||||||
// no per-frame hook to drive the equivalent from here yet, so it
|
|
||||||
// is left at its built "" text rather than wired to nothing.
|
|
||||||
let _ = tabs_ui::build(rsc, &mut ui_state);
|
|
||||||
Self { ui_state }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn back_pressed(&mut self, _rsc: &mut StdRsc<Self>) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "transcript-screen"))]
|
|
||||||
type ActiveClient = Client;
|
|
||||||
#[cfg(all(feature = "transcript-screen", not(feature = "bench")))]
|
|
||||||
type ActiveClient = transcript_client::TranscriptClient;
|
type ActiveClient = transcript_client::TranscriptClient;
|
||||||
#[cfg(feature = "bench")]
|
#[cfg(feature = "bench")]
|
||||||
type ActiveClient = bench_client::BenchClient;
|
type ActiveClient = bench_client::BenchClient;
|
||||||
@@ -79,18 +44,7 @@ extern "system" fn new_view_peer<'local>(
|
|||||||
/// mirrors android-view's own demo, which carries the same comment.
|
/// mirrors android-view's own demo, which carries the same comment.
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub unsafe extern "system" fn JNI_OnLoad(vm: *mut RawJavaVM, _: *mut c_void) -> jint {
|
pub unsafe extern "system" fn JNI_OnLoad(vm: *mut RawJavaVM, _: *mut c_void) -> jint {
|
||||||
// The ring in front of `android_logger` where there is one (see
|
|
||||||
// `app_log`), and `android_logger` alone otherwise. Both install the
|
|
||||||
// same tag and level, so `logcat` cannot tell the two builds apart --
|
|
||||||
// the ring only adds a second reader.
|
|
||||||
#[cfg(feature = "transcript-screen")]
|
|
||||||
app_log::install(LevelFilter::Debug);
|
app_log::install(LevelFilter::Debug);
|
||||||
#[cfg(not(feature = "transcript-screen"))]
|
|
||||||
android_logger::init_once(
|
|
||||||
android_logger::Config::default()
|
|
||||||
.with_max_level(LevelFilter::Debug)
|
|
||||||
.with_tag("iris-android-app"),
|
|
||||||
);
|
|
||||||
let vm = unsafe { JavaVM::from_raw(vm) }.unwrap();
|
let vm = unsafe { JavaVM::from_raw(vm) }.unwrap();
|
||||||
let mut env = vm.get_env().unwrap();
|
let mut env = vm.get_env().unwrap();
|
||||||
register_view_class(&mut env, VIEW_CLASS, new_view_peer);
|
register_view_class(&mut env, VIEW_CLASS, new_view_peer);
|
||||||
@@ -118,11 +72,8 @@ pub extern "system" fn Java_dev_iris_android_demo_MainActivity_nativeSetFilesDir
|
|||||||
let Some(dir) = jstring(&mut env, dir) else {
|
let Some(dir) = jstring(&mut env, dir) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
#[cfg(feature = "transcript-screen")]
|
|
||||||
{
|
|
||||||
app_log::set_crash_dir(Path::new(&dir));
|
app_log::set_crash_dir(Path::new(&dir));
|
||||||
enrollment::set_files_dir(PathBuf::from(&dir));
|
enrollment::set_files_dir(PathBuf::from(&dir));
|
||||||
}
|
|
||||||
log::debug!("iris app: files directory is {dir}");
|
log::debug!("iris app: files directory is {dir}");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,15 +88,12 @@ pub extern "system" fn Java_dev_iris_android_demo_MainActivity_nativeEnroll(
|
|||||||
let Some(uri) = jstring(&mut env, uri) else {
|
let Some(uri) = jstring(&mut env, uri) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
#[cfg(feature = "transcript-screen")]
|
|
||||||
match enrollment::apply_link(&uri) {
|
match enrollment::apply_link(&uri) {
|
||||||
// Never the token: `wg-app-link`'s enroll module forbids logging
|
// Never the token: `wg-app-link`'s enroll module forbids logging
|
||||||
// it, and this line would otherwise be the one place it leaked.
|
// it, and this line would otherwise be the one place it leaked.
|
||||||
Ok(server) => log::info!("iris app: enrolled with {}:{}", server.host, server.port),
|
Ok(server) => log::info!("iris app: enrolled with {}:{}", server.host, server.port),
|
||||||
Err(error) => log::warn!("iris app: that enrolment link was refused -- {error}"),
|
Err(error) => log::warn!("iris app: that enrolment link was refused -- {error}"),
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "transcript-screen"))]
|
|
||||||
log::warn!("iris app: {uri} arrived, but this build has no server to enrol with");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn jstring(env: &mut JNIEnv, value: JString) -> Option<String> {
|
fn jstring(env: &mut JNIEnv, value: JString) -> Option<String> {
|
||||||
|
|||||||
+1
-1
@@ -26,7 +26,7 @@ open work and measured constraints that are expensive to rediscover.
|
|||||||
The application crate has an rlib for tests and desktop and a cdylib for
|
The application crate has an rlib for tests and desktop and a cdylib for
|
||||||
Android. Features select optional surfaces: `screens` links Iris, `fixture`
|
Android. Features select optional surfaces: `screens` links Iris, `fixture`
|
||||||
embeds the synthetic transcript, and `bench` adds the benchmark runner. An
|
embeds the synthetic transcript, and `bench` adds the benchmark runner. An
|
||||||
ordinary APK uses `transcript-screen`, not `bench`.
|
ordinary APK uses `screens`; only a benchmark build adds `bench`.
|
||||||
|
|
||||||
## Current state
|
## Current state
|
||||||
|
|
||||||
|
|||||||
Generated
-8
@@ -1550,7 +1550,6 @@ dependencies = [
|
|||||||
"pollster",
|
"pollster",
|
||||||
"send_wrapper",
|
"send_wrapper",
|
||||||
"swash",
|
"swash",
|
||||||
"tabs-ui",
|
|
||||||
"tokio",
|
"tokio",
|
||||||
"wgpu",
|
"wgpu",
|
||||||
"winit",
|
"winit",
|
||||||
@@ -3316,13 +3315,6 @@ dependencies = [
|
|||||||
"syn 2.0.119",
|
"syn 2.0.119",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tabs-ui"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"iris",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tempfile"
|
name = "tempfile"
|
||||||
version = "3.25.0"
|
version = "3.25.0"
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ force-gles = []
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tokio = { workspace = true, features = ["sync", "rt", "rt-multi-thread", "time"] }
|
tokio = { workspace = true, features = ["sync", "rt", "rt-multi-thread", "time"] }
|
||||||
tabs-ui = { path = "tabs-ui" }
|
|
||||||
bytemuck = { workspace = true }
|
bytemuck = { workspace = true }
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
@@ -47,7 +46,6 @@ harness = false
|
|||||||
members = [
|
members = [
|
||||||
"core",
|
"core",
|
||||||
"macro",
|
"macro",
|
||||||
"tabs-ui",
|
|
||||||
"rig-input",
|
"rig-input",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
File renamed without changes.
+208
-2
@@ -1,4 +1,5 @@
|
|||||||
use iris::prelude::*;
|
use iris::prelude::*;
|
||||||
|
use std::{cell::RefCell, rc::Rc};
|
||||||
use winit::event::WindowEvent;
|
use winit::event::WindowEvent;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
@@ -6,14 +7,14 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(DesktopUiState)]
|
#[derive(DesktopUiState)]
|
||||||
pub struct Client {
|
struct Client {
|
||||||
ui_state: DesktopUiState,
|
ui_state: DesktopUiState,
|
||||||
info: WeakWidget<Text>,
|
info: WeakWidget<Text>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DesktopAppState for Client {
|
impl DesktopAppState for Client {
|
||||||
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 widgets = tabs_ui::build(rsc, &mut ui_state);
|
let widgets = build(rsc, &mut ui_state);
|
||||||
Self {
|
Self {
|
||||||
ui_state,
|
ui_state,
|
||||||
info: widgets.info,
|
info: widgets.info,
|
||||||
@@ -33,3 +34,208 @@ impl DesktopAppState for Client {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct ClientWidgets {
|
||||||
|
info: WeakWidget<Text>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build<Rsc: HasEvents>(rsc: &mut Rsc, ui_state: &mut impl HasRoot<Rsc>) -> ClientWidgets
|
||||||
|
where
|
||||||
|
Rsc::State: FocusHost,
|
||||||
|
{
|
||||||
|
let rrect = rect(PaintId::WHITE).radius(20);
|
||||||
|
let pad_test = (
|
||||||
|
rrect.clone().color(PaintId::BLUE),
|
||||||
|
(
|
||||||
|
rrect
|
||||||
|
.clone()
|
||||||
|
.color(PaintId::RED)
|
||||||
|
.sized((100, 100))
|
||||||
|
.center()
|
||||||
|
.width(rest(2)),
|
||||||
|
(
|
||||||
|
rrect.clone().color(PaintId::ORANGE),
|
||||||
|
rrect.clone().color(PaintId::LIME).pad(10.0),
|
||||||
|
)
|
||||||
|
.span(Dir::RIGHT)
|
||||||
|
.width(rest(2)),
|
||||||
|
rrect.clone().color(PaintId::YELLOW),
|
||||||
|
)
|
||||||
|
.span(Dir::RIGHT)
|
||||||
|
.pad(10)
|
||||||
|
.width(rest(3)),
|
||||||
|
)
|
||||||
|
.span(Dir::RIGHT)
|
||||||
|
.add(rsc);
|
||||||
|
|
||||||
|
let span_test = (
|
||||||
|
rrect.clone().color(PaintId::GREEN).width(100),
|
||||||
|
rrect.clone().color(PaintId::ORANGE),
|
||||||
|
rrect.clone().color(PaintId::CYAN),
|
||||||
|
rrect.clone().color(PaintId::BLUE).width(rel(0.5)),
|
||||||
|
rrect.clone().color(PaintId::MAGENTA).width(100),
|
||||||
|
rrect.color(PaintId::RED).width(100),
|
||||||
|
)
|
||||||
|
.span(Dir::LEFT)
|
||||||
|
.add(rsc);
|
||||||
|
|
||||||
|
let span_add = Span::empty(Dir::RIGHT).add(rsc);
|
||||||
|
|
||||||
|
let add_button = rect(PaintId::LIME)
|
||||||
|
.radius(30)
|
||||||
|
.on(CursorSense::click(), move |_, rsc| {
|
||||||
|
let child = image(include_bytes!("assets/sungals.png"))
|
||||||
|
.center()
|
||||||
|
.add_strong(rsc);
|
||||||
|
span_add(rsc).push(child);
|
||||||
|
})
|
||||||
|
.sized((150, 150))
|
||||||
|
.align(Align::BOT_RIGHT);
|
||||||
|
|
||||||
|
let del_button = rect(PaintId::RED)
|
||||||
|
.radius(30)
|
||||||
|
.on(CursorSense::click(), move |_, rsc| {
|
||||||
|
span_add(rsc).pop();
|
||||||
|
})
|
||||||
|
.sized((150, 150))
|
||||||
|
.align(Align::BOT_LEFT);
|
||||||
|
|
||||||
|
let span_add_test = (span_add, add_button, del_button).stack().add(rsc);
|
||||||
|
|
||||||
|
let btext = |content| wtext(content).size(30);
|
||||||
|
|
||||||
|
let text_test = (
|
||||||
|
btext("this is a").align(Align::LEFT),
|
||||||
|
btext("teeeeeeeest").align(Align::RIGHT),
|
||||||
|
btext("okkk\nokkkkkk!").align(Align::LEFT),
|
||||||
|
btext("hmm"),
|
||||||
|
btext("a"),
|
||||||
|
(
|
||||||
|
btext("'").family(Family::Monospace).align(Align::TOP),
|
||||||
|
btext("'").family(Family::Monospace),
|
||||||
|
btext(":gamer mode").family(Family::Monospace),
|
||||||
|
rect(PaintId::CYAN).sized((10, 10)).center(),
|
||||||
|
rect(PaintId::RED).sized((100, 100)).center(),
|
||||||
|
rect(PaintId::PURPLE).sized((50, 50)).align(Align::TOP),
|
||||||
|
)
|
||||||
|
.span(Dir::RIGHT)
|
||||||
|
.center(),
|
||||||
|
wtext("pretty cool right?").size(50),
|
||||||
|
)
|
||||||
|
.span(Dir::DOWN)
|
||||||
|
.add(rsc);
|
||||||
|
|
||||||
|
let texts = Span::empty(Dir::DOWN).gap(10).add(rsc);
|
||||||
|
let msg_area = texts
|
||||||
|
.scrollable(Axis::Y, Pin::Start)
|
||||||
|
.masked()
|
||||||
|
.background(rect(PaintId::SKY));
|
||||||
|
let add_text = wtext("add")
|
||||||
|
.editable(EditMode::MultiLine)
|
||||||
|
.text_align(Align::LEFT)
|
||||||
|
.size(30)
|
||||||
|
.attr::<Selectable>(())
|
||||||
|
.on(Submit, move |ctx, rsc: &mut Rsc| {
|
||||||
|
let w = ctx.widget;
|
||||||
|
let content = w.edit(rsc).take();
|
||||||
|
let text = wtext(content)
|
||||||
|
.editable(EditMode::MultiLine)
|
||||||
|
.size(30)
|
||||||
|
.text_align(Align::LEFT)
|
||||||
|
.wrap(true)
|
||||||
|
.attr::<Selectable>(());
|
||||||
|
let fill = rsc
|
||||||
|
.ui_mut()
|
||||||
|
.paints
|
||||||
|
.add(Srgba8::WHITE.to_linear().darker(0.5));
|
||||||
|
let msg_box = text.background(rect(fill)).add_strong(rsc);
|
||||||
|
texts(rsc).push(msg_box);
|
||||||
|
})
|
||||||
|
.add(rsc);
|
||||||
|
|
||||||
|
let text_edit_scroll = (
|
||||||
|
msg_area.height(rest(1)),
|
||||||
|
(
|
||||||
|
Rect::new(
|
||||||
|
rsc.ui_mut()
|
||||||
|
.paints
|
||||||
|
.add(Srgba8::WHITE.to_linear().darker(0.9)),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
add_text.width(rest(1)),
|
||||||
|
Rect::new(PaintId::GREEN)
|
||||||
|
.on(CursorSense::click(), move |ctx, rsc: &mut Rsc| {
|
||||||
|
rsc.run_event::<Submit>(add_text, (), ctx.state);
|
||||||
|
})
|
||||||
|
.sized((40, 40)),
|
||||||
|
)
|
||||||
|
.span(Dir::RIGHT)
|
||||||
|
.pad(10),
|
||||||
|
)
|
||||||
|
.stack()
|
||||||
|
.size(StackSize::Child(1))
|
||||||
|
.layer_offset(1)
|
||||||
|
.align(Align::BOT),
|
||||||
|
)
|
||||||
|
.span(Dir::DOWN)
|
||||||
|
.add(rsc);
|
||||||
|
|
||||||
|
let main = WidgetPtr::new().add(rsc);
|
||||||
|
|
||||||
|
let vals = Rc::new(RefCell::new((0, Vec::new())));
|
||||||
|
let mut switch_button = |solid: Srgba8, to: WeakWidget, label| {
|
||||||
|
let value = solid.to_linear();
|
||||||
|
let paint = rsc.ui_mut().paints.add(value);
|
||||||
|
let to = to.upgrade(rsc);
|
||||||
|
let vec = &mut vals.borrow_mut().1;
|
||||||
|
let i = vec.len();
|
||||||
|
if vec.is_empty() {
|
||||||
|
vec.push(None);
|
||||||
|
main(rsc).set(to);
|
||||||
|
} else {
|
||||||
|
vec.push(Some(to));
|
||||||
|
}
|
||||||
|
let vals = vals.clone();
|
||||||
|
let pressed = paint.clone();
|
||||||
|
let hovered = paint.clone();
|
||||||
|
let normal = paint.clone();
|
||||||
|
let rect = rect(paint)
|
||||||
|
.on(CursorSense::click(), move |_ctx, rsc: &mut Rsc| {
|
||||||
|
let (prev, vec) = &mut *vals.borrow_mut();
|
||||||
|
if let Some(h) = vec[i].take() {
|
||||||
|
vec[*prev] = main(rsc).replace(h);
|
||||||
|
*prev = i;
|
||||||
|
}
|
||||||
|
rsc.ui_mut().paints.set(&pressed, value.darker(0.3));
|
||||||
|
})
|
||||||
|
.on(
|
||||||
|
CursorSense::HoverStart | CursorSense::unclick(),
|
||||||
|
move |_ctx, rsc: &mut Rsc| {
|
||||||
|
rsc.ui_mut().paints.set(&hovered, value.brighter(0.2));
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.on(CursorSense::HoverEnd, move |_ctx, rsc: &mut Rsc| {
|
||||||
|
rsc.ui_mut().paints.set(&normal, value);
|
||||||
|
})
|
||||||
|
.label(label);
|
||||||
|
(rect, wtext(label).size(30).text_align(Align::CENTER)).stack()
|
||||||
|
};
|
||||||
|
|
||||||
|
let tabs = (
|
||||||
|
switch_button(Srgba8::RED, pad_test, "pad"),
|
||||||
|
switch_button(Srgba8::GREEN, span_test, "span"),
|
||||||
|
switch_button(Srgba8::BLUE, span_add_test, "image span"),
|
||||||
|
switch_button(Srgba8::MAGENTA, text_test, "text layout"),
|
||||||
|
switch_button(Srgba8::YELLOW, text_edit_scroll, "text edit scroll"),
|
||||||
|
)
|
||||||
|
.span(Dir::RIGHT);
|
||||||
|
|
||||||
|
let info = wtext("").add(rsc);
|
||||||
|
let info_sect = info.pad(10).align(Align::RIGHT);
|
||||||
|
|
||||||
|
((tabs.height(40), main.pad(10)).span(Dir::DOWN), info_sect)
|
||||||
|
.stack()
|
||||||
|
.set_root(rsc, ui_state);
|
||||||
|
|
||||||
|
ClientWidgets { info }
|
||||||
|
}
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "tabs-ui"
|
|
||||||
version.workspace = true
|
|
||||||
edition.workspace = true
|
|
||||||
|
|
||||||
# Shared demo content for the desktop and Android example entry points.
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
iris = { path = ".." }
|
|
||||||
@@ -1,207 +0,0 @@
|
|||||||
use iris::prelude::*;
|
|
||||||
use std::{cell::RefCell, rc::Rc};
|
|
||||||
|
|
||||||
pub struct ClientWidgets {
|
|
||||||
pub info: WeakWidget<Text>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn build<Rsc: HasEvents>(rsc: &mut Rsc, ui_state: &mut impl HasRoot<Rsc>) -> ClientWidgets
|
|
||||||
where
|
|
||||||
Rsc::State: FocusHost,
|
|
||||||
{
|
|
||||||
let rrect = rect(PaintId::WHITE).radius(20);
|
|
||||||
let pad_test = (
|
|
||||||
rrect.clone().color(PaintId::BLUE),
|
|
||||||
(
|
|
||||||
rrect
|
|
||||||
.clone()
|
|
||||||
.color(PaintId::RED)
|
|
||||||
.sized((100, 100))
|
|
||||||
.center()
|
|
||||||
.width(rest(2)),
|
|
||||||
(
|
|
||||||
rrect.clone().color(PaintId::ORANGE),
|
|
||||||
rrect.clone().color(PaintId::LIME).pad(10.0),
|
|
||||||
)
|
|
||||||
.span(Dir::RIGHT)
|
|
||||||
.width(rest(2)),
|
|
||||||
rrect.clone().color(PaintId::YELLOW),
|
|
||||||
)
|
|
||||||
.span(Dir::RIGHT)
|
|
||||||
.pad(10)
|
|
||||||
.width(rest(3)),
|
|
||||||
)
|
|
||||||
.span(Dir::RIGHT)
|
|
||||||
.add(rsc);
|
|
||||||
|
|
||||||
let span_test = (
|
|
||||||
rrect.clone().color(PaintId::GREEN).width(100),
|
|
||||||
rrect.clone().color(PaintId::ORANGE),
|
|
||||||
rrect.clone().color(PaintId::CYAN),
|
|
||||||
rrect.clone().color(PaintId::BLUE).width(rel(0.5)),
|
|
||||||
rrect.clone().color(PaintId::MAGENTA).width(100),
|
|
||||||
rrect.color(PaintId::RED).width(100),
|
|
||||||
)
|
|
||||||
.span(Dir::LEFT)
|
|
||||||
.add(rsc);
|
|
||||||
|
|
||||||
let span_add = Span::empty(Dir::RIGHT).add(rsc);
|
|
||||||
|
|
||||||
let add_button = rect(PaintId::LIME)
|
|
||||||
.radius(30)
|
|
||||||
.on(CursorSense::click(), move |_, rsc| {
|
|
||||||
let child = image(include_bytes!("../assets/sungals.png"))
|
|
||||||
.center()
|
|
||||||
.add_strong(rsc);
|
|
||||||
span_add(rsc).push(child);
|
|
||||||
})
|
|
||||||
.sized((150, 150))
|
|
||||||
.align(Align::BOT_RIGHT);
|
|
||||||
|
|
||||||
let del_button = rect(PaintId::RED)
|
|
||||||
.radius(30)
|
|
||||||
.on(CursorSense::click(), move |_, rsc| {
|
|
||||||
span_add(rsc).pop();
|
|
||||||
})
|
|
||||||
.sized((150, 150))
|
|
||||||
.align(Align::BOT_LEFT);
|
|
||||||
|
|
||||||
let span_add_test = (span_add, add_button, del_button).stack().add(rsc);
|
|
||||||
|
|
||||||
let btext = |content| wtext(content).size(30);
|
|
||||||
|
|
||||||
let text_test = (
|
|
||||||
btext("this is a").align(Align::LEFT),
|
|
||||||
btext("teeeeeeeest").align(Align::RIGHT),
|
|
||||||
btext("okkk\nokkkkkk!").align(Align::LEFT),
|
|
||||||
btext("hmm"),
|
|
||||||
btext("a"),
|
|
||||||
(
|
|
||||||
btext("'").family(Family::Monospace).align(Align::TOP),
|
|
||||||
btext("'").family(Family::Monospace),
|
|
||||||
btext(":gamer mode").family(Family::Monospace),
|
|
||||||
rect(PaintId::CYAN).sized((10, 10)).center(),
|
|
||||||
rect(PaintId::RED).sized((100, 100)).center(),
|
|
||||||
rect(PaintId::PURPLE).sized((50, 50)).align(Align::TOP),
|
|
||||||
)
|
|
||||||
.span(Dir::RIGHT)
|
|
||||||
.center(),
|
|
||||||
wtext("pretty cool right?").size(50),
|
|
||||||
)
|
|
||||||
.span(Dir::DOWN)
|
|
||||||
.add(rsc);
|
|
||||||
|
|
||||||
let texts = Span::empty(Dir::DOWN).gap(10).add(rsc);
|
|
||||||
let msg_area = texts
|
|
||||||
.scrollable(Axis::Y, Pin::Start)
|
|
||||||
.masked()
|
|
||||||
.background(rect(PaintId::SKY));
|
|
||||||
let add_text = wtext("add")
|
|
||||||
.editable(EditMode::MultiLine)
|
|
||||||
.text_align(Align::LEFT)
|
|
||||||
.size(30)
|
|
||||||
.attr::<Selectable>(())
|
|
||||||
.on(Submit, move |ctx, rsc: &mut Rsc| {
|
|
||||||
let w = ctx.widget;
|
|
||||||
let content = w.edit(rsc).take();
|
|
||||||
let text = wtext(content)
|
|
||||||
.editable(EditMode::MultiLine)
|
|
||||||
.size(30)
|
|
||||||
.text_align(Align::LEFT)
|
|
||||||
.wrap(true)
|
|
||||||
.attr::<Selectable>(());
|
|
||||||
let fill = rsc
|
|
||||||
.ui_mut()
|
|
||||||
.paints
|
|
||||||
.add(Srgba8::WHITE.to_linear().darker(0.5));
|
|
||||||
let msg_box = text.background(rect(fill)).add_strong(rsc);
|
|
||||||
texts(rsc).push(msg_box);
|
|
||||||
})
|
|
||||||
.add(rsc);
|
|
||||||
|
|
||||||
let text_edit_scroll = (
|
|
||||||
msg_area.height(rest(1)),
|
|
||||||
(
|
|
||||||
Rect::new(
|
|
||||||
rsc.ui_mut()
|
|
||||||
.paints
|
|
||||||
.add(Srgba8::WHITE.to_linear().darker(0.9)),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
add_text.width(rest(1)),
|
|
||||||
Rect::new(PaintId::GREEN)
|
|
||||||
.on(CursorSense::click(), move |ctx, rsc: &mut Rsc| {
|
|
||||||
rsc.run_event::<Submit>(add_text, (), ctx.state);
|
|
||||||
})
|
|
||||||
.sized((40, 40)),
|
|
||||||
)
|
|
||||||
.span(Dir::RIGHT)
|
|
||||||
.pad(10),
|
|
||||||
)
|
|
||||||
.stack()
|
|
||||||
.size(StackSize::Child(1))
|
|
||||||
.layer_offset(1)
|
|
||||||
.align(Align::BOT),
|
|
||||||
)
|
|
||||||
.span(Dir::DOWN)
|
|
||||||
.add(rsc);
|
|
||||||
|
|
||||||
let main = WidgetPtr::new().add(rsc);
|
|
||||||
|
|
||||||
let vals = Rc::new(RefCell::new((0, Vec::new())));
|
|
||||||
let mut switch_button = |solid: Srgba8, to: WeakWidget, label| {
|
|
||||||
let value = solid.to_linear();
|
|
||||||
let paint = rsc.ui_mut().paints.add(value);
|
|
||||||
let to = to.upgrade(rsc);
|
|
||||||
let vec = &mut vals.borrow_mut().1;
|
|
||||||
let i = vec.len();
|
|
||||||
if vec.is_empty() {
|
|
||||||
vec.push(None);
|
|
||||||
main(rsc).set(to);
|
|
||||||
} else {
|
|
||||||
vec.push(Some(to));
|
|
||||||
}
|
|
||||||
let vals = vals.clone();
|
|
||||||
let pressed = paint.clone();
|
|
||||||
let hovered = paint.clone();
|
|
||||||
let normal = paint.clone();
|
|
||||||
let rect = rect(paint)
|
|
||||||
.on(CursorSense::click(), move |_ctx, rsc: &mut Rsc| {
|
|
||||||
let (prev, vec) = &mut *vals.borrow_mut();
|
|
||||||
if let Some(h) = vec[i].take() {
|
|
||||||
vec[*prev] = main(rsc).replace(h);
|
|
||||||
*prev = i;
|
|
||||||
}
|
|
||||||
rsc.ui_mut().paints.set(&pressed, value.darker(0.3));
|
|
||||||
})
|
|
||||||
.on(
|
|
||||||
CursorSense::HoverStart | CursorSense::unclick(),
|
|
||||||
move |_ctx, rsc: &mut Rsc| {
|
|
||||||
rsc.ui_mut().paints.set(&hovered, value.brighter(0.2));
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.on(CursorSense::HoverEnd, move |_ctx, rsc: &mut Rsc| {
|
|
||||||
rsc.ui_mut().paints.set(&normal, value);
|
|
||||||
})
|
|
||||||
.label(label);
|
|
||||||
(rect, wtext(label).size(30).text_align(Align::CENTER)).stack()
|
|
||||||
};
|
|
||||||
|
|
||||||
let tabs = (
|
|
||||||
switch_button(Srgba8::RED, pad_test, "pad"),
|
|
||||||
switch_button(Srgba8::GREEN, span_test, "span"),
|
|
||||||
switch_button(Srgba8::BLUE, span_add_test, "image span"),
|
|
||||||
switch_button(Srgba8::MAGENTA, text_test, "text layout"),
|
|
||||||
switch_button(Srgba8::YELLOW, text_edit_scroll, "text edit scroll"),
|
|
||||||
)
|
|
||||||
.span(Dir::RIGHT);
|
|
||||||
|
|
||||||
let info = wtext("").add(rsc);
|
|
||||||
let info_sect = info.pad(10).align(Align::RIGHT);
|
|
||||||
|
|
||||||
((tabs.height(40), main.pad(10)).span(Dir::DOWN), info_sect)
|
|
||||||
.stack()
|
|
||||||
.set_root(rsc, ui_state);
|
|
||||||
|
|
||||||
ClientWidgets { info }
|
|
||||||
}
|
|
||||||
Reference in new issue
Block a user