Return the tabs demo to one example
This commit is contained in:
1 parent
213a0debb7
commit
e137f38a5d
14 files changed
+220
-328
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
|
||||
`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
|
||||
|
||||
|
||||
Generated
-8
@@ -180,7 +180,6 @@ dependencies = [
|
||||
"pulldown-cmark",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tabs-ui",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
"ureq",
|
||||
@@ -3611,13 +3610,6 @@ dependencies = [
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tabs-ui"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"iris",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
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.
|
||||
iris = { path = "../iris", optional = true }
|
||||
tabs-ui = { path = "../iris/tabs-ui", optional = true }
|
||||
libc = { version = "0.2.189", optional = true }
|
||||
tokio = { version = "1.53.1", features = ["rt", "time"], optional = true }
|
||||
|
||||
@@ -51,9 +50,7 @@ default = ["screens", "fixture"]
|
||||
screens = ["dep:iris"]
|
||||
# Default-on for tests; APK builds opt in so ordinary APKs omit the 1.9 MB fixture.
|
||||
fixture = ["screens"]
|
||||
transcript-screen = ["screens"]
|
||||
tabs-screen = ["screens", "dep:tabs-ui"]
|
||||
bench = ["transcript-screen", "fixture", "dep:libc", "dep:tokio"]
|
||||
bench = ["screens", "fixture", "dep:libc", "dep:tokio"]
|
||||
force-gles = ["screens", "iris/force-gles"]
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
+3
-3
@@ -7,15 +7,15 @@
|
||||
# emulator stays on debug" rule -- pass `release` explicitly for a phone
|
||||
# build). --abi defaults to arm64-v8a (a phone/real device); pass
|
||||
# x86_64 for this checkout's own AVD. --features defaults to
|
||||
# "transcript-screen". Pass "transcript-screen bench" for the retained
|
||||
# benchmark app. Never force GLES for a phone or emulator build; Iris's
|
||||
# "screens". Pass "screens bench" for the retained benchmark app. Never
|
||||
# force GLES for a phone or emulator build; Iris's
|
||||
# runtime selects the available hardware backend.
|
||||
set -eu
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
BUILD_TYPE="debug"
|
||||
ABI="arm64-v8a"
|
||||
FEATURES="transcript-screen"
|
||||
FEATURES="screens"
|
||||
case "${1:-}" in
|
||||
debug|release) BUILD_TYPE="$1"; shift ;;
|
||||
esac
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@
|
||||
# (android-project/app/build/outputs/apk/release/app-release.apk) if it
|
||||
# exists, else the
|
||||
# debug one. Build it first with:
|
||||
# ./build-apk.sh release --features "transcript-screen bench"
|
||||
# ./build-apk.sh release --features "screens bench"
|
||||
set -eu
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
|
||||
@@ -4,14 +4,9 @@
|
||||
use android_view::jni::JNIEnv;
|
||||
use android_view::jni::objects::{JClass, JObject, JString};
|
||||
use android_view::jni::sys::{jlong, jobjectArray};
|
||||
#[cfg(feature = "transcript-screen")]
|
||||
use std::path::Path;
|
||||
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;
|
||||
|
||||
/// 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,
|
||||
files_dir: JString,
|
||||
) {
|
||||
#[cfg(feature = "transcript-screen")]
|
||||
if let Some(dir) = string_arg(&mut env, &files_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 {
|
||||
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))
|
||||
}
|
||||
|
||||
#[cfg(feature = "transcript-screen")]
|
||||
fn status_fields() -> Vec<String> {
|
||||
let ring = crate::client::log_ring::process_ring();
|
||||
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> {
|
||||
let (lines, _next) = crate::client::log_ring::process_ring().since(since);
|
||||
let mut fields = Vec::with_capacity(lines.len() * FIELDS_PER_LINE);
|
||||
@@ -129,11 +109,6 @@ fn line_fields(since: u64) -> Vec<String> {
|
||||
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`
|
||||
/// reads it as "the provider could not answer" and returns no cursor,
|
||||
/// which Dev Updater already draws as a distinct state. Taking the app
|
||||
|
||||
+5
-57
@@ -7,61 +7,26 @@ use android_view::{
|
||||
},
|
||||
register_view_class,
|
||||
};
|
||||
#[cfg(not(feature = "transcript-screen"))]
|
||||
use iris::{
|
||||
android::{AndroidAppState, AndroidUiState},
|
||||
macros::AndroidUiState,
|
||||
rsc::StdRsc,
|
||||
};
|
||||
use log::LevelFilter;
|
||||
use std::ffi::c_void;
|
||||
#[cfg(feature = "transcript-screen")]
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
/// The app's own log ring and its upload -- only where `client-core` is
|
||||
/// 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")]
|
||||
/// The app's own log ring and its upload.
|
||||
mod app_log;
|
||||
#[cfg(feature = "bench")]
|
||||
mod bench_client;
|
||||
#[cfg(feature = "bench")]
|
||||
mod bench_jni;
|
||||
mod devlog;
|
||||
#[cfg(feature = "transcript-screen")]
|
||||
mod enrollment;
|
||||
#[cfg(all(feature = "transcript-screen", not(feature = "bench")))]
|
||||
#[cfg(not(feature = "bench"))]
|
||||
mod transcript_client;
|
||||
|
||||
/// The app's `View` subclass, matching the Java side's package --
|
||||
/// `app/src/main/java/dev/iris/android/demo/IrisView.java`.
|
||||
const VIEW_CLASS: &str = "dev/iris/android/demo/IrisView";
|
||||
|
||||
#[cfg(not(feature = "transcript-screen"))]
|
||||
#[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")))]
|
||||
#[cfg(not(feature = "bench"))]
|
||||
type ActiveClient = transcript_client::TranscriptClient;
|
||||
#[cfg(feature = "bench")]
|
||||
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.
|
||||
#[unsafe(no_mangle)]
|
||||
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);
|
||||
#[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 mut env = vm.get_env().unwrap();
|
||||
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 {
|
||||
return;
|
||||
};
|
||||
#[cfg(feature = "transcript-screen")]
|
||||
{
|
||||
app_log::set_crash_dir(Path::new(&dir));
|
||||
enrollment::set_files_dir(PathBuf::from(&dir));
|
||||
}
|
||||
app_log::set_crash_dir(Path::new(&dir));
|
||||
enrollment::set_files_dir(PathBuf::from(&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 {
|
||||
return;
|
||||
};
|
||||
#[cfg(feature = "transcript-screen")]
|
||||
match enrollment::apply_link(&uri) {
|
||||
// Never the token: `wg-app-link`'s enroll module forbids logging
|
||||
// it, and this line would otherwise be the one place it leaked.
|
||||
Ok(server) => log::info!("iris app: enrolled with {}:{}", server.host, server.port),
|
||||
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> {
|
||||
|
||||
+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
|
||||
Android. Features select optional surfaces: `screens` links Iris, `fixture`
|
||||
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
|
||||
|
||||
|
||||
Generated
-8
@@ -1550,7 +1550,6 @@ dependencies = [
|
||||
"pollster",
|
||||
"send_wrapper",
|
||||
"swash",
|
||||
"tabs-ui",
|
||||
"tokio",
|
||||
"wgpu",
|
||||
"winit",
|
||||
@@ -3316,13 +3315,6 @@ dependencies = [
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tabs-ui"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"iris",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.25.0"
|
||||
|
||||
@@ -36,7 +36,6 @@ force-gles = []
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { workspace = true, features = ["sync", "rt", "rt-multi-thread", "time"] }
|
||||
tabs-ui = { path = "tabs-ui" }
|
||||
bytemuck = { workspace = true }
|
||||
|
||||
[[bench]]
|
||||
@@ -47,7 +46,6 @@ harness = false
|
||||
members = [
|
||||
"core",
|
||||
"macro",
|
||||
"tabs-ui",
|
||||
"rig-input",
|
||||
]
|
||||
|
||||
|
||||
File renamed without changes.
+208
-2
@@ -1,4 +1,5 @@
|
||||
use iris::prelude::*;
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
use winit::event::WindowEvent;
|
||||
|
||||
fn main() {
|
||||
@@ -6,14 +7,14 @@ fn main() {
|
||||
}
|
||||
|
||||
#[derive(DesktopUiState)]
|
||||
pub struct Client {
|
||||
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 = tabs_ui::build(rsc, &mut ui_state);
|
||||
let widgets = build(rsc, &mut ui_state);
|
||||
Self {
|
||||
ui_state,
|
||||
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