Răsfoiți Sursa

app: disable miniquad resizing the content when IME is shown. instead transfer the responsibility directly to the app, this way we can avoid flickering where our canvas is resized BEFORE ui updates have been applied, and secondly we can draw a full screen canvas.

jkds 7 luni în urmă
părinte
comite
13755ab328

+ 31 - 0
bin/app/java/MainActivity.java

@@ -18,6 +18,37 @@ import videodecode.VideoDecoder;
 
 //% END
 
+//% RESIZING_LAYOUT_BODY
+
+native static void onApplyInsets(
+    int sys_left, int sys_top, int sys_right, int sys_bottom,
+    int ime_left, int ime_top, int ime_right, int ime_bottom
+);
+
+//% END
+
+//% RESIZING_LAYOUT_ON_APPLY_WINDOW_INSETS
+
+{
+    Insets imeInsets = insets.getInsets(WindowInsets.Type.ime());
+    Insets sysInsets = insets.getInsets(WindowInsets.Type.systemBars());
+
+    // Screen: (1440, 3064)
+    // IME height: 1056
+    // Sys insets: (0, 152, 0, 135)
+
+    onApplyInsets(
+        sysInsets.left, sysInsets.top, sysInsets.right, sysInsets.bottom,
+        imeInsets.left, imeInsets.top, imeInsets.right, imeInsets.bottom
+    );
+}
+// Workaround for Java error due to remaining body.
+// We handle the insets in our app directly.
+if (true)
+    return insets;
+
+//% END
+
 //% MAIN_ACTIVITY_BODY
 
 private ViewGroup rootView;

+ 71 - 0
bin/app/src/android/insets.rs

@@ -0,0 +1,71 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2025 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use miniquad::native::android::{self, ndk_sys, ndk_utils};
+use parking_lot::Mutex as SyncMutex;
+use std::sync::LazyLock;
+
+use crate::gfx::Rectangle;
+
+type Insets = [f32; 4];
+type InsetsSender = async_channel::Sender<Insets>;
+
+struct InsetsGlobals {
+    sender: Option<InsetsSender>,
+    insets: Insets,
+}
+
+static GLOBALS: LazyLock<SyncMutex<InsetsGlobals>> =
+    LazyLock::new(|| SyncMutex::new(InsetsGlobals { sender: None, insets: [0.; 4] }));
+
+pub fn set_sender(sender: InsetsSender) {
+    GLOBALS.lock().sender = Some(sender);
+}
+
+pub fn get_insets() -> Insets {
+    GLOBALS.lock().insets.clone()
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn Java_darkfi_darkfi_1app_ResizingLayout_onApplyInsets(
+    _env: *mut ndk_sys::JNIEnv,
+    _: ndk_sys::jobject,
+    sys_left: ndk_sys::jint,
+    sys_top: ndk_sys::jint,
+    sys_right: ndk_sys::jint,
+    sys_bottom: ndk_sys::jint,
+    ime_left: ndk_sys::jint,
+    ime_top: ndk_sys::jint,
+    ime_right: ndk_sys::jint,
+    ime_bottom: ndk_sys::jint,
+) {
+    debug!(
+        target: "android::insets",
+        "onApplyInsets() \
+            sys=({sys_left}, {sys_top}, {sys_right}, {sys_bottom}) \
+            ime=({ime_left}, {ime_top}, {ime_right}, {ime_bottom}) \
+        )"
+    );
+    let mut globals = GLOBALS.lock();
+    globals.insets = [sys_left as f32, sys_top as f32, sys_right as f32, sys_bottom as f32];
+    if let Some(sender) = &globals.sender {
+        let _ = sender.try_send(globals.insets.clone());
+    } else {
+        warn!(target: "android::insets", "Dropping insets notify since no sender is set");
+    }
+}

+ 1 - 0
bin/app/src/android/mod.rs

@@ -22,6 +22,7 @@ use std::{collections::HashMap, path::PathBuf, sync::LazyLock};
 
 use crate::AndroidSuggestEvent;
 
+pub mod insets;
 pub mod vid;
 
 macro_rules! call_mainactivity_int_method {

+ 16 - 17
bin/app/src/app/mod.rs

@@ -40,6 +40,7 @@ use crate::{
 pub mod locale;
 use locale::read_locale_ftl;
 mod node;
+use node::create_window;
 mod schema;
 use schema::get_settingsdb_path;
 
@@ -87,17 +88,6 @@ impl App {
             }
         };
 
-        let mut window = SceneNode::new("window", SceneNodeType::Window);
-
-        let i18n_fish = self.setup_locale(&mut window);
-
-        let mut prop = Property::new("screen_size", PropertyType::Float32, PropertySubType::Pixel);
-        prop.set_array_len(2);
-        window.add_property(prop).unwrap();
-
-        window.add_signal("start", "App UI started", vec![]).unwrap();
-        window.add_signal("stop", "App UI stopped", vec![]).unwrap();
-
         let setting_root = SceneNode::new("setting", SceneNodeType::SettingRoot);
         let setting_root = setting_root.setup_null();
         let settings_tree = db.open_tree("settings").unwrap();
@@ -111,7 +101,7 @@ impl App {
         #[cfg(not(target_os = "android"))]
         let window_scale = 1.;
 
-        d!("Setting window_scale to {window_scale}");
+        d!("Setting window scale to {window_scale}");
 
         settings.add_setting("scale", PropertyValue::Float32(window_scale));
         //settings.load_settings();
@@ -128,6 +118,19 @@ impl App {
             self.tasks.lock().unwrap().push(setting_task);
         }
 
+        let i18n_fish = self.setup_locale();
+
+        let mut window = create_window("window");
+        #[cfg(target_os = "android")]
+        {
+            let insets = android::insets::get_insets();
+            d!("Setting window insets to {insets:?}");
+            let prop = window.get_property("insets").unwrap();
+            let atom = &mut PropertyAtomicGuard::none();
+            for i in 0..4 {
+                prop.set_f32(atom, Role::App, i, insets[i]).unwrap();
+            }
+        }
         let window = window
             .setup(|me| {
                 Window::new(me, self.render_api.clone(), i18n_fish.clone(), setting_root.clone())
@@ -146,7 +149,7 @@ impl App {
         Ok(None)
     }
 
-    fn setup_locale(&self, window: &mut SceneNode) -> I18nBabelFish {
+    fn setup_locale(&self) -> I18nBabelFish {
         /*
         let i18n_src = indoc::indoc! {"
             hello-world = Hello, world!
@@ -176,10 +179,6 @@ impl App {
         info!(target: "app", "Locale: {:?}", locales);
         */
 
-        let mut prop = Property::new("locale", PropertyType::Str, PropertySubType::Locale);
-        prop.set_defaults_str(vec![locale.to_string()]).unwrap();
-        window.add_property(prop).unwrap();
-
         i18n_fish
     }
 

+ 26 - 10
bin/app/src/app/node.rs

@@ -23,8 +23,33 @@ use crate::{
 
 macro_rules! t { ($($arg:tt)*) => { trace!(target: "app::node", $($arg)*); } }
 
+pub fn create_window(name: &str) -> SceneNode {
+    let mut node = SceneNode::new(name, SceneNodeType::Window);
+
+    let mut prop = Property::new("locale", PropertyType::Str, PropertySubType::Locale);
+    prop.set_defaults_str(vec!["en-US".to_string()]).unwrap();
+    node.add_property(prop).unwrap();
+
+    let mut prop = Property::new("screen_size", PropertyType::Float32, PropertySubType::Pixel);
+    prop.set_array_len(2);
+    node.add_property(prop).unwrap();
+
+    let mut prop = Property::new("insets", PropertyType::Float32, PropertySubType::Pixel);
+    prop.set_ui_text(
+        "Window Insets",
+        "Window insets applied by the system (left, top, right, bottom)",
+    );
+    prop.set_array_len(4);
+    prop.set_defaults_f32(vec![0., 0., 0., 0.]).unwrap();
+    node.add_property(prop).unwrap();
+
+    node.add_signal("start", "App UI started", vec![]).unwrap();
+    node.add_signal("stop", "App UI stopped", vec![]).unwrap();
+
+    node
+}
+
 pub fn create_layer(name: &str) -> SceneNode {
-    t!("create_layer({name})");
     let mut node = SceneNode::new(name, SceneNodeType::Layer);
     let prop = Property::new("is_visible", PropertyType::Bool, PropertySubType::Null);
     node.add_property(prop).unwrap();
@@ -44,7 +69,6 @@ pub fn create_layer(name: &str) -> SceneNode {
 }
 
 pub fn create_vector_art(name: &str) -> SceneNode {
-    t!("create_vector_art({name})");
     let mut node = SceneNode::new(name, SceneNodeType::VectorArt);
 
     let mut prop = Property::new("is_visible", PropertyType::Bool, PropertySubType::Null);
@@ -66,7 +90,6 @@ pub fn create_vector_art(name: &str) -> SceneNode {
 }
 
 pub fn create_button(name: &str) -> SceneNode {
-    t!("create_button({name})");
     let mut node = SceneNode::new(name, SceneNodeType::Button);
 
     let mut prop = Property::new("is_active", PropertyType::Bool, PropertySubType::Null);
@@ -90,7 +113,6 @@ pub fn create_button(name: &str) -> SceneNode {
 }
 
 pub fn create_shortcut(name: &str) -> SceneNode {
-    t!("create_shortcut({name})");
     let mut node = SceneNode::new(name, SceneNodeType::Shortcut);
 
     let mut prop = Property::new("key", PropertyType::Str, PropertySubType::Null);
@@ -107,7 +129,6 @@ pub fn create_shortcut(name: &str) -> SceneNode {
 
 #[allow(dead_code)]
 pub fn create_gesture(name: &str) -> SceneNode {
-    t!("create_gesture({name})");
     let mut node = SceneNode::new(name, SceneNodeType::Gesture);
 
     let prop = Property::new("priority", PropertyType::Uint32, PropertySubType::Null);
@@ -125,7 +146,6 @@ pub fn create_gesture(name: &str) -> SceneNode {
 
 #[allow(dead_code)]
 pub fn create_image(name: &str) -> SceneNode {
-    t!("create_image({name})");
     let mut node = SceneNode::new(name, SceneNodeType::Image);
 
     let mut prop = Property::new("rect", PropertyType::Float32, PropertySubType::Pixel);
@@ -153,7 +173,6 @@ pub fn create_image(name: &str) -> SceneNode {
 }
 
 pub fn create_video(name: &str) -> SceneNode {
-    t!("create_video({name})");
     let mut node = SceneNode::new(name, SceneNodeType::Image);
 
     let mut prop = Property::new("rect", PropertyType::Float32, PropertySubType::Pixel);
@@ -185,7 +204,6 @@ pub fn create_video(name: &str) -> SceneNode {
 }
 
 pub fn create_text(name: &str) -> SceneNode {
-    t!("create_text({name})");
     let mut node = SceneNode::new(name, SceneNodeType::Text);
 
     let mut prop = Property::new("rect", PropertyType::Float32, PropertySubType::Pixel);
@@ -379,7 +397,6 @@ pub fn create_multiline_edit(name: &str) -> SceneNode {
 }
 
 pub fn create_chatview(name: &str) -> SceneNode {
-    t!("create_chatview({name})");
     let mut node = SceneNode::new(name, SceneNodeType::ChatView);
 
     let mut prop = Property::new("rect", PropertyType::Float32, PropertySubType::Pixel);
@@ -491,7 +508,6 @@ pub fn create_chatview(name: &str) -> SceneNode {
 }
 
 pub fn create_emoji_picker(name: &str) -> SceneNode {
-    t!("create_emoji_picker({name})");
     let mut node = SceneNode::new(name, SceneNodeType::EmojiPicker);
 
     let mut prop = Property::new("rect", PropertyType::Float32, PropertySubType::Pixel);

+ 3 - 3
bin/app/src/app/schema/chat.rs

@@ -187,7 +187,7 @@ fn is_ime_visible() -> bool {
 
 pub async fn make(
     app: &App,
-    window: SceneNodePtr,
+    content: SceneNodePtr,
     channel: &str,
     db: &sled::Db,
     i18n_fish: &I18nBabelFish,
@@ -229,7 +229,7 @@ pub async fn make(
     layer_node.set_property_bool(atom, Role::App, "is_visible", false).unwrap();
     layer_node.set_property_u32(atom, Role::App, "z_index", 1).unwrap();
     let layer_node = layer_node.setup(|me| Layer::new(me, app.render_api.clone())).await;
-    window.link(layer_node.clone());
+    content.link(layer_node.clone());
 
     // Create a bg mesh on top to fade the bg image
     let node = create_vector_art("bg");
@@ -326,7 +326,7 @@ pub async fn make(
         let editz_node = layer_node2.lookup_node("/content/editz").unwrap();
         editz_node.call_method("unfocus", vec![]).await.unwrap();
 
-        let menu_node = sg_root.lookup_node("/window/menu_layer").unwrap();
+        let menu_node = sg_root.lookup_node("/window/content/menu_layer").unwrap();
         menu_node.set_property_bool(atom, Role::App, "is_visible", true).unwrap();
 
         chatview_is_visible.set(atom, false);

+ 3 - 3
bin/app/src/app/schema/menu.rs

@@ -62,7 +62,7 @@ mod ui_consts {
 
 use ui_consts::*;
 
-pub async fn make(app: &App, window: SceneNodePtr, i18n_fish: &I18nBabelFish) {
+pub async fn make(app: &App, content: SceneNodePtr, i18n_fish: &I18nBabelFish) {
     let window_scale = PropertyFloat32::wrap(
         &app.sg_root.lookup_node("/setting/scale").unwrap(),
         Role::Internal,
@@ -82,7 +82,7 @@ pub async fn make(app: &App, window: SceneNodePtr, i18n_fish: &I18nBabelFish) {
     layer_node.set_property_bool(atom, Role::App, "is_visible", true).unwrap();
     layer_node.set_property_u32(atom, Role::App, "z_index", 1).unwrap();
     let layer_node = layer_node.setup(|me| Layer::new(me, app.render_api.clone())).await;
-    window.link(layer_node.clone());
+    content.link(layer_node.clone());
 
     // Channels label bg
     let node = create_vector_art("channels_label_bg");
@@ -248,7 +248,7 @@ pub async fn make(app: &App, window: SceneNodePtr, i18n_fish: &I18nBabelFish) {
 
         let (slot, recvr) = Slot::new(channel.to_string() + "_clicked");
         node.register("click", slot).unwrap();
-        let chatview_path = "/window/".to_string() + channel + "_chat_layer";
+        let chatview_path = "/window/content/".to_string() + channel + "_chat_layer";
         let chatview_node = app.sg_root.lookup_node(chatview_path).unwrap();
         let chatview_is_visible =
             PropertyBool::wrap(&chatview_node, Role::App, "is_visible", 0).unwrap();

+ 54 - 8
bin/app/src/app/schema/mod.rs

@@ -320,6 +320,52 @@ pub async fn make(app: &App, window: SceneNodePtr, i18n_fish: &I18nBabelFish) {
         window.link(node);
     }
 
+    // Root content layer
+    let content = create_layer("content");
+    let prop = content.get_property("rect").unwrap();
+    prop.set_expr(atom, Role::App, 0, expr::load_var("insets_left")).unwrap();
+    prop.set_expr(atom, Role::App, 1, expr::load_var("insets_top")).unwrap();
+    let code = cc.compile("w - insets_right").unwrap();
+    prop.set_expr(atom, Role::App, 2, code).unwrap();
+    let code = cc.compile("h - insets_bottom").unwrap();
+    prop.set_expr(atom, Role::App, 3, code).unwrap();
+    let window_insets = window.get_property("insets").unwrap();
+    prop.add_depend(&window_insets, 0, "insets_left");
+    prop.add_depend(&window_insets, 1, "insets_top");
+    prop.add_depend(&window_insets, 2, "insets_right");
+    prop.add_depend(&window_insets, 3, "insets_bottom");
+    content.set_property_bool(atom, Role::App, "is_visible", true).unwrap();
+    content.set_property_u32(atom, Role::App, "z_index", 1).unwrap();
+    let content = content.setup(|me| Layer::new(me, app.render_api.clone())).await;
+    window.link(content.clone());
+
+    // Debug bg layer cos of content size issues
+    let node = create_vector_art("bgdbg");
+    let prop = node.get_property("rect").unwrap();
+    prop.set_f32(atom, Role::App, 0, 0.).unwrap();
+    prop.set_f32(atom, Role::App, 1, 0.).unwrap();
+    prop.set_expr(atom, Role::App, 2, expr::load_var("w")).unwrap();
+    prop.set_expr(atom, Role::App, 3, expr::load_var("h")).unwrap();
+    node.set_property_u32(atom, Role::App, "z_index", 1).unwrap();
+    let mut shape = VectorShape::new();
+    shape.add_filled_box(
+        expr::const_f32(0.),
+        expr::const_f32(0.),
+        expr::load_var("w"),
+        expr::load_var("h"),
+        [0.8, 0., 0., 0.3],
+    );
+    shape.add_outline(
+        expr::const_f32(0.),
+        expr::const_f32(0.),
+        expr::load_var("w"),
+        expr::load_var("h"),
+        5.,
+        [0., 1., 0., 1.],
+    );
+    let node = node.setup(|me| VectorArt::new(me, shape, app.render_api.clone())).await;
+    content.link(node);
+
     let netlayer_node = create_layer("netstatus_layer");
     let prop = netlayer_node.get_property("rect").unwrap();
     let code = cc.compile("w - NETSTATUS_ICON_SIZE").unwrap();
@@ -332,7 +378,7 @@ pub async fn make(app: &App, window: SceneNodePtr, i18n_fish: &I18nBabelFish) {
     netlayer_node.set_property_bool(atom, Role::App, "is_visible", true).unwrap();
     netlayer_node.set_property_u32(atom, Role::App, "z_index", 2).unwrap();
     let netlayer_node = netlayer_node.setup(|me| Layer::new(me, app.render_api.clone())).await;
-    window.link(netlayer_node.clone());
+    content.link(netlayer_node.clone());
 
     let node = create_vector_art("net0");
     let prop = node.get_property("rect").unwrap();
@@ -405,7 +451,7 @@ pub async fn make(app: &App, window: SceneNodePtr, i18n_fish: &I18nBabelFish) {
     settingslayer_node.set_property_u32(atom, Role::App, "z_index", 2).unwrap();
     let settingslayer_node =
         settingslayer_node.setup(|me| Layer::new(me, app.render_api.clone())).await;
-    window.link(settingslayer_node.clone());
+    content.link(settingslayer_node.clone());
 
     // Background
     let node = create_vector_art("settings_btn_bg");
@@ -452,7 +498,7 @@ pub async fn make(app: &App, window: SceneNodePtr, i18n_fish: &I18nBabelFish) {
         //  router currently points to view1 and we call router.goto("./view2")).
         //
         //  2. Support of wildcard in lookups in .get_children() or another method, like this "*_chat_layer".
-        let windows = sg_root.lookup_node("/window").unwrap().get_children();
+        let windows = sg_root.lookup_node("/window/content").unwrap().get_children();
         let target_substrings = vec!["_chat_layer", "menu_layer", "settings_layer"];
         for node in windows.iter() {
             if target_substrings.iter().any(|&s| node.name.contains(s)) {
@@ -463,7 +509,7 @@ pub async fn make(app: &App, window: SceneNodePtr, i18n_fish: &I18nBabelFish) {
         }
 
         // Show settings
-        let settings_node = sg_root.lookup_node("/window/settings_layer").unwrap();
+        let settings_node = sg_root.lookup_node("/window/content/settings_layer").unwrap();
         settings_node.set_property_bool(atom, Role::App, "is_visible", true).unwrap();
     };
 
@@ -511,7 +557,7 @@ pub async fn make(app: &App, window: SceneNodePtr, i18n_fish: &I18nBabelFish) {
     for channel in CHANNELS {
         chat::make(
             app,
-            window.clone(),
+            content.clone(),
             channel,
             &db,
             i18n_fish,
@@ -520,11 +566,11 @@ pub async fn make(app: &App, window: SceneNodePtr, i18n_fish: &I18nBabelFish) {
         )
         .await;
     }
-    menu::make(app, window.clone(), i18n_fish).await;
+    menu::make(app, content.clone(), i18n_fish).await;
 
     // @@@ Debug stuff @@@
-    //let chatview_node = app.sg_root.lookup_node("/window/dev_chat_layer").unwrap();
+    //let chatview_node = app.sg_root.lookup_node("/window/content/dev_chat_layer").unwrap();
     //chatview_node.set_property_bool(atom, Role::App, "is_visible", true).unwrap();
-    //let menu_node = app.sg_root.lookup_node("/window/menu_layer").unwrap();
+    //let menu_node = app.sg_root.lookup_node("/window/content/menu_layer").unwrap();
     //menu_node.set_property_bool(atom, Role::App, "is_visible", false).unwrap();
 }

+ 7 - 7
bin/app/src/main.rs

@@ -314,7 +314,7 @@ async fn load_plugins(
             let nick = String::decode(&mut cur).unwrap();
             let msg = String::decode(&mut cur).unwrap();
 
-            let node_path = format!("/window/{channel}_chat_layer/content/chatty");
+            let node_path = format!("/window/content/{channel}_chat_layer/content/chatty");
             t!("Attempting to relay message to {node_path}");
             let Some(chatview) = sg_root2.lookup_node(&node_path) else {
                 d!("Ignoring message since {node_path} doesn't exist");
@@ -335,13 +335,13 @@ async fn load_plugins(
             }
 
             // Apply coloring when you get a message
-            let chat_path = format!("/window/{channel}_chat_layer");
+            let chat_path = format!("/window/content/{channel}_chat_layer");
             let chat_layer = sg_root2.lookup_node(chat_path).unwrap();
             if chat_layer.get_property_bool("is_visible").unwrap() {
                 continue
             }
 
-            let node_path = format!("/window/menu_layer/{channel}_channel_label");
+            let node_path = format!("/window/content/menu_layer/{channel}_channel_label");
             let menu_label = sg_root2.lookup_node(&node_path).unwrap();
             let prop = menu_label.get_property("text_color").unwrap();
             if msg.contains(&darkirc_nick.get()) {
@@ -364,10 +364,10 @@ async fn load_plugins(
     darkirc.register("connect", slot).unwrap();
     let sg_root2 = sg_root.clone();
     let listen_connect = ex.spawn(async move {
-        let net0 = sg_root2.lookup_node("/window/netstatus_layer/net0").unwrap();
-        let net1 = sg_root2.lookup_node("/window/netstatus_layer/net1").unwrap();
-        let net2 = sg_root2.lookup_node("/window/netstatus_layer/net2").unwrap();
-        let net3 = sg_root2.lookup_node("/window/netstatus_layer/net3").unwrap();
+        let net0 = sg_root2.lookup_node("/window/content/netstatus_layer/net0").unwrap();
+        let net1 = sg_root2.lookup_node("/window/content/netstatus_layer/net1").unwrap();
+        let net2 = sg_root2.lookup_node("/window/content/netstatus_layer/net2").unwrap();
+        let net3 = sg_root2.lookup_node("/window/content/netstatus_layer/net3").unwrap();
 
         let net0_is_visible = PropertyBool::wrap(&net0, Role::App, "is_visible", 0).unwrap();
         let net1_is_visible = PropertyBool::wrap(&net1, Role::App, "is_visible", 0).unwrap();

+ 3 - 3
bin/app/src/prop/wrap.rs

@@ -365,14 +365,14 @@ impl PropertyRect {
             self.prop.get_f32(3).ok()?,
         ]))
     }
+    */
 
     pub fn set(&self, atom: &mut PropertyAtomicGuard, rect: &Rectangle) {
         self.prop().set_f32(atom, self.role, 0, rect.x).unwrap();
         self.prop().set_f32(atom, self.role, 1, rect.y).unwrap();
-        self.prop().set_f32(atom, self.role, 2, rect.y).unwrap();
-        self.prop().set_f32(atom, self.role, 3, rect.y).unwrap();
+        self.prop().set_f32(atom, self.role, 2, rect.w).unwrap();
+        self.prop().set_f32(atom, self.role, 3, rect.h).unwrap();
     }
-    */
 
     #[inline]
     pub fn prop(&self) -> PropertyPtr {

+ 0 - 2
bin/app/src/ui/button.rs

@@ -49,8 +49,6 @@ pub struct Button {
 
 impl Button {
     pub async fn new(node: SceneNodeWeak) -> Pimpl {
-        t!("Button::new()");
-
         let node_ref = &node.upgrade().unwrap();
         let is_active = PropertyBool::wrap(node_ref, Role::Internal, "is_active", 0).unwrap();
         let rect = PropertyRect::wrap(node_ref, Role::Internal, "rect").unwrap();

+ 0 - 2
bin/app/src/ui/chatview/mod.rs

@@ -199,8 +199,6 @@ impl ChatView {
         text_shaper: TextShaperPtr,
         sg_root: SceneNodePtr,
     ) -> Pimpl {
-        t!("ChatView::new()");
-
         let node_ref = &node.upgrade().unwrap();
         let rect = PropertyRect::wrap(node_ref, Role::Internal, "rect").unwrap();
         let scroll = PropertyFloat32::wrap(node_ref, Role::Internal, "scroll", 0).unwrap();

+ 0 - 2
bin/app/src/ui/edit/mod.rs

@@ -269,8 +269,6 @@ impl BaseEdit {
         render_api: RenderApi,
         edit_type: BaseEditType,
     ) -> Pimpl {
-        t!("BaseEdit::new()");
-
         let node_ref = &node.upgrade().unwrap();
         let is_active = PropertyBool::wrap(node_ref, Role::Internal, "is_active", 0).unwrap();
         let is_focused = PropertyBool::wrap(node_ref, Role::Internal, "is_focused", 0).unwrap();

+ 0 - 2
bin/app/src/ui/emoji_picker/mod.rs

@@ -78,8 +78,6 @@ impl EmojiPicker {
         render_api: RenderApi,
         emoji_meshes: EmojiMeshesPtr,
     ) -> Pimpl {
-        t!("EmojiPicker::new()");
-
         let node_ref = &node.upgrade().unwrap();
         let rect = PropertyRect::wrap(node_ref, Role::Internal, "rect").unwrap();
         let z_index = PropertyUint32::wrap(node_ref, Role::Internal, "z_index", 0).unwrap();

+ 0 - 2
bin/app/src/ui/gesture.rs

@@ -52,8 +52,6 @@ pub struct Gesture {
 
 impl Gesture {
     pub async fn new(node: SceneNodeWeak) -> Pimpl {
-        t!("Gesture::new()");
-
         let node_ref = &node.upgrade().unwrap();
         let priority = PropertyUint32::wrap(node_ref, Role::Internal, "priority", 0).unwrap();
 

+ 0 - 2
bin/app/src/ui/image.rs

@@ -57,8 +57,6 @@ pub struct Image {
 
 impl Image {
     pub async fn new(node: SceneNodeWeak, render_api: RenderApi) -> Pimpl {
-        t!("Image::new()");
-
         let node_ref = &node.upgrade().unwrap();
         let rect = PropertyRect::wrap(node_ref, Role::Internal, "rect").unwrap();
         let uv = PropertyRect::wrap(node_ref, Role::Internal, "uv").unwrap();

+ 0 - 1
bin/app/src/ui/layer.rs

@@ -56,7 +56,6 @@ pub struct Layer {
 impl Layer {
     pub async fn new(node: SceneNodeWeak, render_api: RenderApi) -> Pimpl {
         let node_ref = &node.upgrade().unwrap();
-        t!("Layer::new({node_ref:?})");
         let is_visible = PropertyBool::wrap(node_ref, Role::Internal, "is_visible", 0).unwrap();
         let rect = PropertyRect::wrap(node_ref, Role::Internal, "rect").unwrap();
         let z_index = PropertyUint32::wrap(node_ref, Role::Internal, "z_index", 0).unwrap();

+ 0 - 2
bin/app/src/ui/shortcut.rs

@@ -44,8 +44,6 @@ pub struct Shortcut {
 
 impl Shortcut {
     pub async fn new(node: SceneNodeWeak) -> Pimpl {
-        t!("Shortcut::new()");
-
         let node_ref = &node.upgrade().unwrap();
         let key = node_ref.get_property("key").unwrap();
         let priority = PropertyUint32::wrap(node_ref, Role::Internal, "priority", 0).unwrap();

+ 0 - 2
bin/app/src/ui/text.rs

@@ -69,8 +69,6 @@ impl Text {
         render_api: RenderApi,
         i18n_fish: I18nBabelFish,
     ) -> Pimpl {
-        t!("Text::new()");
-
         let node_ref = &node.upgrade().unwrap();
         let rect = PropertyRect::wrap(node_ref, Role::Internal, "rect").unwrap();
         let z_index = PropertyUint32::wrap(node_ref, Role::Internal, "z_index", 0).unwrap();

+ 0 - 2
bin/app/src/ui/vector_art/mod.rs

@@ -56,8 +56,6 @@ pub struct VectorArt {
 
 impl VectorArt {
     pub async fn new(node: SceneNodeWeak, shape: VectorShape, render_api: RenderApi) -> Pimpl {
-        t!("VectorArt::new()");
-
         let node_ref = &node.upgrade().unwrap();
         let is_visible = PropertyBool::wrap(node_ref, Role::Internal, "is_visible", 0).unwrap();
         let rect = PropertyRect::wrap(node_ref, Role::Internal, "rect").unwrap();

+ 0 - 2
bin/app/src/ui/vid/mod.rs

@@ -92,8 +92,6 @@ pub struct Video {
 
 impl Video {
     pub async fn new(node: SceneNodeWeak, render_api: RenderApi, ex: ExecutorPtr) -> Pimpl {
-        t!("Video::new()");
-
         let node_ref = &node.upgrade().unwrap();
         let rect = PropertyRect::wrap(node_ref, Role::Internal, "rect").unwrap();
         let uv = PropertyRect::wrap(node_ref, Role::Internal, "uv").unwrap();

+ 28 - 3
bin/app/src/ui/win.rs

@@ -30,13 +30,17 @@ use crate::{
         GraphicsEventTouchSub, Point, Rectangle, RenderApi,
     },
     prop::{
-        BatchGuardPtr, PropertyAtomicGuard, PropertyDimension, PropertyFloat32, PropertyStr, Role,
+        BatchGuardPtr, PropertyAtomicGuard, PropertyDimension, PropertyFloat32, PropertyRect,
+        PropertyStr, Role,
     },
     scene::{Pimpl, SceneNodePtr, SceneNodeWeak},
     util::i18n::I18nBabelFish,
     ExecutorPtr,
 };
 
+#[cfg(target_os = "android")]
+use crate::android;
+
 use super::{get_children_ordered, get_ui_object3, get_ui_object_ptr, OnModify};
 
 macro_rules! i { ($($arg:tt)*) => { info!(target: "ui::window", $($arg)*); } }
@@ -60,6 +64,7 @@ pub struct Window {
     locale: PropertyStr,
     screen_size: PropertyDimension,
     scale: PropertyFloat32,
+    insets: PropertyRect,
 }
 
 impl Window {
@@ -69,8 +74,6 @@ impl Window {
         i18n_fish: I18nBabelFish,
         setting_root: SceneNodePtr,
     ) -> Pimpl {
-        t!("Window::new()");
-
         let node_ref = &node.upgrade().unwrap();
         let locale = PropertyStr::wrap(node_ref, Role::Internal, "locale", 0).unwrap();
         let screen_size = PropertyDimension::wrap(node_ref, Role::Internal, "screen_size").unwrap();
@@ -82,6 +85,8 @@ impl Window {
         )
         .unwrap();
 
+        let insets = PropertyRect::wrap(node_ref, Role::Internal, "insets").unwrap();
+
         let self_ = Arc::new(Self {
             node,
             render_api,
@@ -91,6 +96,7 @@ impl Window {
             locale,
             screen_size,
             scale,
+            insets,
         });
 
         Pimpl::Window(self_)
@@ -171,6 +177,23 @@ impl Window {
         let me2 = me.clone();
         let touch_task = ex.spawn(async move { while Self::process_touch(&me2, &ev_sub).await {} });
 
+        #[cfg(target_os = "android")]
+        let insets_task = {
+            let (insets_tx, insets_rx) = async_channel::unbounded();
+            android::insets::set_sender(insets_tx);
+
+            let me = me.clone();
+            let insets = self.insets.clone();
+            ex.spawn(async move {
+                while let Ok(insets_val) = insets_rx.recv().await {
+                    let Some(self_) = me.upgrade() else { break };
+                    let atom = &mut self_.render_api.make_guard(gfxtag!("Window::insets_task"));
+                    insets.set(atom, &Rectangle::from(insets_val));
+                    self_.draw(atom).await;
+                }
+            })
+        };
+
         async fn reload_locale(self_: Arc<Window>, batch: BatchGuardPtr) {
             let atom = &mut batch.spawn();
             self_.reload_locale(atom).await;
@@ -196,6 +219,8 @@ impl Window {
             touch_task,
         ];
         tasks.append(&mut on_modify.tasks);
+        #[cfg(target_os = "android")]
+        tasks.push(insets_task);
         *self.tasks.lock() = tasks;
 
         for child in self.get_children() {