diff --git a/Cargo.lock b/Cargo.lock index 96e9bbc0..6e32d21c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -105,7 +105,8 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "android-activity" version = "0.6.0" -source = "git+https://github.com/damus-io/android-activity?rev=a8948332c7c551303d32eb26a59d0abd676e47a5#a8948332c7c551303d32eb26a59d0abd676e47a5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046" dependencies = [ "android-properties", "bitflags 2.9.1", @@ -125,7 +126,7 @@ dependencies = [ [[package]] name = "android-activity" version = "0.6.0" -source = "git+https://github.com/damus-io/android-activity?rev=c3c0decc83c4d6c94d2c448391fc8dd51b13f3d9#c3c0decc83c4d6c94d2c448391fc8dd51b13f3d9" +source = "git+https://github.com/damus-io/android-activity?rev=092a83b747937a2890ac219617a4252c001842ea#092a83b747937a2890ac219617a4252c001842ea" dependencies = [ "android-properties", "bitflags 2.9.1", @@ -1402,7 +1403,7 @@ checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" [[package]] name = "dpi" version = "0.1.1" -source = "git+https://github.com/damus-io/winit?rev=eaff639ab0a14fccf595241f687be883154b267c#eaff639ab0a14fccf595241f687be883154b267c" +source = "git+https://github.com/damus-io/winit?rev=9e4ea9de75222d2523a20f18d3a0a108c573737d#9e4ea9de75222d2523a20f18d3a0a108c573737d" [[package]] name = "dpi" @@ -1419,17 +1420,17 @@ checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" [[package]] name = "ecolor" version = "0.31.1" -source = "git+https://github.com/damus-io/egui?rev=a67ab901e197ce13948ff7d00aa6e07e31a68ccd#a67ab901e197ce13948ff7d00aa6e07e31a68ccd" +source = "git+https://github.com/damus-io/egui?rev=c1f9e565aa17a7a8b40736602b6ea8a52876f46f#c1f9e565aa17a7a8b40736602b6ea8a52876f46f" dependencies = [ "bytemuck", - "emath 0.31.1 (git+https://github.com/damus-io/egui?rev=a67ab901e197ce13948ff7d00aa6e07e31a68ccd)", + "emath 0.31.1 (git+https://github.com/damus-io/egui?rev=c1f9e565aa17a7a8b40736602b6ea8a52876f46f)", "serde", ] [[package]] name = "eframe" version = "0.31.1" -source = "git+https://github.com/damus-io/egui?rev=a67ab901e197ce13948ff7d00aa6e07e31a68ccd#a67ab901e197ce13948ff7d00aa6e07e31a68ccd" +source = "git+https://github.com/damus-io/egui?rev=c1f9e565aa17a7a8b40736602b6ea8a52876f46f#c1f9e565aa17a7a8b40736602b6ea8a52876f46f" dependencies = [ "ahash", "bytemuck", @@ -1465,13 +1466,13 @@ dependencies = [ [[package]] name = "egui" version = "0.31.1" -source = "git+https://github.com/damus-io/egui?rev=a67ab901e197ce13948ff7d00aa6e07e31a68ccd#a67ab901e197ce13948ff7d00aa6e07e31a68ccd" +source = "git+https://github.com/damus-io/egui?rev=c1f9e565aa17a7a8b40736602b6ea8a52876f46f#c1f9e565aa17a7a8b40736602b6ea8a52876f46f" dependencies = [ "accesskit", "ahash", "backtrace", "bitflags 2.9.1", - "emath 0.31.1 (git+https://github.com/damus-io/egui?rev=a67ab901e197ce13948ff7d00aa6e07e31a68ccd)", + "emath 0.31.1 (git+https://github.com/damus-io/egui?rev=c1f9e565aa17a7a8b40736602b6ea8a52876f46f)", "epaint", "log", "nohash-hasher", @@ -1483,7 +1484,7 @@ dependencies = [ [[package]] name = "egui-wgpu" version = "0.31.1" -source = "git+https://github.com/damus-io/egui?rev=a67ab901e197ce13948ff7d00aa6e07e31a68ccd#a67ab901e197ce13948ff7d00aa6e07e31a68ccd" +source = "git+https://github.com/damus-io/egui?rev=c1f9e565aa17a7a8b40736602b6ea8a52876f46f#c1f9e565aa17a7a8b40736602b6ea8a52876f46f" dependencies = [ "ahash", "bytemuck", @@ -1502,7 +1503,7 @@ dependencies = [ [[package]] name = "egui-winit" version = "0.31.1" -source = "git+https://github.com/damus-io/egui?rev=a67ab901e197ce13948ff7d00aa6e07e31a68ccd#a67ab901e197ce13948ff7d00aa6e07e31a68ccd" +source = "git+https://github.com/damus-io/egui?rev=c1f9e565aa17a7a8b40736602b6ea8a52876f46f#c1f9e565aa17a7a8b40736602b6ea8a52876f46f" dependencies = [ "ahash", "arboard", @@ -1520,7 +1521,7 @@ dependencies = [ [[package]] name = "egui_extras" version = "0.31.1" -source = "git+https://github.com/damus-io/egui?rev=a67ab901e197ce13948ff7d00aa6e07e31a68ccd#a67ab901e197ce13948ff7d00aa6e07e31a68ccd" +source = "git+https://github.com/damus-io/egui?rev=c1f9e565aa17a7a8b40736602b6ea8a52876f46f#c1f9e565aa17a7a8b40736602b6ea8a52876f46f" dependencies = [ "ahash", "egui", @@ -1537,7 +1538,7 @@ dependencies = [ [[package]] name = "egui_glow" version = "0.31.1" -source = "git+https://github.com/damus-io/egui?rev=a67ab901e197ce13948ff7d00aa6e07e31a68ccd#a67ab901e197ce13948ff7d00aa6e07e31a68ccd" +source = "git+https://github.com/damus-io/egui?rev=c1f9e565aa17a7a8b40736602b6ea8a52876f46f#c1f9e565aa17a7a8b40736602b6ea8a52876f46f" dependencies = [ "ahash", "bytemuck", @@ -1616,7 +1617,7 @@ checksum = "9e4cadcff7a5353ba72b7fea76bf2122b5ebdbc68e8155aa56dfdea90083fe1b" [[package]] name = "emath" version = "0.31.1" -source = "git+https://github.com/damus-io/egui?rev=a67ab901e197ce13948ff7d00aa6e07e31a68ccd#a67ab901e197ce13948ff7d00aa6e07e31a68ccd" +source = "git+https://github.com/damus-io/egui?rev=c1f9e565aa17a7a8b40736602b6ea8a52876f46f#c1f9e565aa17a7a8b40736602b6ea8a52876f46f" dependencies = [ "bytemuck", "serde", @@ -1714,13 +1715,13 @@ dependencies = [ [[package]] name = "epaint" version = "0.31.1" -source = "git+https://github.com/damus-io/egui?rev=a67ab901e197ce13948ff7d00aa6e07e31a68ccd#a67ab901e197ce13948ff7d00aa6e07e31a68ccd" +source = "git+https://github.com/damus-io/egui?rev=c1f9e565aa17a7a8b40736602b6ea8a52876f46f#c1f9e565aa17a7a8b40736602b6ea8a52876f46f" dependencies = [ "ab_glyph", "ahash", "bytemuck", "ecolor", - "emath 0.31.1 (git+https://github.com/damus-io/egui?rev=a67ab901e197ce13948ff7d00aa6e07e31a68ccd)", + "emath 0.31.1 (git+https://github.com/damus-io/egui?rev=c1f9e565aa17a7a8b40736602b6ea8a52876f46f)", "epaint_default_fonts", "log", "nohash-hasher", @@ -1732,7 +1733,7 @@ dependencies = [ [[package]] name = "epaint_default_fonts" version = "0.31.1" -source = "git+https://github.com/damus-io/egui?rev=a67ab901e197ce13948ff7d00aa6e07e31a68ccd#a67ab901e197ce13948ff7d00aa6e07e31a68ccd" +source = "git+https://github.com/damus-io/egui?rev=c1f9e565aa17a7a8b40736602b6ea8a52876f46f#c1f9e565aa17a7a8b40736602b6ea8a52876f46f" [[package]] name = "equator" @@ -3506,6 +3507,7 @@ dependencies = [ name = "notedeck" version = "0.6.0" dependencies = [ + "android-activity 0.6.0 (git+https://github.com/damus-io/android-activity?rev=092a83b747937a2890ac219617a4252c001842ea)", "base32", "bech32", "bincode", @@ -7445,10 +7447,10 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winit" version = "0.30.8" -source = "git+https://github.com/damus-io/winit?rev=eaff639ab0a14fccf595241f687be883154b267c#eaff639ab0a14fccf595241f687be883154b267c" +source = "git+https://github.com/damus-io/winit?rev=9e4ea9de75222d2523a20f18d3a0a108c573737d#9e4ea9de75222d2523a20f18d3a0a108c573737d" dependencies = [ "ahash", - "android-activity 0.6.0 (git+https://github.com/damus-io/android-activity?rev=c3c0decc83c4d6c94d2c448391fc8dd51b13f3d9)", + "android-activity 0.6.0 (git+https://github.com/damus-io/android-activity?rev=092a83b747937a2890ac219617a4252c001842ea)", "atomic-waker", "bitflags 2.9.1", "block2 0.5.1", @@ -7500,7 +7502,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4409c10174df8779dc29a4788cac85ed84024ccbc1743b776b21a520ee1aaf4" dependencies = [ "ahash", - "android-activity 0.6.0 (git+https://github.com/damus-io/android-activity?rev=a8948332c7c551303d32eb26a59d0abd676e47a5)", + "android-activity 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "atomic-waker", "bitflags 2.9.1", "block2 0.5.1", diff --git a/Cargo.toml b/Cargo.toml index 9240f7b8..cd1c309b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -88,7 +88,7 @@ openai-api-rs = "6.0.3" re_memory = "0.23.4" oot_bitset = "0.1.1" blurhash = "0.2.3" - +android-activity = { git = "https://github.com/damus-io/android-activity", rev = "092a83b747937a2890ac219617a4252c001842ea", features = [ "game-activity" ] } [profile.small] inherits = 'release' @@ -106,15 +106,15 @@ strip = true # Strip symbols from binary* #egui_extras = { path = "/home/jb55/dev/github/emilk/egui/crates/egui_extras" } #epaint = { path = "/home/jb55/dev/github/emilk/egui/crates/epaint" } -egui = { git = "https://github.com/damus-io/egui", rev = "a67ab901e197ce13948ff7d00aa6e07e31a68ccd" } -eframe = { git = "https://github.com/damus-io/egui", rev = "a67ab901e197ce13948ff7d00aa6e07e31a68ccd" } -egui-winit = { git = "https://github.com/damus-io/egui", rev = "a67ab901e197ce13948ff7d00aa6e07e31a68ccd" } -egui-wgpu = { git = "https://github.com/damus-io/egui", rev = "a67ab901e197ce13948ff7d00aa6e07e31a68ccd" } -egui_extras = { git = "https://github.com/damus-io/egui", rev = "a67ab901e197ce13948ff7d00aa6e07e31a68ccd" } -epaint = { git = "https://github.com/damus-io/egui", rev = "a67ab901e197ce13948ff7d00aa6e07e31a68ccd" } +egui = { git = "https://github.com/damus-io/egui", rev = "c1f9e565aa17a7a8b40736602b6ea8a52876f46f" } +eframe = { git = "https://github.com/damus-io/egui", rev = "c1f9e565aa17a7a8b40736602b6ea8a52876f46f" } +egui-winit = { git = "https://github.com/damus-io/egui", rev = "c1f9e565aa17a7a8b40736602b6ea8a52876f46f" } +egui-wgpu = { git = "https://github.com/damus-io/egui", rev = "c1f9e565aa17a7a8b40736602b6ea8a52876f46f" } +egui_extras = { git = "https://github.com/damus-io/egui", rev = "c1f9e565aa17a7a8b40736602b6ea8a52876f46f" } +epaint = { git = "https://github.com/damus-io/egui", rev = "c1f9e565aa17a7a8b40736602b6ea8a52876f46f" } puffin = { git = "https://github.com/jb55/puffin", package = "puffin", rev = "c6a6242adaf90b6292c0f462d2acd34d96d224d2" } puffin_egui = { git = "https://github.com/jb55/puffin", package = "puffin_egui", rev = "c6a6242adaf90b6292c0f462d2acd34d96d224d2" } -#winit = { git = "https://github.com/damus-io/winit", rev = "14d61a74bee0c9863abe7ef28efae2c4d8bd3743" } +#winit = { git = "https://github.com/damus-io/winit", rev = "701a43d3c6479b0a3869acd2cebbfd410d399a59" } #winit = { path = "/home/jb55/dev/github/rust-windowing/winit" } -android-activity = { git = "https://github.com/damus-io/android-activity", rev = "a8948332c7c551303d32eb26a59d0abd676e47a5" } +#android-activity = { git = "https://github.com/damus-io/android-activity", rev = "f56c974aa5182d5fbd361879f5899eb8f11a37ec" } #android-activity = { path = "/home/jb55/dev/github/rust-mobile/android-activity/android-activity" } diff --git a/Makefile b/Makefile index 84eb3cb5..e49f3a8c 100644 --- a/Makefile +++ b/Makefile @@ -27,4 +27,4 @@ push-android-config: android: jni cd $(ANDROID_DIR) && ./gradlew installDebug adb shell am start -n com.damus.notedeck/.MainActivity - adb logcat -v color -s RustStdoutStderr -s threaded_app | tee logcat.txt + adb logcat -v color -s GameActivity -s RustStdoutStderr -s threaded_app | tee logcat.txt diff --git a/crates/notedeck/Cargo.toml b/crates/notedeck/Cargo.toml index 25105484..ed79826f 100644 --- a/crates/notedeck/Cargo.toml +++ b/crates/notedeck/Cargo.toml @@ -57,6 +57,7 @@ tokio = { workspace = true } [target.'cfg(target_os = "android")'.dependencies] jni = { workspace = true } +android-activity = { workspace = true } [features] puffin = ["puffin_egui", "dep:puffin"] diff --git a/crates/notedeck/src/app.rs b/crates/notedeck/src/app.rs index 92c60fa6..5c7c1e17 100644 --- a/crates/notedeck/src/app.rs +++ b/crates/notedeck/src/app.rs @@ -22,6 +22,9 @@ use std::rc::Rc; use tracing::{error, info}; use unic_langid::{LanguageIdentifier, LanguageIdentifierError}; +#[cfg(target_os = "android")] +use android_activity::AndroidApp; + pub enum AppAction { Note(NoteAction), ToggleChrome, @@ -51,6 +54,9 @@ pub struct Notedeck { frame_history: FrameHistory, job_pool: JobPool, i18n: Localization, + + #[cfg(target_os = "android")] + android_app: Option, } /// Our chrome, which is basically nothing @@ -138,6 +144,11 @@ fn setup_puffin() { } impl Notedeck { + #[cfg(target_os = "android")] + pub fn set_android_context(&mut self, context: AndroidApp) { + self.android_app = Some(context); + } + pub fn new>(ctx: &egui::Context, data_path: P, args: &[String]) -> Self { #[cfg(feature = "puffin")] setup_puffin(); @@ -272,6 +283,8 @@ impl Notedeck { zaps, job_pool, i18n, + #[cfg(target_os = "android")] + android_app: None, } } @@ -335,6 +348,8 @@ impl Notedeck { frame_history: &mut self.frame_history, job_pool: &mut self.job_pool, i18n: &mut self.i18n, + #[cfg(target_os = "android")] + android: self.android_app.as_ref().unwrap().clone(), } } diff --git a/crates/notedeck/src/context.rs b/crates/notedeck/src/context.rs index feac9eda..949b5a6e 100644 --- a/crates/notedeck/src/context.rs +++ b/crates/notedeck/src/context.rs @@ -8,6 +8,9 @@ use egui_winit::clipboard::Clipboard; use enostr::RelayPool; use nostrdb::Ndb; +#[cfg(target_os = "android")] +use android_activity::AndroidApp; +use egui::{Pos2, Rect}; // TODO: make this interface more sandboxed pub struct AppContext<'a> { @@ -26,4 +29,49 @@ pub struct AppContext<'a> { pub frame_history: &'a mut FrameHistory, pub job_pool: &'a mut JobPool, pub i18n: &'a mut Localization, + + #[cfg(target_os = "android")] + pub android: AndroidApp, +} + +#[derive(Debug, Clone)] +pub enum SoftKeyboardContext { + Virtual, + Platform { ppp: f32 }, +} + +impl<'a> AppContext<'a> { + pub fn soft_keyboard_rect(&self, screen_rect: Rect, ctx: SoftKeyboardContext) -> Option { + match ctx { + SoftKeyboardContext::Virtual => { + let height = 400.0; + skb_rect_from_screen_rect(screen_rect, height) + } + + #[allow(unused_variables)] + SoftKeyboardContext::Platform { ppp } => { + #[cfg(target_os = "android")] + { + use android_activity::InsetType; + let inset = self.android.get_window_insets(InsetType::Ime); + let height = inset.bottom as f32 / ppp; + skb_rect_from_screen_rect(screen_rect, height) + } + + #[cfg(not(target_os = "android"))] + { + None + } + } + } + } +} + +#[inline] +fn skb_rect_from_screen_rect(screen_rect: Rect, height: f32) -> Option { + if height == 0.0 { + return None; + } + let min = Pos2::new(0.0, screen_rect.max.y - height); + Some(Rect::from_min_max(min, screen_rect.max)) } diff --git a/crates/notedeck/src/lib.rs b/crates/notedeck/src/lib.rs index e0562544..4aa24617 100644 --- a/crates/notedeck/src/lib.rs +++ b/crates/notedeck/src/lib.rs @@ -46,7 +46,7 @@ pub use account::relay::RelayAction; pub use account::FALLBACK_PUBKEY; pub use app::{App, AppAction, Notedeck}; pub use args::Args; -pub use context::AppContext; +pub use context::{AppContext, SoftKeyboardContext}; pub use error::{show_one_error_message, Error, FilterError, ZapError}; pub use filter::{FilterState, FilterStates, UnifiedSubscription}; pub use fonts::NamedFontFamily; diff --git a/crates/notedeck/src/platform/android.rs b/crates/notedeck/src/platform/android.rs index 7497346c..1417e05a 100644 --- a/crates/notedeck/src/platform/android.rs +++ b/crates/notedeck/src/platform/android.rs @@ -16,7 +16,7 @@ pub extern "C" fn Java_com_damus_notedeck_KeyboardHeightHelper_nativeKeyboardHei debug!("updating virtual keyboard height {}", height); // Convert and store atomically - KEYBOARD_HEIGHT.store(height, Ordering::SeqCst); + KEYBOARD_HEIGHT.store(height.max(0), Ordering::SeqCst); } /// Gets the current Android virtual keyboard height. Useful for transforming diff --git a/crates/notedeck/src/platform/mod.rs b/crates/notedeck/src/platform/mod.rs index d251128e..dac0b363 100644 --- a/crates/notedeck/src/platform/mod.rs +++ b/crates/notedeck/src/platform/mod.rs @@ -20,3 +20,13 @@ pub fn virtual_keyboard_height(virt: bool) -> i32 { 0 } } + +pub fn virtual_keyboard_rect(ui: &egui::Ui, virt: bool) -> Option { + let height = virtual_keyboard_height(virt); + if height <= 0 { + return None; + } + let screen_rect = ui.ctx().screen_rect(); + let min = egui::Pos2::new(0.0, screen_rect.max.y - height as f32); + Some(egui::Rect::from_min_max(min, screen_rect.max)) +} diff --git a/crates/notedeck_chrome/android/app/src/main/AndroidManifest.xml b/crates/notedeck_chrome/android/app/src/main/AndroidManifest.xml index 799a1e30..ece6da0d 100644 --- a/crates/notedeck_chrome/android/app/src/main/AndroidManifest.xml +++ b/crates/notedeck_chrome/android/app/src/main/AndroidManifest.xml @@ -10,6 +10,7 @@ android:name=".MainActivity" android:configChanges="orientation|screenSize|screenLayout|keyboardHidden|uiMode|fontScale|smallestScreenSize" android:exported="true" + android:windowSoftInputMode="adjustResize" android:launchMode="singleTask" > @@ -37,4 +38,4 @@ - \ No newline at end of file + diff --git a/crates/notedeck_chrome/android/app/src/main/java/com/damus/notedeck/MainActivity.java b/crates/notedeck_chrome/android/app/src/main/java/com/damus/notedeck/MainActivity.java index 93658f05..61a20681 100644 --- a/crates/notedeck_chrome/android/app/src/main/java/com/damus/notedeck/MainActivity.java +++ b/crates/notedeck_chrome/android/app/src/main/java/com/damus/notedeck/MainActivity.java @@ -26,12 +26,15 @@ public class MainActivity extends GameActivity { @Override protected void onCreate(Bundle savedInstanceState) { // Shrink view so it does not get covered by insets. + super.onCreate(savedInstanceState); setupInsets(); + //setupFullscreen() - keyboardHelper = new KeyboardHeightHelper(this); + + //keyboardHelper = new KeyboardHeightHelper(this); - super.onCreate(savedInstanceState); + } private void setupFullscreen() { @@ -61,6 +64,18 @@ public class MainActivity extends GameActivity { } private void setupInsets() { + + // NOTE(jb55): This is needed for keyboard visibility. Without this the + // window still gets the right insets, but they’re consumed before they + // reach the NDK side. + //WindowCompat.setDecorFitsSystemWindows(getWindow(), false); + + // NOTE(jb55): This is needed for keyboard visibility. If the bars are + // permanently gone, Android routes the keyboard over the GL surface and + // doesn’t change insets. + //WindowInsetsControllerCompat ic = WindowCompat.getInsetsController(getWindow(), getWindow().getDecorView()); + //ic.setSystemBarsBehavior(WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE); + View content = getContent(); ViewCompat.setOnApplyWindowInsetsListener(content, (v, windowInsets) -> { Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()); @@ -72,12 +87,13 @@ public class MainActivity extends GameActivity { mlp.rightMargin = insets.right; v.setLayoutParams(mlp); - return WindowInsetsCompat.CONSUMED; + return windowInsets; }); - WindowCompat.setDecorFitsSystemWindows(getWindow(), true); + WindowCompat.setDecorFitsSystemWindows(getWindow(), false); } + /* @Override public void onResume() { super.onResume(); @@ -95,6 +111,7 @@ public class MainActivity extends GameActivity { super.onDestroy(); keyboardHelper.close(); } + */ @Override public boolean onTouchEvent(MotionEvent event) { diff --git a/crates/notedeck_chrome/src/android.rs b/crates/notedeck_chrome/src/android.rs index aec9f020..f36bf87a 100644 --- a/crates/notedeck_chrome/src/android.rs +++ b/crates/notedeck_chrome/src/android.rs @@ -17,7 +17,7 @@ pub async fn android_main(app: AndroidApp) { //std::env::set_var("DAVE_MODEL", "hhao/qwen2.5-coder-tools:latest"); std::env::set_var( "RUST_LOG", - "egui=debug,egui-winit=debug,notedeck=debug,notedeck_columns=debug,notedeck_chrome=debug,enostr=debug,android_activity=debug", + "egui=debug,egui-winit=debug,winit=debug,notedeck=debug,notedeck_columns=debug,notedeck_chrome=debug,enostr=debug,android_activity=debug", ); //std::env::set_var( @@ -57,7 +57,7 @@ pub async fn android_main(app: AndroidApp) { options.android_app = Some(app.clone()); - let app_args = get_app_args(app); + let app_args = get_app_args(app.clone()); let _res = eframe::run_native( "Damus Notedeck", @@ -65,6 +65,7 @@ pub async fn android_main(app: AndroidApp) { Box::new(move |cc| { let ctx = &cc.egui_ctx; let mut notedeck = Notedeck::new(ctx, path, &app_args); + notedeck.set_android_context(app.clone()); notedeck.setup(ctx); let chrome = Chrome::new_with_apps(cc, &app_args, &mut notedeck)?; notedeck.set_app(chrome); diff --git a/crates/notedeck_chrome/src/chrome.rs b/crates/notedeck_chrome/src/chrome.rs index 20327c9f..804ef9c9 100644 --- a/crates/notedeck_chrome/src/chrome.rs +++ b/crates/notedeck_chrome/src/chrome.rs @@ -8,6 +8,7 @@ use egui::{vec2, Button, Color32, Label, Layout, Rect, RichText, ThemePreference use egui_extras::{Size, StripBuilder}; use nostrdb::{ProfileRecord, Transaction}; use notedeck::Error; +use notedeck::SoftKeyboardContext; use notedeck::{ tr, App, AppAction, AppContext, Localization, Notedeck, NotedeckOptions, NotedeckTextStyle, UserAccount, WalletType, @@ -267,9 +268,40 @@ impl Chrome { let amt_open = self.amount_open(ui); let r = self.panel(ctx, StripBuilder::new(ui), amt_open); - // virtual keyboard - if self.options.contains(ChromeOptions::VirtualKeyboard) { - virtual_keyboard_ui(ui); + let skb_ctx = if self.options.contains(ChromeOptions::VirtualKeyboard) { + SoftKeyboardContext::Virtual + } else { + SoftKeyboardContext::Platform { + ppp: ui.ctx().pixels_per_point(), + } + }; + + // move screen up if virtual keyboard intersects with input_rect + let screen_rect = ui.ctx().screen_rect(); + let mut keyboard_height = 0.0; + if let Some(vkb_rect) = ctx.soft_keyboard_rect(screen_rect, skb_ctx.clone()) { + if let SoftKeyboardContext::Virtual = skb_ctx { + virtual_keyboard_ui(ui, vkb_rect); + } + if let Some(input_rect) = notedeck_ui::input_rect(ui) { + if input_rect.intersects(vkb_rect) { + tracing::debug!("screen:{screen_rect} skb:{vkb_rect}"); + keyboard_height = vkb_rect.height(); + } + } + } else { + // clear last input box position state + notedeck_ui::clear_input_rect(ui); + } + + let anim_height = + ui.ctx() + .animate_value_with_time(egui::Id::new("keyboard_anim"), keyboard_height, 0.1); + if anim_height > 0.0 { + ui.ctx().transform_layer_shapes( + ui.layer_id(), + egui::emath::TSTransform::from_translation(egui::Vec2::new(0.0, -anim_height)), + ); } r @@ -912,12 +944,7 @@ fn repaint_causes_window(ui: &mut egui::Ui, causes: &HashMap match key { egui::Key::J => { - columns.select_down(); + //columns.select_down(); + {} } + /* egui::Key::K => { columns.select_up(); } @@ -90,7 +87,13 @@ fn handle_key_events(input: &egui::InputState, columns: &mut Columns) { columns.get_selected_router().go_back(); } _ => {} + }, + + egui::Event::InsetsChanged => { + tracing::debug!("insets have changed!"); } + + _ => {} } } } @@ -102,7 +105,7 @@ fn try_process_event( ) -> Result<()> { let current_columns = get_active_columns_mut(app_ctx.i18n, app_ctx.accounts, &mut damus.decks_cache); - ctx.input(|i| handle_key_events(i, current_columns)); + ctx.input(|i| handle_egui_events(i, current_columns)); let ctx2 = ctx.clone(); let wakeup = move || { diff --git a/crates/notedeck_columns/src/nav.rs b/crates/notedeck_columns/src/nav.rs index e002398f..d14594d7 100644 --- a/crates/notedeck_columns/src/nav.rs +++ b/crates/notedeck_columns/src/nav.rs @@ -832,7 +832,7 @@ fn render_nav_body( return action; }; - if EditProfileView::new(ctx.i18n, state, ctx.img_cache).ui(ui) { + if EditProfileView::new(ctx.i18n, state, ctx.img_cache, ctx.clipboard).ui(ui) { if let Some(state) = app.view_state.pubkey_to_profile_state.get(kp.pubkey) { action = Some(RenderNavAction::ProfileAction(ProfileAction::SaveChanges( SaveProfileChanges::new(kp.to_full(), state.clone()), diff --git a/crates/notedeck_columns/src/ui/account_login_view.rs b/crates/notedeck_columns/src/ui/account_login_view.rs index bfa60db5..5cc9ec09 100644 --- a/crates/notedeck_columns/src/ui/account_login_view.rs +++ b/crates/notedeck_columns/src/ui/account_login_view.rs @@ -59,7 +59,7 @@ impl<'a> AccountLoginView<'a> { let text_edit_width = available_width - button_width; let textedit_resp = ui.add_sized([text_edit_width, 40.0], login_textedit(self.manager, self.i18n)); - input_context(&textedit_resp, self.clipboard, self.manager.input_buffer(), PasteBehavior::Clear); + input_context(ui, &textedit_resp, self.clipboard, self.manager.input_buffer(), PasteBehavior::Clear); if eye_button(ui, self.manager.password_visible()).clicked() { self.manager.toggle_password_visibility(); diff --git a/crates/notedeck_columns/src/ui/note/post.rs b/crates/notedeck_columns/src/ui/note/post.rs index 2e45b602..48443803 100644 --- a/crates/notedeck_columns/src/ui/note/post.rs +++ b/crates/notedeck_columns/src/ui/note/post.rs @@ -209,6 +209,7 @@ impl<'a, 'd> PostView<'a, 'd> { let out = textedit.show(ui); input_context( + ui, &out.response, self.note_context.clipboard, &mut self.draft.buffer.text_buffer, diff --git a/crates/notedeck_columns/src/ui/profile/edit.rs b/crates/notedeck_columns/src/ui/profile/edit.rs index d1c3720d..2bbd6f5b 100644 --- a/crates/notedeck_columns/src/ui/profile/edit.rs +++ b/crates/notedeck_columns/src/ui/profile/edit.rs @@ -1,12 +1,15 @@ use core::f32; use egui::{vec2, Button, CornerRadius, Layout, Margin, RichText, ScrollArea, TextEdit}; +use egui_winit::clipboard::Clipboard; use enostr::ProfileState; use notedeck::{profile::unwrap_profile_url, tr, Images, Localization, NotedeckTextStyle}; +use notedeck_ui::context_menu::{input_context, PasteBehavior}; use notedeck_ui::{profile::banner, ProfilePic}; pub struct EditProfileView<'a> { state: &'a mut ProfileState, + clipboard: &'a mut Clipboard, img_cache: &'a mut Images, i18n: &'a mut Localization, } @@ -16,11 +19,13 @@ impl<'a> EditProfileView<'a> { i18n: &'a mut Localization, state: &'a mut ProfileState, img_cache: &'a mut Images, + clipboard: &'a mut Clipboard, ) -> Self { Self { i18n, state, img_cache, + clipboard, } } @@ -95,14 +100,14 @@ impl<'a> EditProfileView<'a> { ) .as_str(), )); - ui.add(singleline_textedit(self.state.str_mut("display_name"))); + singleline_textedit(ui, self.state.str_mut("display_name"), self.clipboard); }); in_frame(ui, |ui| { ui.add(label( tr!(self.i18n, "Username", "Profile username field label").as_str(), )); - ui.add(singleline_textedit(self.state.str_mut("name"))); + singleline_textedit(ui, self.state.str_mut("name"), self.clipboard); }); in_frame(ui, |ui| { @@ -114,28 +119,28 @@ impl<'a> EditProfileView<'a> { ) .as_str(), )); - ui.add(multiline_textedit(self.state.str_mut("picture"))); + multiline_textedit(ui, self.state.str_mut("picture"), self.clipboard); }); in_frame(ui, |ui| { ui.add(label( tr!(self.i18n, "Banner", "Profile banner URL field label").as_str(), )); - ui.add(multiline_textedit(self.state.str_mut("banner"))); + multiline_textedit(ui, self.state.str_mut("banner"), self.clipboard); }); in_frame(ui, |ui| { ui.add(label( tr!(self.i18n, "About", "Profile about/bio field label").as_str(), )); - ui.add(multiline_textedit(self.state.str_mut("about"))); + multiline_textedit(ui, self.state.str_mut("about"), self.clipboard); }); in_frame(ui, |ui| { ui.add(label( tr!(self.i18n, "Website", "Profile website field label").as_str(), )); - ui.add(singleline_textedit(self.state.str_mut("website"))); + singleline_textedit(ui, self.state.str_mut("website"), self.clipboard); }); in_frame(ui, |ui| { @@ -147,7 +152,7 @@ impl<'a> EditProfileView<'a> { ) .as_str(), )); - ui.add(multiline_textedit(self.state.str_mut("lud16"))); + multiline_textedit(ui, self.state.str_mut("lud16"), self.clipboard); }); in_frame(ui, |ui| { @@ -159,7 +164,8 @@ impl<'a> EditProfileView<'a> { ) .as_str(), )); - ui.add(singleline_textedit(self.state.str_mut("nip05"))); + + singleline_textedit(ui, self.state.str_mut("nip05"), self.clipboard); let Some(nip05) = self.state.nip05() else { return; @@ -208,21 +214,29 @@ fn label(text: &str) -> impl egui::Widget + '_ { } } -fn singleline_textedit(data: &mut String) -> impl egui::Widget + '_ { - TextEdit::singleline(data) - .min_size(vec2(0.0, 40.0)) - .vertical_align(egui::Align::Center) - .margin(Margin::symmetric(12, 10)) - .desired_width(f32::INFINITY) +fn singleline_textedit(ui: &mut egui::Ui, data: &mut String, clipboard: &mut Clipboard) { + let r = ui.add( + TextEdit::singleline(data) + .min_size(vec2(0.0, 40.0)) + .vertical_align(egui::Align::Center) + .margin(Margin::symmetric(12, 10)) + .desired_width(f32::INFINITY), + ); + + input_context(ui, &r, clipboard, data, PasteBehavior::Clear); } -fn multiline_textedit(data: &mut String) -> impl egui::Widget + '_ { - TextEdit::multiline(data) - // .min_size(vec2(0.0, 40.0)) - .vertical_align(egui::Align::TOP) - .margin(Margin::symmetric(12, 10)) - .desired_width(f32::INFINITY) - .desired_rows(1) +fn multiline_textedit(ui: &mut egui::Ui, data: &mut String, clipboard: &mut Clipboard) { + let r = ui.add( + TextEdit::multiline(data) + // .min_size(vec2(0.0, 40.0)) + .vertical_align(egui::Align::TOP) + .margin(Margin::symmetric(12, 10)) + .desired_width(f32::INFINITY) + .desired_rows(1), + ); + + input_context(ui, &r, clipboard, data, PasteBehavior::Clear); } fn in_frame(ui: &mut egui::Ui, contents: impl FnOnce(&mut egui::Ui)) { diff --git a/crates/notedeck_columns/src/ui/search/mod.rs b/crates/notedeck_columns/src/ui/search/mod.rs index 0b66b3d2..f3e658c9 100644 --- a/crates/notedeck_columns/src/ui/search/mod.rs +++ b/crates/notedeck_columns/src/ui/search/mod.rs @@ -311,7 +311,7 @@ fn search_box( .frame(false), ); - input_context(&response, clipboard, input, PasteBehavior::Append); + input_context(ui, &response, clipboard, input, PasteBehavior::Append); let mut requested_focus = false; if focus_state == FocusState::ShouldRequestFocus { diff --git a/crates/notedeck_columns/src/ui/settings.rs b/crates/notedeck_columns/src/ui/settings.rs index 31e846b7..a7644732 100644 --- a/crates/notedeck_columns/src/ui/settings.rs +++ b/crates/notedeck_columns/src/ui/settings.rs @@ -147,7 +147,7 @@ impl<'a> SettingsView<'a> { "Label for appearance settings section", ); settings_group(ui, title, |ui| { - ui.horizontal(|ui| { + ui.horizontal_wrapped(|ui| { ui.label(richtext_small(tr!( self.note_context.i18n, "Font size:", @@ -207,7 +207,7 @@ impl<'a> SettingsView<'a> { let current_zoom = ui.ctx().zoom_factor(); - ui.horizontal(|ui| { + ui.horizontal_wrapped(|ui| { ui.label(richtext_small(tr!( self.note_context.i18n, "Zoom Level:", @@ -260,7 +260,7 @@ impl<'a> SettingsView<'a> { } }); - ui.horizontal(|ui| { + ui.horizontal_wrapped(|ui| { ui.label(richtext_small(tr!( self.note_context.i18n, "Language:", @@ -288,7 +288,7 @@ impl<'a> SettingsView<'a> { }); }); - ui.horizontal(|ui| { + ui.horizontal_wrapped(|ui| { ui.label(richtext_small(tr!( self.note_context.i18n, "Theme:", @@ -441,7 +441,7 @@ impl<'a> SettingsView<'a> { "Label for others settings section" ); settings_group(ui, title, |ui| { - ui.horizontal(|ui| { + ui.horizontal_wrapped(|ui| { ui.label(richtext_small(tr!( self.note_context.i18n, "Sort replies newest first:", diff --git a/crates/notedeck_columns/src/ui/wallet.rs b/crates/notedeck_columns/src/ui/wallet.rs index e9436492..5fd84227 100644 --- a/crates/notedeck_columns/src/ui/wallet.rs +++ b/crates/notedeck_columns/src/ui/wallet.rs @@ -237,8 +237,10 @@ fn show_no_wallet( .password(true); // add paste context menu + let text_edit_resp = ui.add(text_edit); input_context( - &ui.add(text_edit), + ui, + &text_edit_resp, clipboard, &mut state.buf, PasteBehavior::Clear, @@ -388,13 +390,17 @@ fn show_default_zap( }; let id = ui.id().with("default_zap_amount"); - ui.add( - egui::TextEdit::singleline(text) - .desired_width(desired_width) - .margin(egui::Margin::same(8)) - .font(font) - .id(id), - ); + + { + let r = ui.add( + egui::TextEdit::singleline(text) + .desired_width(desired_width) + .margin(egui::Margin::same(8)) + .font(font) + .id(id)); + + notedeck_ui::include_input(ui, &r); + } ui.memory_mut(|m| m.request_focus(id)); diff --git a/crates/notedeck_dave/src/ui/dave.rs b/crates/notedeck_dave/src/ui/dave.rs index 1ca8e6db..792280a9 100644 --- a/crates/notedeck_dave/src/ui/dave.rs +++ b/crates/notedeck_dave/src/ui/dave.rs @@ -342,6 +342,7 @@ impl<'a> DaveUi<'a> { ) .frame(false), ); + notedeck_ui::include_input(ui, &r); if r.has_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter)) { DaveResponse::send() diff --git a/crates/notedeck_ui/src/context_menu.rs b/crates/notedeck_ui/src/context_menu.rs index 8292d8c3..bec37064 100644 --- a/crates/notedeck_ui/src/context_menu.rs +++ b/crates/notedeck_ui/src/context_menu.rs @@ -20,6 +20,7 @@ fn handle_paste(clipboard: &mut Clipboard, input: &mut String, paste_behavior: P } pub fn input_context( + ui: &mut egui::Ui, response: &egui::Response, clipboard: &mut Clipboard, input: &mut String, @@ -46,4 +47,7 @@ pub fn input_context( if response.middle_clicked() { handle_paste(clipboard, input, paste_behavior) } + + // for keyboard visibility + crate::include_input(ui, response) } diff --git a/crates/notedeck_ui/src/lib.rs b/crates/notedeck_ui/src/lib.rs index 423fc5be..ff0b69e3 100644 --- a/crates/notedeck_ui/src/lib.rs +++ b/crates/notedeck_ui/src/lib.rs @@ -62,3 +62,34 @@ pub fn secondary_label(ui: &mut egui::Ui, s: impl Into) -> egui::Respons let color = ui.style().visuals.noninteractive().fg_stroke.color; ui.add(Label::new(RichText::new(s).size(10.0).color(color)).selectable(false)) } + +const INPUT_RECT_KEY: &str = "notedeck_input_rect"; + +/// Includes an input rect for keyboard visibility purposes. We use this to move the screen up if +/// a soft keyboard intersects with the input box +pub fn include_input(ui: &mut egui::Ui, resp: &egui::Response) { + // only include input if we have focus + if !resp.has_focus() { + return; + } + + ui.data_mut(|d| { + let id = egui::Id::new(INPUT_RECT_KEY); + match d.get_temp::(id) { + Some(r) => d.insert_temp(id, resp.rect.union(r)), + None => d.insert_temp(id, resp.rect), + } + }) +} + +/// Set the last input rect for keyboard visibility purposes. We use this to move the screen up if +/// a soft keyboard intersects with the input box +pub fn input_rect(ui: &mut egui::Ui) -> Option { + ui.data(|d| d.get_temp(egui::Id::new(INPUT_RECT_KEY))) +} + +/// Set the last input rect for keyboard visibility purposes. We use this to move the screen up if +/// a soft keyboard intersects with the input box +pub fn clear_input_rect(ui: &mut egui::Ui) { + ui.data_mut(|d| d.remove::(egui::Id::new(INPUT_RECT_KEY))) +}