瀏覽代碼

wallet: chatedit proper wrapping and adjustment by chatview.

darkfi 1 年之前
父節點
當前提交
f62f3179ad

+ 2 - 0
bin/darkwallet/gui/print_tree.py

@@ -42,6 +42,8 @@ def print_node_info(parent_path, indent):
                 child_type = "chat_view"
             case SceneNodeType.EDIT_BOX:
                 child_type = "edit_box"
+            case SceneNodeType.CHAT_EDIT:
+                child_type = "chat_edit"
             case SceneNodeType.BUTTON:
                 child_type = "button"
 

+ 3 - 1
bin/darkwallet/pydrk/api.py

@@ -60,7 +60,9 @@ class SceneNodeType:
     PLUGIN = 15
     CHAT_VIEW = 16
     EDIT_BOX = 17
-    BUTTON = 18
+    CHAT_EDIT = 18
+    IMAGE = 18
+    BUTTON = 19
 
 class PropertyType:
     NULL = 0

+ 112 - 0
bin/darkwallet/src/app/node.rs

@@ -245,6 +245,118 @@ pub fn create_editbox(name: &str) -> SceneNode {
     node
 }
 
+pub fn create_chatedit(name: &str) -> SceneNode {
+    debug!(target: "app", "create_chatedit({name})");
+    let mut node = SceneNode::new(name, SceneNodeType::ChatEdit);
+
+    let mut prop = Property::new("is_active", PropertyType::Bool, PropertySubType::Null);
+    prop.set_ui_text("Is Active", "An active EditBox can be focused");
+    node.add_property(prop).unwrap();
+
+    let mut prop = Property::new("is_focused", PropertyType::Bool, PropertySubType::Null);
+    prop.set_ui_text("Is Focused", "A focused EditBox receives input");
+    node.add_property(prop).unwrap();
+
+    let prop = Property::new("max_height", PropertyType::Float32, PropertySubType::Pixel);
+    node.add_property(prop).unwrap();
+
+    let mut prop = Property::new("rect", PropertyType::Float32, PropertySubType::Pixel);
+    prop.set_array_len(4);
+    prop.allow_exprs();
+    node.add_property(prop).unwrap();
+
+    let prop = Property::new("baseline", PropertyType::Float32, PropertySubType::Pixel);
+    node.add_property(prop).unwrap();
+
+    let mut prop = Property::new("scroll", PropertyType::Float32, PropertySubType::Pixel);
+    prop.set_range_f32(0., f32::MAX);
+    node.add_property(prop).unwrap();
+
+    let mut prop = Property::new("scroll_speed", PropertyType::Float32, PropertySubType::Pixel);
+    prop.set_ui_text("Scroll Speed", "Scrolling speed");
+    prop.set_defaults_f32(vec![4.]).unwrap();
+    node.add_property(prop).unwrap();
+
+    let mut prop = Property::new("cursor_pos", PropertyType::Uint32, PropertySubType::Pixel);
+    prop.set_range_u32(0, u32::MAX);
+    node.add_property(prop).unwrap();
+
+    let prop = Property::new("font_size", PropertyType::Float32, PropertySubType::Pixel);
+    node.add_property(prop).unwrap();
+
+    let prop = Property::new("text", PropertyType::Str, PropertySubType::Null);
+    node.add_property(prop).unwrap();
+
+    let mut prop = Property::new("text_color", PropertyType::Float32, PropertySubType::Color);
+    prop.set_array_len(4);
+    prop.set_range_f32(0., 1.);
+    node.add_property(prop).unwrap();
+
+    let mut prop = Property::new("text_hi_color", PropertyType::Float32, PropertySubType::Color);
+    prop.set_array_len(4);
+    prop.set_range_f32(0., 1.);
+    node.add_property(prop).unwrap();
+
+    let mut prop = Property::new("cursor_color", PropertyType::Float32, PropertySubType::Color);
+    prop.set_array_len(4);
+    prop.set_range_f32(0., 1.);
+    node.add_property(prop).unwrap();
+
+    let mut prop = Property::new("cursor_width", PropertyType::Float32, PropertySubType::Pixel);
+    prop.set_defaults_f32(vec![2.]).unwrap();
+    prop.set_range_f32(0., f32::MAX);
+    node.add_property(prop).unwrap();
+
+    let mut prop = Property::new("cursor_ascent", PropertyType::Float32, PropertySubType::Pixel);
+    prop.set_defaults_f32(vec![10.]).unwrap();
+    prop.set_range_f32(0., f32::MAX);
+    node.add_property(prop).unwrap();
+
+    let mut prop = Property::new("cursor_descent", PropertyType::Float32, PropertySubType::Pixel);
+    prop.set_range_f32(0., f32::MAX);
+    node.add_property(prop).unwrap();
+
+    let mut prop = Property::new("select_ascent", PropertyType::Float32, PropertySubType::Pixel);
+    prop.set_defaults_f32(vec![10.]).unwrap();
+    prop.set_range_f32(0., f32::MAX);
+    node.add_property(prop).unwrap();
+
+    let mut prop = Property::new("select_descent", PropertyType::Float32, PropertySubType::Pixel);
+    prop.set_range_f32(0., f32::MAX);
+    node.add_property(prop).unwrap();
+
+    let mut prop = Property::new("hi_bg_color", PropertyType::Float32, PropertySubType::Color);
+    prop.set_array_len(4);
+    prop.set_range_f32(0., 1.);
+    node.add_property(prop).unwrap();
+
+    let mut prop = Property::new("selected", PropertyType::Uint32, PropertySubType::Color);
+    prop.set_array_len(2);
+    prop.allow_null_values();
+    prop.set_defaults_null().unwrap();
+    node.add_property(prop).unwrap();
+
+    let mut prop = Property::new("cursor_blink_time", PropertyType::Uint32, PropertySubType::Null);
+    prop.set_defaults_u32(vec![500]).unwrap();
+    prop.set_range_u32(0, u32::MAX);
+    node.add_property(prop).unwrap();
+
+    let mut prop = Property::new("cursor_idle_time", PropertyType::Uint32, PropertySubType::Null);
+    prop.set_defaults_u32(vec![150]).unwrap();
+    prop.set_range_u32(0, u32::MAX);
+    node.add_property(prop).unwrap();
+
+    let prop = Property::new("z_index", PropertyType::Uint32, PropertySubType::Null);
+    node.add_property(prop).unwrap();
+
+    let prop = Property::new("debug", PropertyType::Bool, PropertySubType::Null);
+    node.add_property(prop).unwrap();
+
+    node.add_signal("enter_pressed", "Enter key pressed", vec![]).unwrap();
+
+    node
+}
+
 pub fn create_chatview(name: &str) -> SceneNode {
     debug!(target: "app", "create_chatview({name})");
     let mut node = SceneNode::new(name, SceneNodeType::ChatView);

+ 14 - 8
bin/darkwallet/src/app/schema.rs

@@ -29,15 +29,16 @@ use crate::{
     scene::{SceneNodePtr, Slot},
     text::TextShaperPtr,
     ui::{
-        Button, ChatView, EditBox, Image, Layer, ShapeVertex, Text, VectorArt, VectorShape, Window,
+        Button, ChatEdit, ChatView, EditBox, Image, Layer, ShapeVertex, Text, VectorArt,
+        VectorShape, Window,
     },
     ExecutorPtr,
 };
 
 use super::{
     node::{
-        create_button, create_chatview, create_editbox, create_image, create_layer, create_text,
-        create_vector_art,
+        create_button, create_chatedit, create_chatview, create_editbox, create_image,
+        create_layer, create_text, create_vector_art,
     },
     populate_tree, App,
 };
@@ -46,6 +47,7 @@ const LIGHTMODE: bool = false;
 
 mod android_ui_consts {
     pub const EDITCHAT_HEIGHT: f32 = 163.;
+    pub const EDITCHAT_BOTTOM_PAD: f32 = 70.;
     pub const EDITCHAT_CURSOR_ASCENT: f32 = 50.;
     pub const EDITCHAT_CURSOR_DESCENT: f32 = 20.;
     pub const EDITCHAT_SELECT_ASCENT: f32 = 40.;
@@ -87,6 +89,7 @@ mod ui_consts {
     pub const KING_PATH: &str = "assets/king.png";
     pub const BG_PATH: &str = "assets/bg.png";
     pub const EDITCHAT_HEIGHT: f32 = 50.;
+    pub const EDITCHAT_BOTTOM_PAD: f32 = 16.;
     pub const EDITCHAT_CURSOR_ASCENT: f32 = 25.;
     pub const EDITCHAT_CURSOR_DESCENT: f32 = 8.;
     pub const EDITCHAT_SELECT_ASCENT: f32 = 30.;
@@ -485,6 +488,7 @@ pub(super) async fn make(app: &App, window: SceneNodePtr) {
     let mut cc = Compiler::new();
 
     cc.add_const_f32("EDITCHAT_HEIGHT", EDITCHAT_HEIGHT);
+    cc.add_const_f32("EDITCHAT_BOTTOM_PAD", EDITCHAT_BOTTOM_PAD);
     cc.add_const_f32("SENDLABEL_WIDTH", SENDLABEL_WIDTH);
     cc.add_const_f32("SENDLABEL_LHS_PAD", SENDLABEL_LHS_PAD);
 
@@ -634,7 +638,7 @@ pub(super) async fn make(app: &App, window: SceneNodePtr) {
     prop.set_f32(Role::App, 1, EDITCHAT_HEIGHT).unwrap();
     let code = cc.compile("w - 30").unwrap();
     prop.set_expr(Role::App, 2, code).unwrap();
-    let code = cc.compile("h - EDITCHAT_HEIGHT - editz_h").unwrap();
+    let code = cc.compile("h - EDITCHAT_HEIGHT - editz_h - EDITCHAT_BOTTOM_PAD").unwrap();
     prop.set_expr(Role::App, 3, code).unwrap();
     let chatview_rect_prop = prop.clone();
     node.set_property_f32(Role::App, "font_size", FONTSIZE).unwrap();
@@ -799,15 +803,17 @@ pub(super) async fn make(app: &App, window: SceneNodePtr) {
     layer_node.clone().link(node);
 
     // Text edit
-    let node = create_editbox("editz");
+    let node = create_chatedit("editz");
     node.set_property_bool(Role::App, "is_active", true).unwrap();
     node.set_property_bool(Role::App, "is_focused", true).unwrap();
 
+    node.set_property_f32(Role::App, "max_height", 300.).unwrap();
+
     let prop = node.get_property("rect").unwrap();
     prop.set_f32(Role::App, 0, EDITCHAT_LHS_PAD).unwrap();
-    let code = cc.compile("h - EDITCHAT_HEIGHT").unwrap();
+    let code = cc.compile("parent_h - rect_h - EDITCHAT_BOTTOM_PAD").unwrap();
     prop.set_expr(Role::App, 1, code).unwrap();
-    let code = cc.compile("w - (SENDLABEL_WIDTH + SENDLABEL_LHS_PAD)").unwrap();
+    let code = cc.compile("parent_w - (SENDLABEL_WIDTH + SENDLABEL_LHS_PAD)").unwrap();
     prop.set_expr(Role::App, 2, code).unwrap();
     prop.set_f32(Role::App, 3, EDITCHAT_HEIGHT).unwrap();
 
@@ -882,7 +888,7 @@ pub(super) async fn make(app: &App, window: SceneNodePtr) {
 
     let node = node
         .setup(|me| {
-            EditBox::new(
+            ChatEdit::new(
                 me,
                 window_scale.clone(),
                 app.render_api.clone(),

+ 27 - 4
bin/darkwallet/src/prop/mod.rs

@@ -20,6 +20,7 @@ use crate::error::{Error, Result};
 use darkfi_serial::{async_trait, Encodable, FutAsyncWriteExt, SerialDecodable, SerialEncodable};
 use std::{
     io::Write,
+    ops::Range,
     sync::{Arc, Mutex as SyncMutex, Weak},
 };
 
@@ -195,7 +196,7 @@ impl Encodable for PropertyValue {
 pub enum ModifyAction {
     Clear,
     Set(usize),
-    SetCache(usize),
+    SetCache(Vec<usize>),
     Push(usize),
 }
 
@@ -466,14 +467,36 @@ impl Property {
             return Err(Error::PropertyWrongIndex)
         }
         cache[i] = val;
-        self.on_modify.notify((role, ModifyAction::SetCache(i)));
         Ok(())
     }
     pub fn set_cache_f32(&self, role: Role, i: usize, val: f32) -> Result<()> {
-        self.set_cache(role, i, PropertyValue::Float32(val))
+        self.set_cache(role, i, PropertyValue::Float32(val))?;
+        self.on_modify.notify((role, ModifyAction::SetCache(vec![i])));
+        Ok(())
     }
     pub fn set_cache_u32(&self, role: Role, i: usize, val: u32) -> Result<()> {
-        self.set_cache(role, i, PropertyValue::Uint32(val))
+        self.set_cache(role, i, PropertyValue::Uint32(val))?;
+        self.on_modify.notify((role, ModifyAction::SetCache(vec![i])));
+        Ok(())
+    }
+
+    pub fn set_cache_f32_multi(&self, role: Role, changes: Vec<(usize, f32)>) -> Result<()> {
+        let mut idxs = vec![];
+        for (idx, val) in changes {
+            self.set_cache(role, idx, PropertyValue::Float32(val))?;
+            idxs.push(idx);
+        }
+        self.on_modify.notify((role, ModifyAction::SetCache(idxs)));
+        Ok(())
+    }
+    pub fn set_cache_u32_range(&self, role: Role, changes: Vec<(usize, u32)>) -> Result<()> {
+        let mut idxs = vec![];
+        for (idx, val) in changes {
+            self.set_cache(role, idx, PropertyValue::Uint32(val))?;
+            idxs.push(idx);
+        }
+        self.on_modify.notify((role, ModifyAction::SetCache(idxs)));
+        Ok(())
     }
 
     // Push

+ 19 - 6
bin/darkwallet/src/prop/wrap.rs

@@ -16,6 +16,8 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
+use std::ops::Range;
+
 use crate::{
     error::{Error, Result},
     expr::{SExprMachine, SExprVal},
@@ -282,10 +284,14 @@ impl PropertyRect {
     }
 
     pub fn eval(&self, parent_rect: &Rectangle) -> Result<()> {
-        let mut globals = vec![
-            ("w".to_string(), SExprVal::Float32(parent_rect.w)),
-            ("h".to_string(), SExprVal::Float32(parent_rect.h)),
-        ];
+        self.eval_with(
+            0..4,
+            vec![("w".to_string(), parent_rect.w), ("h".to_string(), parent_rect.h)],
+        )
+    }
+
+    pub fn eval_with(&self, range: Range<usize>, extras: Vec<(String, f32)>) -> Result<()> {
+        let mut globals = vec![];
 
         for dep in self.prop.get_depends() {
             let Some(prop) = dep.prop.upgrade() else { return Err(Error::PropertyNotFound) };
@@ -294,9 +300,15 @@ impl PropertyRect {
 
             globals.push((dep.local_name, SExprVal::Float32(value)));
         }
+
+        for (name, val) in extras {
+            globals.push((name, SExprVal::Float32(val)));
+        }
+
         debug!(target: "prop::wrap", "PropertyRect::eval() [globals = {globals:?}]");
 
-        for i in 0..4 {
+        let mut changes = vec![];
+        for i in range.clone() {
             if !self.prop.is_expr(i)? {
                 continue
             }
@@ -306,8 +318,9 @@ impl PropertyRect {
             let mut machine = SExprMachine { globals: globals.clone(), stmts: &expr };
 
             let v = machine.call()?.as_f32()?;
-            self.prop.set_cache_f32(self.role, i, v).unwrap();
+            changes.push((i, v));
         }
+        self.prop.set_cache_f32_multi(self.role, changes).unwrap();
         Ok(())
     }
 

+ 3 - 2
bin/darkwallet/src/scene.rs

@@ -110,8 +110,9 @@ pub enum SceneNodeType {
     Plugin = 15,
     ChatView = 16,
     EditBox = 17,
-    Image = 18,
-    Button = 19,
+    ChatEdit = 18,
+    Image = 19,
+    Button = 20,
 }
 
 pub struct SceneNode {

+ 73 - 50
bin/darkwallet/src/ui/chatedit.rs

@@ -144,6 +144,7 @@ pub struct ChatEdit {
 
     is_active: PropertyBool,
     is_focused: PropertyBool,
+    max_height: PropertyFloat32,
     rect: PropertyRect,
     baseline: PropertyFloat32,
     scroll: PropertyFloat32,
@@ -197,6 +198,7 @@ impl ChatEdit {
         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();
+        let max_height = PropertyFloat32::wrap(node_ref, Role::Internal, "max_height", 0).unwrap();
         let rect = PropertyRect::wrap(node_ref, Role::Internal, "rect").unwrap();
         let baseline = PropertyFloat32::wrap(node_ref, Role::Internal, "baseline", 0).unwrap();
         let scroll = PropertyFloat32::wrap(node_ref, Role::Internal, "scroll", 0).unwrap();
@@ -247,6 +249,7 @@ impl ChatEdit {
 
             is_active,
             is_focused,
+            max_height,
             rect,
             baseline: baseline.clone(),
             scroll: scroll.clone(),
@@ -299,10 +302,7 @@ impl ChatEdit {
     }
 
     /// Called whenever the text or any text property changes.
-    fn regen_text_mesh(&self, mut clip: Rectangle) -> GfxDrawMesh {
-        clip.x = 0.;
-        clip.y = 0.;
-
+    fn regen_text_mesh(&self) -> GfxDrawMesh {
         let is_focused = self.is_focused.get();
         let text = self.text.get();
         let font_size = self.font_size.get();
@@ -320,51 +320,78 @@ impl ChatEdit {
         let rendered = self.editable.lock().unwrap().render();
         let atlas = text::make_texture_atlas(&self.render_api, &rendered.glyphs);
 
+        let width = self.rect.prop().get_f32(2).unwrap();
+        let wrapped_glyphs = text::wrap(width, font_size, window_scale, &rendered.glyphs);
+        let wrapped_lines_len = std::cmp::max(wrapped_glyphs.len(), 1);
+
+        let total_height = wrapped_lines_len as f32 * baseline;
+        //let height = std::cmp::min(total_height, self.max_height.get());
+        let height = total_height;
+        debug!("height = {height}");
+
+        self.rect.prop().set_f32(Role::Internal, 3, height);
+
+        // Eval the rect
+        let parent_rect = self.parent_rect.lock().unwrap().clone().unwrap();
+        self.rect.eval_with(
+            0..3,
+            vec![
+                ("parent_w".to_string(), parent_rect.w),
+                ("parent_h".to_string(), parent_rect.h),
+                ("rect_h".to_string(), height),
+            ],
+        );
+        debug!("rect = {:?}", self.rect.get());
+
+        let mut clip = self.rect.get();
+        clip.x = 0.;
+        clip.y = 0.;
+
         let mut mesh = MeshBuilder::with_clip(clip.clone());
 
-        let selections = self.select.lock().unwrap().clone();
-        // Just an assert
-        if self.is_phone_select.load(Ordering::Relaxed) {
-            assert!(selections.len() <= 1);
-        }
-        for select in &selections {
-            self.draw_selected(&mut mesh, select, &rendered.glyphs, clip.h);
-        }
-        let select_marks = self.mark_selected_glyphs(&rendered.glyphs, selections);
-
-        if rendered.has_underline() {
-            self.draw_underline(
-                &mut mesh,
-                &rendered.glyphs,
-                clip.h,
-                rendered.under_start,
-                rendered.under_end,
-            );
-        }
+        for (line_idx, glyphs) in wrapped_glyphs.iter().enumerate() {
+            let selections = self.select.lock().unwrap().clone();
+            // Just an assert
+            if self.is_phone_select.load(Ordering::Relaxed) {
+                assert!(selections.len() <= 1);
+            }
+            for select in &selections {
+                //self.draw_selected(&mut mesh, select, &rendered.glyphs, clip.h);
+            }
+            let select_marks = self.mark_selected_glyphs(&glyphs, selections);
+
+            /*
+            if rendered.has_underline() {
+                self.draw_underline(
+                    &mut mesh,
+                    &rendered.glyphs,
+                    clip.h,
+                    rendered.under_start,
+                    rendered.under_end,
+                );
+            }
+            */
 
-        let glyph_pos_iter =
-            GlyphPositionIter::new(font_size, window_scale, &rendered.glyphs, baseline);
+            let glyph_pos_iter = GlyphPositionIter::new(font_size, window_scale, &glyphs, baseline);
 
-        for (glyph_idx, mut glyph_rect, glyph, is_selected) in
-            zip3(glyph_pos_iter, rendered.glyphs.iter(), select_marks.iter())
-        {
-            let uv_rect = atlas.fetch_uv(glyph.glyph_id).expect("missing glyph UV rect");
+            for (glyph_idx, mut glyph_rect, glyph, is_selected) in
+                zip3(glyph_pos_iter, glyphs.iter(), select_marks.iter())
+            {
+                let uv_rect = atlas.fetch_uv(glyph.glyph_id).expect("missing glyph UV rect");
 
-            glyph_rect.x -= scroll;
+                glyph_rect.x -= scroll;
+                glyph_rect.y += line_idx as f32 * baseline;
 
-            //mesh.draw_outline(&glyph_rect, COLOR_BLUE, 2.);
-            let mut color = text_color.clone();
-            if *is_selected {
-                color = text_hi_color.clone();
-            }
-            if glyph.sprite.has_color {
-                color = COLOR_WHITE;
+                //mesh.draw_outline(&glyph_rect, COLOR_BLUE, 2.);
+                let mut color = text_color.clone();
+                if *is_selected {
+                    color = text_hi_color.clone();
+                }
+                if glyph.sprite.has_color {
+                    color = COLOR_WHITE;
+                }
+                mesh.draw_box(&glyph_rect, color, uv_rect);
             }
-            mesh.draw_box(&glyph_rect, color, uv_rect);
-        }
-
-        if debug {
-            mesh.draw_outline(&clip, COLOR_BLUE, 1.);
         }
 
         mesh.alloc(&self.render_api).draw_with_texture(atlas.texture)
@@ -1151,9 +1178,6 @@ impl ChatEdit {
     async fn redraw(&self) {
         debug!(target: "ui::chatedit", "redraw()");
 
-        let parent_rect = self.parent_rect.lock().unwrap().unwrap().clone();
-        self.rect.eval(&parent_rect).expect("unable to eval rect");
-
         let Some(draw_update) = self.make_draw_calls() else {
             error!(target: "ui::chatedit", "Text failed to draw");
             return;
@@ -1206,11 +1230,11 @@ impl ChatEdit {
     }
 
     fn make_draw_calls(&self) -> Option<DrawUpdate> {
-        let rect = self.rect.get();
-
-        let text_mesh = self.regen_text_mesh(rect.clone());
+        let text_mesh = self.regen_text_mesh();
         let cursor_instrs = self.get_cursor_instrs();
 
+        let rect = self.rect.get();
+
         Some(DrawUpdate {
             key: self.text_dc_key,
             draw_calls: vec![
@@ -1320,9 +1344,8 @@ impl UIObject for ChatEdit {
     async fn draw(&self, parent_rect: Rectangle) -> Option<DrawUpdate> {
         debug!(target: "ui::chatedit", "EditBox::draw()");
         *self.parent_rect.lock().unwrap() = Some(parent_rect);
-        self.rect.eval(&parent_rect).ok()?;
 
-        self.clamp_scroll();
+        //self.clamp_scroll();
         self.make_draw_calls()
     }
 

+ 9 - 4
bin/darkwallet/src/ui/mod.rs

@@ -132,16 +132,21 @@ impl<T: Send + Sync + 'static> OnModify<T> {
         let task = self.ex.spawn(async move {
             loop {
                 let mut poll_queues = FuturesUnordered::new();
-                for on_modify_sub in &on_modify_subs {
-                    poll_queues.push(on_modify_sub.receive());
+                for (i, on_modify_sub) in on_modify_subs.iter().enumerate() {
+                    let recv = on_modify_sub.receive();
+                    poll_queues.push(async move {
+                        let (role, _action) = recv.await.ok()?;
+                        Some((i, role))
+                    });
                 }
 
-                let Some(Ok((role, _))) = poll_queues.next().await else {
+                let Some(Some((idx, role))) = poll_queues.next().await else {
                     error!(target: "app", "Property '{}':{}/'{}' on_modify pipe is broken", node_name, node_id, prop_name);
                     return
                 };
 
-                if role == Role::Internal {
+                // Skip internal messages from ourselves
+                if idx == 0 && role == Role::Internal {
                     continue
                 }