From 37848fc178b2703e621f30798d70cd21205a4a0f Mon Sep 17 00:00:00 2001 From: Samuel Guerra Date: Fri, 27 May 2022 00:41:32 -0300 Subject: [PATCH] Implemented widget access from UiNode and UiNodeList. --- TODO/_current.md | 3 +- tests/focus.rs | 86 ++++---- zero-ui-core/src/ui_list.rs | 130 ++++++++++-- zero-ui-core/src/ui_list/chain.rs | 262 ++++++++++++++++++++++--- zero-ui-core/src/ui_list/sorted_vec.rs | 101 ++++++++-- zero-ui-core/src/ui_list/tuples.rs | 202 +++++++++++++++++-- zero-ui-core/src/ui_list/vec.rs | 172 +++++++++++++++- zero-ui-core/src/ui_list/z_sorted.rs | 110 +++++++++-- zero-ui-core/src/ui_node.rs | 256 ++++++++++++++++++++++++ zero-ui-core/src/widget_base.rs | 59 ++++-- zero-ui-core/src/widget_info.rs | 42 +++- zero-ui/src/widgets/layouts/stacks.rs | 8 +- 12 files changed, 1262 insertions(+), 169 deletions(-) diff --git a/TODO/_current.md b/TODO/_current.md index be3acc2d7..d584c2f99 100644 --- a/TODO/_current.md +++ b/TODO/_current.md @@ -4,4 +4,5 @@ * Review layout double-pass of stacks. * Fix text final size, either clip or return accurate size. -* Get Widget from UiNode (at least to avoid having to do custom reference frames in fill_node). \ No newline at end of file + +* Test scroll to end when the height changes by scrolling. \ No newline at end of file diff --git a/tests/focus.rs b/tests/focus.rs index 63663aa5c..130882cd3 100644 --- a/tests/focus.rs +++ b/tests/focus.rs @@ -13,7 +13,7 @@ pub fn first_and_last_window_events() { let root_id = WidgetId::new_unique(); let stack_id = WidgetId::new_unique(); - let button_0_id = buttons.widget_id(0); + let button_0_id = buttons.item_id(0); let mut app = TestApp::new_w(window! { content = v_stack!(id = stack_id; items = buttons); @@ -87,7 +87,7 @@ pub fn window_tab_cycle_index_auto() { button! { content = text("Button 2"); tab_index = tab_ids[2] }, ]; // we collect the widget_id values in the TAB navigation order. - let mut ids: Vec<_> = (0..3).map(|i| (buttons.widget_id(i), tab_ids[i])).collect(); + let mut ids: Vec<_> = (0..3).map(|i| (buttons.item_id(i), tab_ids[i])).collect(); ids.sort_by_key(|(_, ti)| *ti); let ids: Vec<_> = ids.into_iter().map(|(id, _)| id).collect(); @@ -132,7 +132,7 @@ pub fn window_tab_cycle_and_alt_scope() { button! { content = text("Button 0"); tab_index = tab_ids[0] }, button! { content = text("Button 1"); tab_index = tab_ids[1] }, ]; - let mut ids: Vec<_> = (0..2).map(|i| (buttons.widget_id(i), tab_ids[i])).collect(); + let mut ids: Vec<_> = (0..2).map(|i| (buttons.item_id(i), tab_ids[i])).collect(); ids.sort_by_key(|(_, ti)| *ti); let ids: Vec<_> = ids.into_iter().map(|(id, _)| id).collect(); @@ -141,7 +141,7 @@ pub fn window_tab_cycle_and_alt_scope() { button! { content = text("Alt 1"); tab_index = tab_ids[3] }, button! { content = text("Alt 2"); tab_index = tab_ids[4] }, ]; - let mut alt_ids: Vec<_> = (0..3).map(|i| (alt_buttons.widget_id(i), tab_ids[i + 2])).collect(); + let mut alt_ids: Vec<_> = (0..3).map(|i| (alt_buttons.item_id(i), tab_ids[i + 2])).collect(); alt_ids.sort_by_key(|(_, ti)| *ti); let alt_ids: Vec<_> = alt_ids.into_iter().map(|(id, _)| id).collect(); @@ -232,7 +232,7 @@ fn window_tab_contained_and_continue(tab_nav: TabNav) { button! { content = text("Button 2"); tab_index = tab_ids[2] }, ]; // we collect the widget_id values in the TAB navigation order. - let mut ids: Vec<_> = (0..3).map(|i| (buttons.widget_id(i), tab_ids[i])).collect(); + let mut ids: Vec<_> = (0..3).map(|i| (buttons.item_id(i), tab_ids[i])).collect(); ids.sort_by_key(|(_, ti)| *ti); let ids: Vec<_> = ids.into_iter().map(|(id, _)| id).collect(); @@ -285,7 +285,7 @@ fn window_tab_once_and_none(tab_nav: TabNav) { button! { content = text("Button 2"); tab_index = tab_ids[2] }, ]; // we collect the widget_id values in the TAB navigation order. - let mut ids: Vec<_> = (0..3).map(|i| (buttons.widget_id(i), tab_ids[i])).collect(); + let mut ids: Vec<_> = (0..3).map(|i| (buttons.item_id(i), tab_ids[i])).collect(); ids.sort_by_key(|(_, ti)| *ti); let ids: Vec<_> = ids.into_iter().map(|(id, _)| id).collect(); @@ -328,14 +328,14 @@ fn two_continue_scopes_or_containers_in_tab_cycle_window(focus_scope: bool) { button! { content = text("Button 1") }, button! { content = text("Button 2") }, ]; - let ids_a: Vec<_> = (0..3).map(|i| buttons_a.widget_id(i)).collect(); + let ids_a: Vec<_> = (0..3).map(|i| buttons_a.item_id(i)).collect(); let buttons_b = widgets![ button! { content = text("Button 0") }, button! { content = text("Button 1") }, button! { content = text("Button 2") }, ]; - let ids_b: Vec<_> = (0..3).map(|i| buttons_b.widget_id(i)).collect(); + let ids_b: Vec<_> = (0..3).map(|i| buttons_b.item_id(i)).collect(); let a = v_stack! { items = buttons_a; @@ -409,14 +409,14 @@ pub fn two_continue_scopes_with_mixed_indexes() { button! { content = text("Button 2"); tab_index = 5; }, button! { content = text("Button 1"); tab_index = 3; }, ]; - let ids_a: Vec<_> = (0..3).map(|i| buttons_a.widget_id(i)).collect(); + let ids_a: Vec<_> = (0..3).map(|i| buttons_a.item_id(i)).collect(); let buttons_b = widgets![ button! { content = text("Button 3"); tab_index = 2; }, button! { content = text("Button 4"); tab_index = 4; }, button! { content = text("Button 5"); tab_index = 6; }, ]; - let ids_b: Vec<_> = (0..3).map(|i| buttons_b.widget_id(i)).collect(); + let ids_b: Vec<_> = (0..3).map(|i| buttons_b.item_id(i)).collect(); let a = v_stack! { items = buttons_a; @@ -481,14 +481,14 @@ pub fn two_containers_with_mixed_indexes() { button! { content = text("Button 2"); tab_index = 5; }, button! { content = text("Button 1"); tab_index = 3; }, ]; - let ids_a: Vec<_> = (0..3).map(|i| buttons_a.widget_id(i)).collect(); + let ids_a: Vec<_> = (0..3).map(|i| buttons_a.item_id(i)).collect(); let buttons_b = widgets![ button! { content = text("Button 3"); tab_index = 2; }, button! { content = text("Button 4"); tab_index = 4; }, button! { content = text("Button 5"); tab_index = 6; }, ]; - let ids_b: Vec<_> = (0..3).map(|i| buttons_b.widget_id(i)).collect(); + let ids_b: Vec<_> = (0..3).map(|i| buttons_b.item_id(i)).collect(); let a = v_stack(buttons_a); let b = v_stack(buttons_b); @@ -539,7 +539,7 @@ pub fn tab_index_skip() { button! { content = text("Button 1"); tab_index = TabIndex::SKIP; }, button! { content = text("Button 2") }, ]; - let ids: Vec<_> = (0..3).map(|i| buttons.widget_id(i)).collect(); + let ids: Vec<_> = (0..3).map(|i| buttons.item_id(i)).collect(); let mut app = TestApp::new(v_stack(buttons)); @@ -562,13 +562,13 @@ pub fn tab_inner_container() { // sanity check for `tab_skip_inner_container`. let inner_buttons = widgets![button! { content = text("Button 1") }, button! { content = text("Button 2") },]; - let inner_ids: Vec<_> = (0..2).map(|i| inner_buttons.widget_id(i)).collect(); + let inner_ids: Vec<_> = (0..2).map(|i| inner_buttons.item_id(i)).collect(); let items = widgets![ button! { content = text("Button 0") }, v_stack(inner_buttons), button! { content = text("Button 3") }, ]; - let item_ids: Vec<_> = (0..3).map(|i| items.widget_id(i)).collect(); + let item_ids: Vec<_> = (0..3).map(|i| items.item_id(i)).collect(); let mut app = TestApp::new(v_stack(items)); @@ -595,7 +595,7 @@ pub fn tab_skip_inner_container() { // directly. let inner_buttons = widgets![button! { content = text("Button 1") }, button! { content = text("Button 2") },]; - let inner_ids: Vec<_> = (0..2).map(|i| inner_buttons.widget_id(i)).collect(); + let inner_ids: Vec<_> = (0..2).map(|i| inner_buttons.item_id(i)).collect(); let items = widgets![ button! { content = text("Button 0") }, v_stack! { @@ -604,7 +604,7 @@ pub fn tab_skip_inner_container() { }, button! { content = text("Button 3") }, ]; - let item_ids: Vec<_> = (0..3).map(|i| items.widget_id(i)).collect(); + let item_ids: Vec<_> = (0..3).map(|i| items.item_id(i)).collect(); let mut app = TestApp::new(v_stack(items)); @@ -643,7 +643,7 @@ pub fn tab_inner_scope_continue() { // sanity check for `tab_skip_inner_scope_continue`. let inner_buttons = widgets![button! { content = text("Button 1") }, button! { content = text("Button 2") },]; - let inner_ids: Vec<_> = (0..2).map(|i| inner_buttons.widget_id(i)).collect(); + let inner_ids: Vec<_> = (0..2).map(|i| inner_buttons.item_id(i)).collect(); let items = widgets![ button! { content = text("Button 0") }, v_stack! { @@ -653,7 +653,7 @@ pub fn tab_inner_scope_continue() { }, button! { content = text("Button 3") }, ]; - let item_ids: Vec<_> = (0..3).map(|i| items.widget_id(i)).collect(); + let item_ids: Vec<_> = (0..3).map(|i| items.item_id(i)).collect(); let mut app = TestApp::new(v_stack(items)); @@ -680,7 +680,7 @@ pub fn tab_skip_inner_scope_continue() { // directly. let inner_buttons = widgets![button! { content = text("Button 1") }, button! { content = text("Button 2") },]; - let inner_ids: Vec<_> = (0..2).map(|i| inner_buttons.widget_id(i)).collect(); + let inner_ids: Vec<_> = (0..2).map(|i| inner_buttons.item_id(i)).collect(); let items = widgets![ button! { content = text("Button 0") }, v_stack! { @@ -691,7 +691,7 @@ pub fn tab_skip_inner_scope_continue() { }, button! { content = text("Button 3") }, ]; - let item_ids: Vec<_> = (0..3).map(|i| items.widget_id(i)).collect(); + let item_ids: Vec<_> = (0..3).map(|i| items.item_id(i)).collect(); let mut app = TestApp::new(v_stack(items)); @@ -730,7 +730,7 @@ pub fn tab_inner_scope_cycle() { // we expect tab navigation to enter the inner scope and get trapped in there. let inner_buttons = widgets![button! { content = text("Button 1") }, button! { content = text("Button 2") },]; - let inner_ids: Vec<_> = (0..2).map(|i| inner_buttons.widget_id(i)).collect(); + let inner_ids: Vec<_> = (0..2).map(|i| inner_buttons.item_id(i)).collect(); let items = widgets![ button! { content = text("Button 0") }, v_stack! { @@ -740,7 +740,7 @@ pub fn tab_inner_scope_cycle() { }, button! { content = text("Button 3") }, ]; - let item_ids: Vec<_> = (0..3).map(|i| items.widget_id(i)).collect(); + let item_ids: Vec<_> = (0..3).map(|i| items.item_id(i)).collect(); let mut app = TestApp::new(v_stack(items)); @@ -772,7 +772,7 @@ pub fn tab_inner_scope_contained() { // we expect tab navigation to enter the inner scope and get trapped in there. let inner_buttons = widgets![button! { content = text("Button 1") }, button! { content = text("Button 2") },]; - let inner_ids: Vec<_> = (0..2).map(|i| inner_buttons.widget_id(i)).collect(); + let inner_ids: Vec<_> = (0..2).map(|i| inner_buttons.item_id(i)).collect(); let items = widgets![ button! { content = text("Button 0") }, v_stack! { @@ -782,7 +782,7 @@ pub fn tab_inner_scope_contained() { }, button! { content = text("Button 3") }, ]; - let item_ids: Vec<_> = (0..3).map(|i| items.widget_id(i)).collect(); + let item_ids: Vec<_> = (0..3).map(|i| items.item_id(i)).collect(); let mut app = TestApp::new(v_stack(items)); @@ -814,7 +814,7 @@ pub fn tab_inner_scope_once() { // we expect tab navigation to enter the inner scope but then leave it. let inner_buttons = widgets![button! { content = text("Button 1") }, button! { content = text("Button 2") },]; - let inner_ids: Vec<_> = (0..2).map(|i| inner_buttons.widget_id(i)).collect(); + let inner_ids: Vec<_> = (0..2).map(|i| inner_buttons.item_id(i)).collect(); let items = widgets![ button! { content = text("Button 0") }, v_stack! { @@ -824,7 +824,7 @@ pub fn tab_inner_scope_once() { }, button! { content = text("Button 3") }, ]; - let item_ids: Vec<_> = (0..3).map(|i| items.widget_id(i)).collect(); + let item_ids: Vec<_> = (0..3).map(|i| items.item_id(i)).collect(); let mut app = TestApp::new(v_stack(items)); @@ -850,7 +850,7 @@ pub fn tab_inner_scope_none() { // we expect tab navigation to enter the inner scope and then not move. let inner_buttons = widgets![button! { content = text("Button 1") }, button! { content = text("Button 2") },]; - let inner_ids: Vec<_> = (0..2).map(|i| inner_buttons.widget_id(i)).collect(); + let inner_ids: Vec<_> = (0..2).map(|i| inner_buttons.item_id(i)).collect(); let items = widgets![ button! { content = text("Button 0") }, v_stack! { @@ -860,7 +860,7 @@ pub fn tab_inner_scope_none() { }, button! { content = text("Button 3") }, ]; - let item_ids: Vec<_> = (0..3).map(|i| items.widget_id(i)).collect(); + let item_ids: Vec<_> = (0..3).map(|i| items.item_id(i)).collect(); let mut app = TestApp::new(v_stack(items)); @@ -996,7 +996,7 @@ fn focused_removed_test(button1: impl Widget, set_var: impl FnOnce(&Vars)) { button1, button! { content = text("Button 2") }, ]; - let ids: Vec<_> = (0..3).map(|i| buttons.widget_id(i)).collect(); + let ids: Vec<_> = (0..3).map(|i| buttons.item_id(i)).collect(); let mut app = TestApp::new(v_stack(buttons)); @@ -1212,7 +1212,7 @@ pub fn directional_focus_up() { button! { content = text("Button 1") }, button! { content = text("Button 2") }, ]; - let ids: Vec<_> = (0..3).map(|i| buttons.widget_id(i)).collect(); + let ids: Vec<_> = (0..3).map(|i| buttons.item_id(i)).collect(); let mut app = TestApp::new(v_stack(buttons)); @@ -1233,7 +1233,7 @@ pub fn directional_focus_down() { button! { content = text("Button 1") }, button! { content = text("Button 2") }, ]; - let ids: Vec<_> = (0..3).map(|i| buttons.widget_id(i)).collect(); + let ids: Vec<_> = (0..3).map(|i| buttons.item_id(i)).collect(); let mut app = TestApp::new(v_stack(buttons)); @@ -1253,7 +1253,7 @@ pub fn directional_focus_left() { button! { content = text("Button 1") }, button! { content = text("Button 2") }, ]; - let ids: Vec<_> = (0..3).map(|i| buttons.widget_id(i)).collect(); + let ids: Vec<_> = (0..3).map(|i| buttons.item_id(i)).collect(); let mut app = TestApp::new(h_stack(buttons)); @@ -1274,7 +1274,7 @@ pub fn directional_focus_right() { button! { content = text("Button 1") }, button! { content = text("Button 2") }, ]; - let ids: Vec<_> = (0..3).map(|i| buttons.widget_id(i)).collect(); + let ids: Vec<_> = (0..3).map(|i| buttons.item_id(i)).collect(); let mut app = TestApp::new(h_stack(buttons)); @@ -1294,7 +1294,7 @@ pub fn directional_cycle_vertical() { button! { content = text("Button 1") }, button! { content = text("Button 2") }, ]; - let ids: Vec<_> = (0..3).map(|i| buttons.widget_id(i)).collect(); + let ids: Vec<_> = (0..3).map(|i| buttons.item_id(i)).collect(); let mut app = TestApp::new_w(window! { directional_nav = DirectionalNav::Cycle; @@ -1316,7 +1316,7 @@ pub fn directional_cycle_horizontal() { button! { content = text("Button 1") }, button! { content = text("Button 2") }, ]; - let ids: Vec<_> = (0..3).map(|i| buttons.widget_id(i)).collect(); + let ids: Vec<_> = (0..3).map(|i| buttons.item_id(i)).collect(); let mut app = TestApp::new_w(window! { directional_nav = DirectionalNav::Cycle; @@ -1338,7 +1338,7 @@ pub fn directional_contained_vertical() { button! { content = text("Button 1") }, button! { content = text("Button 2") }, ]; - let ids: Vec<_> = (0..3).map(|i| buttons.widget_id(i)).collect(); + let ids: Vec<_> = (0..3).map(|i| buttons.item_id(i)).collect(); let mut app = TestApp::new_w(window! { directional_nav = DirectionalNav::Contained; @@ -1360,7 +1360,7 @@ pub fn directional_contained_horizontal() { button! { content = text("Button 1") }, button! { content = text("Button 2") }, ]; - let ids: Vec<_> = (0..3).map(|i| buttons.widget_id(i)).collect(); + let ids: Vec<_> = (0..3).map(|i| buttons.item_id(i)).collect(); let mut app = TestApp::new_w(window! { directional_nav = DirectionalNav::Contained; @@ -1383,7 +1383,7 @@ pub fn directional_none() { button! { content = text("Button 1") }, button! { content = text("Button 2") }, ]; - let ids: Vec<_> = (0..3).map(|i| buttons.widget_id(i)).collect(); + let ids: Vec<_> = (0..3).map(|i| buttons.item_id(i)).collect(); let mut app = TestApp::new_w(window! { directional_nav = DirectionalNav::None; @@ -1417,7 +1417,7 @@ pub fn directional_continue_up() { }, button! { content = text("Button 2") }, ]; - let ids: Vec<_> = (0..3).map(|i| buttons.widget_id(i)).collect(); + let ids: Vec<_> = (0..3).map(|i| buttons.item_id(i)).collect(); let mut app = TestApp::new(v_stack(buttons)); @@ -1442,7 +1442,7 @@ pub fn directional_continue_down() { }, button! { content = text("Button 2") }, ]; - let ids: Vec<_> = (0..3).map(|i| buttons.widget_id(i)).collect(); + let ids: Vec<_> = (0..3).map(|i| buttons.item_id(i)).collect(); let mut app = TestApp::new(v_stack(buttons)); @@ -1467,7 +1467,7 @@ pub fn directional_continue_left() { }, button! { content = text("Button 2") }, ]; - let ids: Vec<_> = (0..3).map(|i| buttons.widget_id(i)).collect(); + let ids: Vec<_> = (0..3).map(|i| buttons.item_id(i)).collect(); let mut app = TestApp::new(h_stack(buttons)); @@ -1492,7 +1492,7 @@ pub fn directional_continue_right() { }, button! { content = text("Button 2") }, ]; - let ids: Vec<_> = (0..3).map(|i| buttons.widget_id(i)).collect(); + let ids: Vec<_> = (0..3).map(|i| buttons.item_id(i)).collect(); let mut app = TestApp::new(h_stack(buttons)); diff --git a/zero-ui-core/src/ui_list.rs b/zero-ui-core/src/ui_list.rs index 9eaa19f6a..ffb734f58 100644 --- a/zero-ui-core/src/ui_list.rs +++ b/zero-ui-core/src/ui_list.rs @@ -118,6 +118,54 @@ pub trait UiNodeList: 'static { /// Calls [`UiNode::render_update`] in only the `index` node or widget. fn item_render_update(&self, index: usize, ctx: &mut RenderContext, update: &mut FrameUpdate); + + /// Gets the id of the widget at the `index` if the node is a full widget. + /// + /// The index is zero-based. + fn try_item_id(&self, index: usize) -> Option; + + /// Reference the state of the widget at the `index`. + fn try_item_state(&self, index: usize) -> Option<&StateMap>; + + /// Exclusive reference the state of the widget at the `index`. + fn try_item_state_mut(&mut self, index: usize) -> Option<&mut StateMap>; + + /// Gets the bounds layout info of the node at the `index` if it is a full widget. + /// + /// See [`Widget::bounds_info`] for more details. + fn try_item_bounds_info(&self, index: usize) -> Option<&WidgetBoundsInfo>; + + /// Gets the border and corners info from the node at the `index` if it is a full widget. + /// + /// See [`Widget::border_info`] for more details. + fn try_item_border_info(&self, index: usize) -> Option<&WidgetBorderInfo>; + + /// Gets the render info from the node at the `index` if it is a full widget. + /// + /// See [`Widget::render_info`] for more details. + fn try_item_render_info(&self, index: usize) -> Option<&WidgetRenderInfo>; + + /// Calls [`UiNode::render`] in all nodes allowed by a `filter`, skips rendering the rest. + fn render_node_filtered(&self, filter: F, ctx: &mut RenderContext, frame: &mut FrameBuilder) + where + F: FnMut(UiNodeFilterArgs) -> bool; + + /// Calls [`WidgetLayout::try_with_outer`] in only the `index` node. The `transform` closure only runs if the node + /// is a full widget. + fn try_item_outer(&mut self, index: usize, wl: &mut WidgetLayout, keep_previous: bool, transform: F) -> Option + where + F: FnOnce(&mut WidgetLayoutTranslation, PosLayoutArgs) -> R; + + /// Calls [`WidgetLayout::try_with_outer`] in all nodes on the list. The `transform` closures only runs for the nodes + /// that are full widgets. + fn try_outer_all(&mut self, wl: &mut WidgetLayout, keep_previous: bool, transform: F) + where + F: FnMut(&mut WidgetLayoutTranslation, PosLayoutArgs); + + /// Count nodes that pass the `filter`. + fn count_nodes(&self, filter: F) -> usize + where + F: FnMut(UiNodeFilterArgs) -> bool; } /// Arguments for the closure in [`UiNodeList::layout_all`] that runs before each child is layout. @@ -127,7 +175,7 @@ pub struct PreLayoutArgs<'a> { /// Mutable reference to the widget state. /// - /// Is `None` in lists that only implement [`UiNodeList`]. + /// Can be `None` in [`UiNodeList`] for nodes that are not full widgets. pub state: Option<&'a mut StateMap>, /// Constrains overwrite just for this child. @@ -151,10 +199,10 @@ pub struct PosLayoutArgs<'a> { /// Mutable reference to the widget state. /// - /// Is `None` in lists that only implement [`UiNodeList`]. + /// Can be `None` in [`UiNodeList`] for nodes that are not full widgets. pub state: Option<&'a mut StateMap>, - /// The widget outer size. + /// The updated size. pub size: PxSize, } impl<'a> PosLayoutArgs<'a> { @@ -196,18 +244,20 @@ fn default_ui_node_list_layout_all( D: FnMut(&mut LayoutContext, &mut WidgetLayout, PosLayoutArgs), { let (size, _) = wl.with_child(ctx, |ctx, wl| { - let mut args = PreLayoutArgs::new(index, None); + let mut args = PreLayoutArgs::new(index, node.try_state_mut()); pre_layout(ctx, wl, &mut args); ctx.with_constrains(|c| args.constrains.take().unwrap_or(c), |ctx| node.layout(ctx, wl)) }); pos_layout(ctx, wl, PosLayoutArgs::new(index, None, size)); } -/// All [`Widget`] accessible *info*. +/// All [`Widget`] accessible info. pub struct WidgetFilterArgs<'a> { /// The widget index in the list. pub index: usize, + /// The [`Widget::id`]. + pub id: WidgetId, /// The [`Widget::bounds_info`]. pub bounds_info: &'a WidgetBoundsInfo, /// The [`Widget::border_info`]. @@ -222,10 +272,11 @@ impl<'a> WidgetFilterArgs<'a> { pub fn get(list: &'a impl WidgetList, index: usize) -> Self { WidgetFilterArgs { index, - bounds_info: list.widget_bounds_info(index), - border_info: list.widget_border_info(index), - render_info: list.widget_render_info(index), - state: list.widget_state(index), + id: list.item_id(index), + bounds_info: list.item_bounds_info(index), + border_info: list.item_border_info(index), + render_info: list.item_render_info(index), + state: list.item_state(index), } } @@ -233,6 +284,7 @@ impl<'a> WidgetFilterArgs<'a> { pub fn new(index: usize, widget: &'a impl Widget) -> Self { WidgetFilterArgs { index, + id: widget.id(), bounds_info: widget.bounds_info(), border_info: widget.border_info(), render_info: widget.render_info(), @@ -241,11 +293,53 @@ impl<'a> WidgetFilterArgs<'a> { } } +/// All [`UiNode`] accessible widget info. +pub struct UiNodeFilterArgs<'a> { + /// The node index in the list. + pub index: usize, + + /// The [`UiNode::try_id`]. + pub id: Option, + /// The [`UiNode::try_bounds_info`]. + pub bounds_info: Option<&'a WidgetBoundsInfo>, + /// The [`UiNode::try_border_info`]. + pub border_info: Option<&'a WidgetBorderInfo>, + /// The [`UiNode::try_render_info`]. + pub render_info: Option<&'a WidgetRenderInfo>, + /// The [`UiNode::try_state`]. + pub state: Option<&'a StateMap>, +} +impl<'a> UiNodeFilterArgs<'a> { + /// Copy or borrow all info from a node list and index. + pub fn get(list: &'a impl UiNodeList, index: usize) -> Self { + UiNodeFilterArgs { + index, + id: list.try_item_id(index), + bounds_info: list.try_item_bounds_info(index), + border_info: list.try_item_border_info(index), + render_info: list.try_item_render_info(index), + state: list.try_item_state(index), + } + } + + /// Copy or borrow all info from a node reference. + pub fn new(index: usize, node: &'a impl UiNode) -> Self { + UiNodeFilterArgs { + index, + id: node.try_id(), + bounds_info: node.try_bounds_info(), + border_info: node.try_border_info(), + render_info: node.try_render_info(), + state: node.try_state(), + } + } +} + /// A generic view over a list of [`Widget`] UI nodes. /// /// Layout widgets should use this to abstract the children list type if possible. pub trait WidgetList: UiNodeList { - /// Count widgets that pass filter using the widget state. + /// Count widgets that pass the `filter`. fn count(&self, filter: F) -> usize where F: FnMut(WidgetFilterArgs) -> bool; @@ -265,28 +359,28 @@ pub trait WidgetList: UiNodeList { /// Gets the id of the widget at the `index`. /// /// The index is zero-based. - fn widget_id(&self, index: usize) -> WidgetId; + fn item_id(&self, index: usize) -> WidgetId; /// Reference the state of the widget at the `index`. - fn widget_state(&self, index: usize) -> &StateMap; + fn item_state(&self, index: usize) -> &StateMap; /// Exclusive reference the state of the widget at the `index`. - fn widget_state_mut(&mut self, index: usize) -> &mut StateMap; + fn item_state_mut(&mut self, index: usize) -> &mut StateMap; /// Gets the bounds layout info of the widget at the `index`. /// /// See [`Widget::bounds_info`] for more details. - fn widget_bounds_info(&self, index: usize) -> &WidgetBoundsInfo; + fn item_bounds_info(&self, index: usize) -> &WidgetBoundsInfo; /// Gets the border and corners info of the widget at the `index`. /// /// See [`Widget::border_info`] for more details. - fn widget_border_info(&self, index: usize) -> &WidgetBorderInfo; + fn item_border_info(&self, index: usize) -> &WidgetBorderInfo; /// Gets the render info the widget at the `index`. /// /// See [`Widget::render_info`] for more details. - fn widget_render_info(&self, index: usize) -> &WidgetRenderInfo; + fn item_render_info(&self, index: usize) -> &WidgetRenderInfo; /// Calls [`UiNode::render`] in all widgets allowed by a `filter`, skips rendering the rest. fn render_filtered(&self, filter: F, ctx: &mut RenderContext, frame: &mut FrameBuilder) @@ -294,9 +388,9 @@ pub trait WidgetList: UiNodeList { F: FnMut(WidgetFilterArgs) -> bool; /// Calls [`WidgetLayout::with_outer`] in only the `index` widget. - fn widget_outer(&mut self, index: usize, wl: &mut WidgetLayout, keep_previous: bool, transform: F) + fn item_outer(&mut self, index: usize, wl: &mut WidgetLayout, keep_previous: bool, transform: F) -> R where - F: FnOnce(&mut WidgetLayoutTranslation, PosLayoutArgs); + F: FnOnce(&mut WidgetLayoutTranslation, PosLayoutArgs) -> R; /// Calls [`WidgetLayout::with_outer`] in all widgets on the list. fn outer_all(&mut self, wl: &mut WidgetLayout, keep_previous: bool, transform: F) diff --git a/zero-ui-core/src/ui_list/chain.rs b/zero-ui-core/src/ui_list/chain.rs index a33a04a96..d1bb60e85 100644 --- a/zero-ui-core/src/ui_list/chain.rs +++ b/zero-ui-core/src/ui_list/chain.rs @@ -142,6 +142,115 @@ impl UiNodeList for WidgetListChain { self.1.item_render_update(index - a_len, ctx, update) } } + + fn try_item_id(&self, index: usize) -> Option { + let a_len = self.0.len(); + if index < a_len { + self.0.try_item_id(index) + } else { + self.1.try_item_id(index - a_len) + } + } + + fn try_item_state(&self, index: usize) -> Option<&StateMap> { + let a_len = self.0.len(); + if index < a_len { + self.0.try_item_state(index) + } else { + self.1.try_item_state(index - a_len) + } + } + + fn try_item_state_mut(&mut self, index: usize) -> Option<&mut StateMap> { + let a_len = self.0.len(); + if index < a_len { + self.0.try_item_state_mut(index) + } else { + self.1.try_item_state_mut(index - a_len) + } + } + + fn try_item_bounds_info(&self, index: usize) -> Option<&WidgetBoundsInfo> { + let a_len = self.0.len(); + if index < a_len { + self.0.try_item_bounds_info(index) + } else { + self.1.try_item_bounds_info(index - a_len) + } + } + + fn try_item_border_info(&self, index: usize) -> Option<&WidgetBorderInfo> { + let a_len = self.0.len(); + if index < a_len { + self.0.try_item_border_info(index) + } else { + self.1.try_item_border_info(index - a_len) + } + } + + fn try_item_render_info(&self, index: usize) -> Option<&WidgetRenderInfo> { + let a_len = self.0.len(); + if index < a_len { + self.0.try_item_render_info(index) + } else { + self.1.try_item_render_info(index - a_len) + } + } + + fn render_node_filtered(&self, mut filter: F, ctx: &mut RenderContext, frame: &mut FrameBuilder) + where + F: FnMut(super::UiNodeFilterArgs) -> bool, + { + self.0.render_node_filtered(&mut filter, ctx, frame); + let offset = self.0.len(); + self.1.render_node_filtered( + |mut a| { + a.index += offset; + filter(a) + }, + ctx, + frame, + ); + } + + fn try_item_outer(&mut self, index: usize, wl: &mut WidgetLayout, keep_previous: bool, transform: F) -> Option + where + F: FnOnce(&mut WidgetLayoutTranslation, PosLayoutArgs) -> R, + { + let a_len = self.0.len(); + if index < a_len { + self.0.try_item_outer(index, wl, keep_previous, transform) + } else { + self.1.try_item_outer(index - a_len, wl, keep_previous, transform) + } + } + + fn try_outer_all(&mut self, wl: &mut WidgetLayout, keep_previous: bool, mut transform: F) + where + F: FnMut(&mut WidgetLayoutTranslation, PosLayoutArgs), + { + self.0.try_outer_all(wl, keep_previous, &mut transform); + let offset = self.0.len(); + self.1.try_outer_all(wl, keep_previous, |wlt, mut args| { + args.index += offset; + transform(wlt, args); + }) + } + + fn count_nodes(&self, mut filter: F) -> usize + where + F: FnMut(super::UiNodeFilterArgs) -> bool, + { + let a_count = self.0.count_nodes(&mut filter); + + let offset = self.0.len(); + let b_count = self.1.count_nodes(|mut args| { + args.index += offset; + filter(args) + }); + + a_count + b_count + } } impl WidgetList for WidgetListChain { @@ -183,69 +292,69 @@ impl WidgetList for WidgetListChain { ); } - fn widget_id(&self, index: usize) -> WidgetId { + fn item_id(&self, index: usize) -> WidgetId { let a_len = self.0.len(); if index < a_len { - self.0.widget_id(index) + self.0.item_id(index) } else { - self.1.widget_id(index - a_len) + self.1.item_id(index - a_len) } } - fn widget_state(&self, index: usize) -> &StateMap { + fn item_state(&self, index: usize) -> &StateMap { let a_len = self.0.len(); if index < a_len { - self.0.widget_state(index) + self.0.item_state(index) } else { - self.1.widget_state(index - a_len) + self.1.item_state(index - a_len) } } - fn widget_state_mut(&mut self, index: usize) -> &mut StateMap { + fn item_state_mut(&mut self, index: usize) -> &mut StateMap { let a_len = self.0.len(); if index < a_len { - self.0.widget_state_mut(index) + self.0.item_state_mut(index) } else { - self.1.widget_state_mut(index - a_len) + self.1.item_state_mut(index - a_len) } } - fn widget_bounds_info(&self, index: usize) -> &WidgetBoundsInfo { + fn item_bounds_info(&self, index: usize) -> &WidgetBoundsInfo { let a_len = self.0.len(); if index < a_len { - self.0.widget_bounds_info(index) + self.0.item_bounds_info(index) } else { - self.1.widget_bounds_info(index - a_len) + self.1.item_bounds_info(index - a_len) } } - fn widget_border_info(&self, index: usize) -> &WidgetBorderInfo { + fn item_border_info(&self, index: usize) -> &WidgetBorderInfo { let a_len = self.0.len(); if index < a_len { - self.0.widget_border_info(index) + self.0.item_border_info(index) } else { - self.1.widget_border_info(index - a_len) + self.1.item_border_info(index - a_len) } } - fn widget_render_info(&self, index: usize) -> &WidgetRenderInfo { + fn item_render_info(&self, index: usize) -> &WidgetRenderInfo { let a_len = self.0.len(); if index < a_len { - self.0.widget_render_info(index) + self.0.item_render_info(index) } else { - self.1.widget_render_info(index - a_len) + self.1.item_render_info(index - a_len) } } - fn widget_outer(&mut self, index: usize, wl: &mut WidgetLayout, keep_previous: bool, transform: F) + fn item_outer(&mut self, index: usize, wl: &mut WidgetLayout, keep_previous: bool, transform: F) -> R where - F: FnOnce(&mut WidgetLayoutTranslation, PosLayoutArgs), + F: FnOnce(&mut WidgetLayoutTranslation, PosLayoutArgs) -> R, { let a_len = self.0.len(); if index < a_len { - self.0.widget_outer(index, wl, keep_previous, transform); + self.0.item_outer(index, wl, keep_previous, transform) } else { - self.1.widget_outer(index - a_len, wl, keep_previous, transform); + self.1.item_outer(index - a_len, wl, keep_previous, transform) } } @@ -391,4 +500,113 @@ impl UiNodeList for UiNodeListChain { self.1.item_render_update(index - a_len, ctx, update) } } + + fn try_item_id(&self, index: usize) -> Option { + let a_len = self.0.len(); + if index < a_len { + self.0.try_item_id(index) + } else { + self.1.try_item_id(index - a_len) + } + } + + fn try_item_state(&self, index: usize) -> Option<&StateMap> { + let a_len = self.0.len(); + if index < a_len { + self.0.try_item_state(index) + } else { + self.1.try_item_state(index - a_len) + } + } + + fn try_item_state_mut(&mut self, index: usize) -> Option<&mut StateMap> { + let a_len = self.0.len(); + if index < a_len { + self.0.try_item_state_mut(index) + } else { + self.1.try_item_state_mut(index - a_len) + } + } + + fn try_item_bounds_info(&self, index: usize) -> Option<&WidgetBoundsInfo> { + let a_len = self.0.len(); + if index < a_len { + self.0.try_item_bounds_info(index) + } else { + self.1.try_item_bounds_info(index - a_len) + } + } + + fn try_item_border_info(&self, index: usize) -> Option<&WidgetBorderInfo> { + let a_len = self.0.len(); + if index < a_len { + self.0.try_item_border_info(index) + } else { + self.1.try_item_border_info(index - a_len) + } + } + + fn try_item_render_info(&self, index: usize) -> Option<&WidgetRenderInfo> { + let a_len = self.0.len(); + if index < a_len { + self.0.try_item_render_info(index) + } else { + self.1.try_item_render_info(index - a_len) + } + } + + fn render_node_filtered(&self, mut filter: F, ctx: &mut RenderContext, frame: &mut FrameBuilder) + where + F: FnMut(super::UiNodeFilterArgs) -> bool, + { + self.0.render_node_filtered(&mut filter, ctx, frame); + let offset = self.0.len(); + self.1.render_node_filtered( + |mut a| { + a.index += offset; + filter(a) + }, + ctx, + frame, + ); + } + + fn try_item_outer(&mut self, index: usize, wl: &mut WidgetLayout, keep_previous: bool, transform: F) -> Option + where + F: FnOnce(&mut WidgetLayoutTranslation, PosLayoutArgs) -> R, + { + let a_len = self.0.len(); + if index < a_len { + self.0.try_item_outer(index, wl, keep_previous, transform) + } else { + self.1.try_item_outer(index - a_len, wl, keep_previous, transform) + } + } + + fn try_outer_all(&mut self, wl: &mut WidgetLayout, keep_previous: bool, mut transform: F) + where + F: FnMut(&mut WidgetLayoutTranslation, PosLayoutArgs), + { + self.0.try_outer_all(wl, keep_previous, &mut transform); + let offset = self.0.len(); + self.1.try_outer_all(wl, keep_previous, |wlt, mut args| { + args.index += offset; + transform(wlt, args); + }) + } + + fn count_nodes(&self, mut filter: F) -> usize + where + F: FnMut(super::UiNodeFilterArgs) -> bool, + { + let a_count = self.0.count_nodes(&mut filter); + + let offset = self.0.len(); + let b_count = self.1.count_nodes(|mut args| { + args.index += offset; + filter(args) + }); + + a_count + b_count + } } diff --git a/zero-ui-core/src/ui_list/sorted_vec.rs b/zero-ui-core/src/ui_list/sorted_vec.rs index a9f0cbfd7..fbc857745 100644 --- a/zero-ui-core/src/ui_list/sorted_vec.rs +++ b/zero-ui-core/src/ui_list/sorted_vec.rs @@ -13,6 +13,8 @@ use crate::{ BoxedWidget, UiNode, Widget, WidgetId, }; +use super::UiNodeFilterArgs; + /// A vector of boxed [`Widget`] items that remains sorted. /// /// This type is a [`WidgetList`] that can be modified during runtime, and automatically remains sorted @@ -372,6 +374,81 @@ impl UiNodeList for SortedWidgetVec { fn item_render_update(&self, index: usize, ctx: &mut RenderContext, update: &mut FrameUpdate) { self.vec[index].render_update(ctx, update); } + + fn try_item_id(&self, index: usize) -> Option { + self.vec[index].try_id() + } + + fn try_item_state(&self, index: usize) -> Option<&StateMap> { + self.vec[index].try_state() + } + + fn try_item_state_mut(&mut self, index: usize) -> Option<&mut StateMap> { + self.vec[index].try_state_mut() + } + + fn try_item_bounds_info(&self, index: usize) -> Option<&WidgetBoundsInfo> { + self.vec[index].try_bounds_info() + } + + fn try_item_border_info(&self, index: usize) -> Option<&WidgetBorderInfo> { + self.vec[index].try_border_info() + } + + fn try_item_render_info(&self, index: usize) -> Option<&WidgetRenderInfo> { + self.vec[index].try_render_info() + } + + fn render_node_filtered(&self, mut filter: F, ctx: &mut RenderContext, frame: &mut FrameBuilder) + where + F: FnMut(UiNodeFilterArgs) -> bool, + { + for (i, w) in self.iter().enumerate() { + if filter(UiNodeFilterArgs::new(i, w)) { + w.render(ctx, frame); + } + } + } + + fn try_item_outer(&mut self, index: usize, wl: &mut WidgetLayout, keep_previous: bool, transform: F) -> Option + where + F: FnOnce(&mut WidgetLayoutTranslation, PosLayoutArgs) -> R, + { + let w = &mut self.vec[index]; + if let Some(size) = w.try_bounds_info().map(|i| i.outer_size()) { + wl.try_with_outer(w, keep_previous, |wlt, w| { + transform(wlt, PosLayoutArgs::new(index, w.try_state_mut(), size)) + }) + } else { + None + } + } + + fn try_outer_all(&mut self, wl: &mut WidgetLayout, keep_previous: bool, mut transform: F) + where + F: FnMut(&mut WidgetLayoutTranslation, PosLayoutArgs), + { + for (i, w) in self.vec.iter_mut().enumerate() { + if let Some(size) = w.try_bounds_info().map(|i| i.outer_size()) { + wl.try_with_outer(w, keep_previous, |wlt, w| { + transform(wlt, PosLayoutArgs::new(i, w.try_state_mut(), size)); + }); + } + } + } + + fn count_nodes(&self, mut filter: F) -> usize + where + F: FnMut(UiNodeFilterArgs) -> bool, + { + let mut count = 0; + for (i, w) in self.iter().enumerate() { + if filter(UiNodeFilterArgs::new(i, w)) { + count += 1; + } + } + count + } } impl WidgetList for SortedWidgetVec { fn boxed_widget_all(mut self) -> WidgetVec { @@ -381,27 +458,27 @@ impl WidgetList for SortedWidgetVec { } } - fn widget_id(&self, index: usize) -> WidgetId { + fn item_id(&self, index: usize) -> WidgetId { self.vec[index].id() } - fn widget_state(&self, index: usize) -> &StateMap { + fn item_state(&self, index: usize) -> &StateMap { self.vec[index].state() } - fn widget_state_mut(&mut self, index: usize) -> &mut StateMap { + fn item_state_mut(&mut self, index: usize) -> &mut StateMap { self.vec[index].state_mut() } - fn widget_bounds_info(&self, index: usize) -> &WidgetBoundsInfo { + fn item_bounds_info(&self, index: usize) -> &WidgetBoundsInfo { self.vec[index].bounds_info() } - fn widget_border_info(&self, index: usize) -> &WidgetBorderInfo { + fn item_border_info(&self, index: usize) -> &WidgetBorderInfo { self.vec[index].border_info() } - fn widget_render_info(&self, index: usize) -> &WidgetRenderInfo { + fn item_render_info(&self, index: usize) -> &WidgetRenderInfo { self.vec[index].render_info() } @@ -431,26 +508,26 @@ impl WidgetList for SortedWidgetVec { count } - fn widget_outer(&mut self, index: usize, wl: &mut WidgetLayout, keep_previous: bool, transform: F) + fn item_outer(&mut self, index: usize, wl: &mut WidgetLayout, keep_previous: bool, transform: F) -> R where - F: FnOnce(&mut WidgetLayoutTranslation, PosLayoutArgs), + F: FnOnce(&mut WidgetLayoutTranslation, PosLayoutArgs) -> R, { let w = &mut self.vec[index]; let size = w.bounds_info().outer_size(); wl.with_outer(w, keep_previous, |wlt, w| { - transform(wlt, PosLayoutArgs::new(index, Some(w.state_mut()), size)); - }); + transform(wlt, PosLayoutArgs::new(index, Some(w.state_mut()), size)) + }) } fn outer_all(&mut self, wl: &mut WidgetLayout, keep_previous: bool, mut transform: F) where - F: FnMut(&mut WidgetLayoutTranslation, PosLayoutArgs), + F: FnMut(&mut WidgetLayoutTranslation, PosLayoutArgs) , { for (i, w) in self.vec.iter_mut().enumerate() { let size = w.bounds_info().outer_size(); wl.with_outer(w, keep_previous, |wlt, w| { transform(wlt, PosLayoutArgs::new(i, Some(w.state_mut()), size)); - }) + }); } } } diff --git a/zero-ui-core/src/ui_list/tuples.rs b/zero-ui-core/src/ui_list/tuples.rs index a6ea06f71..717924f8f 100644 --- a/zero-ui-core/src/ui_list/tuples.rs +++ b/zero-ui-core/src/ui_list/tuples.rs @@ -1,10 +1,11 @@ -use super::WidgetFilterArgs; use crate::{ context::{InfoContext, LayoutContext, RenderContext, StateMap, WidgetContext}, event::EventUpdateArgs, node_vec, render::{FrameBuilder, FrameUpdate}, - ui_list::{PosLayoutArgs, PreLayoutArgs, UiListObserver, UiNodeList, UiNodeVec, WidgetList, WidgetVec}, + ui_list::{ + PosLayoutArgs, PreLayoutArgs, UiListObserver, UiNodeFilterArgs, UiNodeList, UiNodeVec, WidgetFilterArgs, WidgetList, WidgetVec, + }, units::PxSize, widget_info::{ WidgetBorderInfo, WidgetBoundsInfo, WidgetInfoBuilder, WidgetLayout, WidgetLayoutTranslation, WidgetRenderInfo, WidgetSubscriptions, @@ -84,59 +85,59 @@ macro_rules! impl_tuples { )+ } - fn widget_id(&self, index: usize) -> WidgetId { + fn item_id(&self, index: usize) -> WidgetId { match index { $($n => self.items.$n.id(),)+ _ => panic!("index {index} out of range for length {}", self.len()) } } - fn widget_state(&self, index: usize) -> &StateMap { + fn item_state(&self, index: usize) -> &StateMap { match index { $($n => self.items.$n.state(),)+ _ => panic!("index {index} out of range for length {}", self.len()) } } - fn widget_state_mut(&mut self, index: usize) -> &mut StateMap { + fn item_state_mut(&mut self, index: usize) -> &mut StateMap { match index { $($n => self.items.$n.state_mut(),)+ _ => panic!("index {index} out of range for length {}", self.len()) } } - fn widget_bounds_info(&self, index: usize) -> &WidgetBoundsInfo { + fn item_bounds_info(&self, index: usize) -> &WidgetBoundsInfo { match index { $($n => self.items.$n.bounds_info(),)+ _ => panic!("index {index} out of range for length {}", self.len()) } } - fn widget_border_info(&self, index: usize) -> &WidgetBorderInfo { + fn item_border_info(&self, index: usize) -> &WidgetBorderInfo { match index { $($n => self.items.$n.border_info(),)+ _ => panic!("index {index} out of range for length {}", self.len()) } } - fn widget_render_info(&self, index: usize) -> &WidgetRenderInfo { + fn item_render_info(&self, index: usize) -> &WidgetRenderInfo { match index { $($n => self.items.$n.render_info(),)+ _ => panic!("index {index} out of range for length {}", self.len()) } } - fn widget_outer(&mut self, index: usize, wl: &mut WidgetLayout, keep_previous: bool, transform: F) + fn item_outer(&mut self, index: usize, wl: &mut WidgetLayout, keep_previous: bool, transform: F) -> R where - F: FnOnce(&mut WidgetLayoutTranslation, PosLayoutArgs), + F: FnOnce(&mut WidgetLayoutTranslation, PosLayoutArgs) -> R, { match index { $($n => { let w = &mut self.items.$n; let size = w.bounds_info().outer_size(); wl.with_outer(w, keep_previous, |wlt, w| { - transform(wlt, PosLayoutArgs::new($n, Some(w.state_mut()), size)); - }); + transform(wlt, PosLayoutArgs::new($n, Some(w.state_mut()), size)) + }) })+ _ => panic!("index {index} out of range for length {}", self.len()) } @@ -288,6 +289,117 @@ macro_rules! impl_tuples { _ => panic!("index {index} out of range for length {}", self.len()), } } + + fn try_item_id(&self, index: usize) -> Option { + match index { + $( + $n => self.items.$n.try_id(), + )+ + _ => panic!("index {index} out of range for length {}", self.len()), + } + } + + fn try_item_state(&self, index: usize) -> Option<&StateMap> { + match index { + $( + $n => self.items.$n.try_state(), + )+ + _ => panic!("index {index} out of range for length {}", self.len()), + } + } + + fn try_item_state_mut(&mut self, index: usize) -> Option<&mut StateMap> { + match index { + $( + $n => self.items.$n.try_state_mut(), + )+ + _ => panic!("index {index} out of range for length {}", self.len()), + } + } + + fn try_item_bounds_info(&self, index: usize) -> Option<&WidgetBoundsInfo> { + match index { + $( + $n => self.items.$n.try_bounds_info(), + )+ + _ => panic!("index {index} out of range for length {}", self.len()), + } + } + + fn try_item_border_info(&self, index: usize) -> Option<&WidgetBorderInfo> { + match index { + $( + $n => self.items.$n.try_border_info(), + )+ + _ => panic!("index {index} out of range for length {}", self.len()), + } + } + + fn try_item_render_info(&self, index: usize) -> Option<&WidgetRenderInfo> { + match index { + $( + $n => self.items.$n.try_render_info(), + )+ + _ => panic!("index {index} out of range for length {}", self.len()), + } + } + + fn render_node_filtered(&self, mut filter: F, ctx: &mut RenderContext, frame: &mut FrameBuilder) + where + F: FnMut(super::UiNodeFilterArgs) -> bool, + { + $( + if filter(UiNodeFilterArgs::new($n, &self.items.$n)) { + self.items.$n.render(ctx, frame); + } + )+ + } + + fn try_item_outer(&mut self, index: usize, wl: &mut WidgetLayout, keep_previous: bool, transform: F) -> Option + where + F: FnOnce(&mut WidgetLayoutTranslation, PosLayoutArgs) -> R, + { + match index { + $($n => { + let w = &mut self.items.$n; + if let Some(size) = w.try_bounds_info().map(|i|i.outer_size()) { + wl.try_with_outer(w, keep_previous, |wlt, w| { + transform(wlt, PosLayoutArgs::new($n, w.try_state_mut(), size)) + }) + } else { + None + } + })+ + _ => panic!("index {index} out of range for length {}", self.len()) + } + } + + fn try_outer_all(&mut self, wl: &mut WidgetLayout, keep_previous: bool, mut transform: F) + where + F: FnMut(&mut WidgetLayoutTranslation, PosLayoutArgs), + { + $( + let w = &mut self.items.$n; + if let Some(size) = w.try_bounds_info().map(|i|i.outer_size()) { + wl.try_with_outer(w, keep_previous, |wlt, w| { + transform(wlt, PosLayoutArgs::new($n, w.try_state_mut(), size)); + }); + } + )* + } + + fn count_nodes(&self, mut filter: F) -> usize + where + F: FnMut(super::UiNodeFilterArgs) -> bool, + { + let mut count = 0; + $( + if filter(UiNodeFilterArgs::new($n, &self.items.$n)) { + count += 1; + } + )+ + count + } } }; } @@ -388,6 +500,56 @@ macro_rules! empty_node_list { fn item_render_update(&self, index: usize, _: &mut RenderContext, _: &mut FrameUpdate) { panic!("index {index} out of range for length 0") } + + fn try_item_id(&self, index: usize) -> Option { + panic!("index {index} out of range for length 0") + } + + fn try_item_state(&self, index: usize) -> Option<&StateMap> { + panic!("index {index} out of range for length 0") + } + + fn try_item_state_mut(&mut self, index: usize) -> Option<&mut StateMap> { + panic!("index {index} out of range for length 0") + } + + fn try_item_bounds_info(&self, index: usize) -> Option<&WidgetBoundsInfo> { + panic!("index {index} out of range for length 0") + } + + fn try_item_border_info(&self, index: usize) -> Option<&WidgetBorderInfo> { + panic!("index {index} out of range for length 0") + } + + fn try_item_render_info(&self, index: usize) -> Option<&WidgetRenderInfo> { + panic!("index {index} out of range for length 0") + } + + fn render_node_filtered(&self, _: F, _: &mut RenderContext, _: &mut FrameBuilder) + where + F: FnMut(super::UiNodeFilterArgs) -> bool, + { + } + + fn try_item_outer(&mut self, index: usize, _: &mut WidgetLayout, _: bool, _: F) -> Option + where + F: FnOnce(&mut WidgetLayoutTranslation, PosLayoutArgs) -> R, + { + panic!("index {index} out of range for length 0") + } + + fn try_outer_all(&mut self, _: &mut WidgetLayout, _: bool, _: F) + where + F: FnMut(&mut WidgetLayoutTranslation, PosLayoutArgs), + { + } + + fn count_nodes(&self, _: F) -> usize + where + F: FnMut(super::UiNodeFilterArgs) -> bool, + { + 0 + } } )+} } @@ -413,33 +575,33 @@ impl WidgetList for WidgetList0 { { } - fn widget_id(&self, index: usize) -> WidgetId { + fn item_id(&self, index: usize) -> WidgetId { panic!("index {index} out of range for length 0") } - fn widget_state(&self, index: usize) -> &StateMap { + fn item_state(&self, index: usize) -> &StateMap { panic!("index {index} out of range for length 0") } - fn widget_state_mut(&mut self, index: usize) -> &mut StateMap { + fn item_state_mut(&mut self, index: usize) -> &mut StateMap { panic!("index {index} out of range for length 0") } - fn widget_bounds_info(&self, index: usize) -> &WidgetBoundsInfo { + fn item_bounds_info(&self, index: usize) -> &WidgetBoundsInfo { panic!("index {index} out of range for length 0") } - fn widget_border_info(&self, index: usize) -> &WidgetBorderInfo { + fn item_border_info(&self, index: usize) -> &WidgetBorderInfo { panic!("index {index} out of range for length 0") } - fn widget_render_info(&self, index: usize) -> &WidgetRenderInfo { + fn item_render_info(&self, index: usize) -> &WidgetRenderInfo { panic!("index {index} out of range for length 0") } - fn widget_outer(&mut self, index: usize, _: &mut WidgetLayout, _: bool, _: F) + fn item_outer(&mut self, index: usize, _: &mut WidgetLayout, _: bool, _: F) -> R where - F: FnOnce(&mut WidgetLayoutTranslation, PosLayoutArgs), + F: FnOnce(&mut WidgetLayoutTranslation, PosLayoutArgs) -> R, { panic!("index {index} out of range for length 0") } diff --git a/zero-ui-core/src/ui_list/vec.rs b/zero-ui-core/src/ui_list/vec.rs index 1310a2946..a6651c073 100644 --- a/zero-ui-core/src/ui_list/vec.rs +++ b/zero-ui-core/src/ui_list/vec.rs @@ -389,33 +389,108 @@ impl UiNodeList for WidgetVec { fn item_render_update(&self, index: usize, ctx: &mut RenderContext, update: &mut FrameUpdate) { self.vec[index].render_update(ctx, update); } + + fn try_item_id(&self, index: usize) -> Option { + self.vec[index].try_id() + } + + fn try_item_state(&self, index: usize) -> Option<&StateMap> { + self.vec[index].try_state() + } + + fn try_item_state_mut(&mut self, index: usize) -> Option<&mut StateMap> { + self.vec[index].try_state_mut() + } + + fn try_item_bounds_info(&self, index: usize) -> Option<&WidgetBoundsInfo> { + self.vec[index].try_bounds_info() + } + + fn try_item_border_info(&self, index: usize) -> Option<&WidgetBorderInfo> { + self.vec[index].try_border_info() + } + + fn try_item_render_info(&self, index: usize) -> Option<&WidgetRenderInfo> { + self.vec[index].try_render_info() + } + + fn render_node_filtered(&self, mut filter: F, ctx: &mut RenderContext, frame: &mut FrameBuilder) + where + F: FnMut(UiNodeFilterArgs) -> bool, + { + for (i, w) in self.iter().enumerate() { + if filter(UiNodeFilterArgs::new(i, w)) { + w.render(ctx, frame); + } + } + } + + fn try_item_outer(&mut self, index: usize, wl: &mut WidgetLayout, keep_previous: bool, transform: F) -> Option + where + F: FnOnce(&mut WidgetLayoutTranslation, PosLayoutArgs) -> R, + { + let w = &mut self.vec[index]; + if let Some(size) = w.try_bounds_info().map(|i| i.outer_size()) { + wl.try_with_outer(w, keep_previous, |wlt, w| { + transform(wlt, PosLayoutArgs::new(index, w.try_state_mut(), size)) + }) + } else { + None + } + } + + fn try_outer_all(&mut self, wl: &mut WidgetLayout, keep_previous: bool, mut transform: F) + where + F: FnMut(&mut WidgetLayoutTranslation, PosLayoutArgs), + { + for (i, w) in self.vec.iter_mut().enumerate() { + if let Some(size) = w.try_bounds_info().map(|i| i.outer_size()) { + wl.try_with_outer(w, keep_previous, |wlt, w| { + transform(wlt, PosLayoutArgs::new(i, w.try_state_mut(), size)); + }); + } + } + } + + fn count_nodes(&self, mut filter: F) -> usize + where + F: FnMut(UiNodeFilterArgs) -> bool, + { + let mut count = 0; + for (i, w) in self.iter().enumerate() { + if filter(UiNodeFilterArgs::new(i, w)) { + count += 1; + } + } + count + } } impl WidgetList for WidgetVec { fn boxed_widget_all(self) -> WidgetVec { self } - fn widget_id(&self, index: usize) -> WidgetId { + fn item_id(&self, index: usize) -> WidgetId { self.vec[index].id() } - fn widget_state(&self, index: usize) -> &StateMap { + fn item_state(&self, index: usize) -> &StateMap { self.vec[index].state() } - fn widget_state_mut(&mut self, index: usize) -> &mut StateMap { + fn item_state_mut(&mut self, index: usize) -> &mut StateMap { self.vec[index].state_mut() } - fn widget_bounds_info(&self, index: usize) -> &WidgetBoundsInfo { + fn item_bounds_info(&self, index: usize) -> &WidgetBoundsInfo { self.vec[index].bounds_info() } - fn widget_border_info(&self, index: usize) -> &WidgetBorderInfo { + fn item_border_info(&self, index: usize) -> &WidgetBorderInfo { self.vec[index].border_info() } - fn widget_render_info(&self, index: usize) -> &WidgetRenderInfo { + fn item_render_info(&self, index: usize) -> &WidgetRenderInfo { self.vec[index].render_info() } @@ -445,15 +520,15 @@ impl WidgetList for WidgetVec { count } - fn widget_outer(&mut self, index: usize, wl: &mut WidgetLayout, keep_previous: bool, transform: F) + fn item_outer(&mut self, index: usize, wl: &mut WidgetLayout, keep_previous: bool, transform: F) -> R where - F: FnOnce(&mut WidgetLayoutTranslation, PosLayoutArgs), + F: FnOnce(&mut WidgetLayoutTranslation, PosLayoutArgs) -> R, { let w = &mut self.vec[index]; let size = w.bounds_info().outer_size(); wl.with_outer(w, keep_previous, |wlt, w| { - transform(wlt, PosLayoutArgs::new(index, Some(w.state_mut()), size)); - }); + transform(wlt, PosLayoutArgs::new(index, Some(w.state_mut()), size)) + }) } fn outer_all(&mut self, wl: &mut WidgetLayout, keep_previous: bool, mut transform: F) @@ -839,6 +914,81 @@ impl UiNodeList for UiNodeVec { fn item_render_update(&self, index: usize, ctx: &mut RenderContext, update: &mut FrameUpdate) { self.vec[index].render_update(ctx, update); } + + fn try_item_id(&self, index: usize) -> Option { + self.vec[index].try_id() + } + + fn try_item_state(&self, index: usize) -> Option<&StateMap> { + self.vec[index].try_state() + } + + fn try_item_state_mut(&mut self, index: usize) -> Option<&mut StateMap> { + self.vec[index].try_state_mut() + } + + fn try_item_bounds_info(&self, index: usize) -> Option<&WidgetBoundsInfo> { + self.vec[index].try_bounds_info() + } + + fn try_item_border_info(&self, index: usize) -> Option<&WidgetBorderInfo> { + self.vec[index].try_border_info() + } + + fn try_item_render_info(&self, index: usize) -> Option<&WidgetRenderInfo> { + self.vec[index].try_render_info() + } + + fn render_node_filtered(&self, mut filter: F, ctx: &mut RenderContext, frame: &mut FrameBuilder) + where + F: FnMut(UiNodeFilterArgs) -> bool, + { + for (i, w) in self.iter().enumerate() { + if filter(UiNodeFilterArgs::new(i, w)) { + w.render(ctx, frame); + } + } + } + + fn try_item_outer(&mut self, index: usize, wl: &mut WidgetLayout, keep_previous: bool, transform: F) -> Option + where + F: FnOnce(&mut WidgetLayoutTranslation, PosLayoutArgs) -> R, + { + let w = &mut self.vec[index]; + if let Some(size) = w.try_bounds_info().map(|i| i.outer_size()) { + wl.try_with_outer(w, keep_previous, |wlt, w| { + transform(wlt, PosLayoutArgs::new(index, w.try_state_mut(), size)) + }) + } else { + None + } + } + + fn try_outer_all(&mut self, wl: &mut WidgetLayout, keep_previous: bool, mut transform: F) + where + F: FnMut(&mut WidgetLayoutTranslation, PosLayoutArgs), + { + for (i, w) in self.vec.iter_mut().enumerate() { + if let Some(size) = w.try_bounds_info().map(|i| i.outer_size()) { + wl.try_with_outer(w, keep_previous, |wlt, w| { + transform(wlt, PosLayoutArgs::new(i, w.try_state_mut(), size)); + }); + } + } + } + + fn count_nodes(&self, mut filter: F) -> usize + where + F: FnMut(UiNodeFilterArgs) -> bool, + { + let mut count = 0; + for (i, w) in self.iter().enumerate() { + if filter(UiNodeFilterArgs::new(i, w)) { + count += 1; + } + } + count + } } /// Creates a [`WidgetVec`] containing the arguments. @@ -902,3 +1052,5 @@ macro_rules! node_vec { } #[doc(inline)] pub use crate::node_vec; + +use super::UiNodeFilterArgs; diff --git a/zero-ui-core/src/ui_list/z_sorted.rs b/zero-ui-core/src/ui_list/z_sorted.rs index 3a320b938..3fbd99adf 100644 --- a/zero-ui-core/src/ui_list/z_sorted.rs +++ b/zero-ui-core/src/ui_list/z_sorted.rs @@ -183,6 +183,75 @@ impl UiNodeList for ZSortedWidgetList { fn item_render_update(&self, index: usize, ctx: &mut RenderContext, update: &mut FrameUpdate) { self.list.item_render_update(index, ctx, update) } + + fn try_item_id(&self, index: usize) -> Option { + self.list.try_item_id(index) + } + + fn try_item_state(&self, index: usize) -> Option<&StateMap> { + self.list.try_item_state(index) + } + + fn try_item_state_mut(&mut self, index: usize) -> Option<&mut StateMap> { + self.list.try_item_state_mut(index) + } + + fn try_item_bounds_info(&self, index: usize) -> Option<&WidgetBoundsInfo> { + self.list.try_item_bounds_info(index) + } + + fn try_item_border_info(&self, index: usize) -> Option<&WidgetBorderInfo> { + self.list.try_item_border_info(index) + } + + fn try_item_render_info(&self, index: usize) -> Option<&WidgetRenderInfo> { + self.list.try_item_render_info(index) + } + + fn render_node_filtered(&self, mut filter: F, ctx: &mut RenderContext, frame: &mut FrameBuilder) + where + F: FnMut(UiNodeFilterArgs) -> bool, + { + if self.lookup.is_empty() { + self.list.render_node_filtered(filter, ctx, frame); + } else { + for &i in &self.lookup { + let i = i as usize; + let args = UiNodeFilterArgs { + index: i, + id: self.try_item_id(i), + bounds_info: self.try_item_bounds_info(i), + border_info: self.try_item_border_info(i), + render_info: self.try_item_render_info(i), + state: self.try_item_state(i), + }; + if filter(args) { + self.item_render(i, ctx, frame); + } + } + } + } + + fn try_item_outer(&mut self, index: usize, wl: &mut WidgetLayout, keep_previous: bool, transform: F) -> Option + where + F: FnOnce(&mut WidgetLayoutTranslation, PosLayoutArgs) -> R, + { + self.list.try_item_outer(index, wl, keep_previous, transform) + } + + fn try_outer_all(&mut self, wl: &mut WidgetLayout, keep_previous: bool, transform: F) + where + F: FnMut(&mut WidgetLayoutTranslation, PosLayoutArgs), + { + self.list.try_outer_all(wl, keep_previous, transform) + } + + fn count_nodes(&self, filter: F) -> usize + where + F: FnMut(UiNodeFilterArgs) -> bool, + { + self.list.count_nodes(filter) + } } impl WidgetList for ZSortedWidgetList { fn count(&self, filter: F) -> usize @@ -196,28 +265,28 @@ impl WidgetList for ZSortedWidgetList { self.list.boxed_widget_all() } - fn widget_id(&self, index: usize) -> WidgetId { - self.list.widget_id(index) + fn item_id(&self, index: usize) -> WidgetId { + self.list.item_id(index) } - fn widget_state(&self, index: usize) -> &StateMap { - self.list.widget_state(index) + fn item_state(&self, index: usize) -> &StateMap { + self.list.item_state(index) } - fn widget_state_mut(&mut self, index: usize) -> &mut StateMap { - self.list.widget_state_mut(index) + fn item_state_mut(&mut self, index: usize) -> &mut StateMap { + self.list.item_state_mut(index) } - fn widget_bounds_info(&self, index: usize) -> &WidgetBoundsInfo { - self.list.widget_bounds_info(index) + fn item_bounds_info(&self, index: usize) -> &WidgetBoundsInfo { + self.list.item_bounds_info(index) } - fn widget_border_info(&self, index: usize) -> &WidgetBorderInfo { - self.list.widget_border_info(index) + fn item_border_info(&self, index: usize) -> &WidgetBorderInfo { + self.list.item_border_info(index) } - fn widget_render_info(&self, index: usize) -> &WidgetRenderInfo { - self.list.widget_render_info(index) + fn item_render_info(&self, index: usize) -> &WidgetRenderInfo { + self.list.item_render_info(index) } fn render_filtered(&self, mut filter: F, ctx: &mut RenderContext, frame: &mut FrameBuilder) @@ -231,10 +300,11 @@ impl WidgetList for ZSortedWidgetList { let i = i as usize; let args = WidgetFilterArgs { index: i, - bounds_info: self.widget_bounds_info(i), - border_info: self.widget_border_info(i), - render_info: self.widget_render_info(i), - state: self.widget_state(i), + id: self.item_id(i), + bounds_info: self.item_bounds_info(i), + border_info: self.item_border_info(i), + render_info: self.item_render_info(i), + state: self.item_state(i), }; if filter(args) { self.item_render(i, ctx, frame); @@ -243,11 +313,11 @@ impl WidgetList for ZSortedWidgetList { } } - fn widget_outer(&mut self, index: usize, wl: &mut WidgetLayout, keep_previous: bool, transform: F) + fn item_outer(&mut self, index: usize, wl: &mut WidgetLayout, keep_previous: bool, transform: F) -> R where - F: FnOnce(&mut WidgetLayoutTranslation, PosLayoutArgs), + F: FnOnce(&mut WidgetLayoutTranslation, PosLayoutArgs) -> R, { - self.list.widget_outer(index, wl, keep_previous, transform) + self.list.item_outer(index, wl, keep_previous, transform) } fn outer_all(&mut self, wl: &mut WidgetLayout, keep_previous: bool, transform: F) @@ -467,7 +537,7 @@ pub trait WidgetListZIndexExt { } impl WidgetListZIndexExt for L { fn widget_z_index(&self, index: usize) -> ZIndex { - self.widget_state(index).copy(ZIndexKey).unwrap_or_default() + self.item_state(index).copy(ZIndexKey).unwrap_or_default() } fn init_all_z(&mut self, ctx: &mut WidgetContext, sort_z: &mut bool) { diff --git a/zero-ui-core/src/ui_node.rs b/zero-ui-core/src/ui_node.rs index aa9c603de..a86d843f1 100644 --- a/zero-ui-core/src/ui_node.rs +++ b/zero-ui-core/src/ui_node.rs @@ -272,6 +272,68 @@ pub trait UiNode: 'static { { self } + + /// Gets if this node is a [`Widget`] implementer. + fn is_widget(&self) -> bool { + false + } + + /// Gets the [`Widget::id`] if this node [`is_widget`]. + /// + /// [`is_widget`]: UiNode::is_widget + fn try_id(&self) -> Option { + None + } + + /// Gets the [`Widget::state`] if this node [`is_widget`]. + /// + /// [`is_widget`]: UiNode::is_widget + fn try_state(&self) -> Option<&StateMap> { + None + } + + /// Gets the [`Widget::state_mut`] if this node [`is_widget`]. + /// + /// [`is_widget`]: UiNode::is_widget + fn try_state_mut(&mut self) -> Option<&mut StateMap> { + None + } + + /// Gets the [`Widget::bounds_info`] if this node [`is_widget`]. + /// + /// [`is_widget`]: UiNode::is_widget + fn try_bounds_info(&self) -> Option<&WidgetBoundsInfo> { + None + } + + /// Gets the [`Widget::border_info`] if this node [`is_widget`]. + /// + /// [`is_widget`]: UiNode::is_widget + fn try_border_info(&self) -> Option<&WidgetBorderInfo> { + None + } + + /// Gets the [`Widget::render_info`] if this node [`is_widget`]. + /// + /// [`is_widget`]: UiNode::is_widget + fn try_render_info(&self) -> Option<&WidgetRenderInfo> { + None + } + + /// Gets this node as a [`BoxedWidget`], if the node [`is_widget`] this is the same as + /// [`Widget::boxed_wgt`], otherwise a new widget is generated with the node as the *inner*. + /// + /// [`is_widget`]: Self::is_widget + fn into_widget(self) -> BoxedWidget + where + Self: Sized, + { + use crate::widget_base::implicit_base::nodes; + + let node = nodes::inner(self.cfg_boxed()); + let wgt = nodes::widget(node, WidgetId::new_unique()); + wgt.boxed_wgt() + } } #[doc(hidden)] @@ -285,6 +347,15 @@ pub trait UiNodeBoxed: 'static { fn layout_boxed(&mut self, ctx: &mut LayoutContext, wl: &mut WidgetLayout) -> PxSize; fn render_boxed(&self, ctx: &mut RenderContext, frame: &mut FrameBuilder); fn render_update_boxed(&self, ctx: &mut RenderContext, update: &mut FrameUpdate); + + fn is_widget_boxed(&self) -> bool; + fn try_id_boxed(&self) -> Option; + fn try_state_boxed(&self) -> Option<&StateMap>; + fn try_state_mut_boxed(&mut self) -> Option<&mut StateMap>; + fn try_bounds_info_boxed(&self) -> Option<&WidgetBoundsInfo>; + fn try_border_info_boxed(&self) -> Option<&WidgetBorderInfo>; + fn try_render_info_boxed(&self) -> Option<&WidgetRenderInfo>; + fn into_widget_boxed(self: Box) -> BoxedWidget; } impl UiNodeBoxed for U { @@ -323,6 +394,38 @@ impl UiNodeBoxed for U { fn render_update_boxed(&self, ctx: &mut RenderContext, update: &mut FrameUpdate) { self.render_update(ctx, update); } + + fn is_widget_boxed(&self) -> bool { + self.is_widget() + } + + fn try_id_boxed(&self) -> Option { + self.try_id() + } + + fn try_state_boxed(&self) -> Option<&StateMap> { + self.try_state() + } + + fn try_state_mut_boxed(&mut self) -> Option<&mut StateMap> { + self.try_state_mut() + } + + fn try_bounds_info_boxed(&self) -> Option<&WidgetBoundsInfo> { + self.try_bounds_info() + } + + fn try_border_info_boxed(&self) -> Option<&WidgetBorderInfo> { + self.try_border_info() + } + + fn try_render_info_boxed(&self) -> Option<&WidgetRenderInfo> { + self.try_render_info() + } + + fn into_widget_boxed(self: Box) -> BoxedWidget { + self.into_widget() + } } /// An [`UiNode`] in a box. @@ -375,6 +478,41 @@ impl UiNode for BoxedUiNode { { self } + + fn is_widget(&self) -> bool { + self.as_ref().is_widget_boxed() + } + + fn try_id(&self) -> Option { + self.as_ref().try_id_boxed() + } + + fn try_state(&self) -> Option<&StateMap> { + self.as_ref().try_state_boxed() + } + + fn try_state_mut(&mut self) -> Option<&mut StateMap> { + self.as_mut().try_state_mut_boxed() + } + + fn try_bounds_info(&self) -> Option<&WidgetBoundsInfo> { + self.as_ref().try_bounds_info_boxed() + } + + fn try_border_info(&self) -> Option<&WidgetBorderInfo> { + self.as_ref().try_border_info_boxed() + } + + fn try_render_info(&self) -> Option<&WidgetRenderInfo> { + self.as_ref().try_render_info_boxed() + } + + fn into_widget(self) -> BoxedWidget + where + Self: Sized, + { + self.into_widget_boxed() + } } impl UiNode for Option { @@ -433,6 +571,75 @@ impl UiNode for Option { node.render_update(ctx, update); } } + + fn boxed(self) -> BoxedUiNode + where + Self: Sized, + { + match self { + Some(node) => node.boxed(), + None => NilUiNode.boxed(), + } + } + + fn is_widget(&self) -> bool { + match self { + Some(node) => node.is_widget(), + None => false, + } + } + + fn try_id(&self) -> Option { + match self { + Some(node) => node.try_id(), + None => None, + } + } + + fn try_state(&self) -> Option<&StateMap> { + match self { + Some(node) => node.try_state(), + None => None, + } + } + + fn try_state_mut(&mut self) -> Option<&mut StateMap> { + match self { + Some(node) => node.try_state_mut(), + None => None, + } + } + + fn try_bounds_info(&self) -> Option<&WidgetBoundsInfo> { + match self { + Some(node) => node.try_bounds_info(), + None => None, + } + } + + fn try_border_info(&self) -> Option<&WidgetBorderInfo> { + match self { + Some(node) => node.try_border_info(), + None => None, + } + } + + fn try_render_info(&self) -> Option<&WidgetRenderInfo> { + match self { + Some(node) => node.try_render_info(), + None => None, + } + } + + fn into_widget(self) -> BoxedWidget + where + Self: Sized, + { + match self { + Some(node) => node.into_widget(), + None => NilUiNode.into_widget(), + } + } } macro_rules! declare_widget_test_calls { @@ -657,6 +864,48 @@ impl UiNode for BoxedWidget { fn render_update(&self, ctx: &mut RenderContext, update: &mut FrameUpdate) { self.as_ref().render_update_boxed(ctx, update); } + + fn boxed(self) -> BoxedUiNode + where + Self: Sized, + { + Box::new(self) + } + + fn is_widget(&self) -> bool { + true + } + + fn try_id(&self) -> Option { + Some(self.id()) + } + + fn try_state(&self) -> Option<&StateMap> { + Some(self.state()) + } + + fn try_state_mut(&mut self) -> Option<&mut StateMap> { + Some(self.state_mut()) + } + + fn try_bounds_info(&self) -> Option<&WidgetBoundsInfo> { + Some(self.bounds_info()) + } + + fn try_border_info(&self) -> Option<&WidgetBorderInfo> { + Some(self.border_info()) + } + + fn try_render_info(&self) -> Option<&WidgetRenderInfo> { + Some(self.render_info()) + } + + fn into_widget(self) -> BoxedWidget + where + Self: Sized, + { + self + } } impl Widget for BoxedWidget { fn id(&self) -> WidgetId { @@ -682,6 +931,13 @@ impl Widget for BoxedWidget { fn render_info(&self) -> &WidgetRenderInfo { self.as_ref().render_info_boxed() } + + fn boxed_wgt(self) -> BoxedWidget + where + Self: Sized, + { + self + } } /// A UI node that does not contain any other node, only takes the minimum space and renders nothing. diff --git a/zero-ui-core/src/widget_base.rs b/zero-ui-core/src/widget_base.rs index d69b05161..b5bfa0dab 100644 --- a/zero-ui-core/src/widget_base.rs +++ b/zero-ui-core/src/widget_base.rs @@ -368,18 +368,6 @@ pub mod implicit_base { } } - fn update(&mut self, ctx: &mut WidgetContext) { - #[cfg(debug_assertions)] - if !self.inited { - tracing::error!(target: "widget_base", "`UiNode::update` called in not inited widget {:?}", self.id); - } - - if self.subscriptions.borrow().update_intersects(ctx.updates) { - let (_, updates) = ctx.widget_context(self.id, &self.info, &mut self.state, |ctx| self.child.update(ctx)); - *self.pending_updates.get_mut() |= updates; - } - } - fn event(&mut self, ctx: &mut WidgetContext, args: &EU) { #[cfg(debug_assertions)] if !self.inited { @@ -392,6 +380,18 @@ pub mod implicit_base { } } + fn update(&mut self, ctx: &mut WidgetContext) { + #[cfg(debug_assertions)] + if !self.inited { + tracing::error!(target: "widget_base", "`UiNode::update` called in not inited widget {:?}", self.id); + } + + if self.subscriptions.borrow().update_intersects(ctx.updates) { + let (_, updates) = ctx.widget_context(self.id, &self.info, &mut self.state, |ctx| self.child.update(ctx)); + *self.pending_updates.get_mut() |= updates; + } + } + fn layout(&mut self, ctx: &mut LayoutContext, wl: &mut WidgetLayout) -> PxSize { #[cfg(debug_assertions)] if !self.inited { @@ -442,6 +442,41 @@ pub mod implicit_base { } // else, don't need to do anything, updates are additive. } + + fn is_widget(&self) -> bool { + true + } + + fn try_id(&self) -> Option { + Some(self.id()) + } + + fn try_state(&self) -> Option<&StateMap> { + Some(self.state()) + } + + fn try_state_mut(&mut self) -> Option<&mut StateMap> { + Some(self.state_mut()) + } + + fn try_bounds_info(&self) -> Option<&WidgetBoundsInfo> { + Some(self.bounds_info()) + } + + fn try_border_info(&self) -> Option<&WidgetBorderInfo> { + Some(self.border_info()) + } + + fn try_render_info(&self) -> Option<&WidgetRenderInfo> { + Some(self.render_info()) + } + + fn into_widget(self) -> crate::BoxedWidget + where + Self: Sized, + { + self.boxed_wgt() + } } impl Widget for WidgetNode { fn id(&self) -> WidgetId { diff --git a/zero-ui-core/src/widget_info.rs b/zero-ui-core/src/widget_info.rs index 3b85fa672..edd5e1039 100644 --- a/zero-ui-core/src/widget_info.rs +++ b/zero-ui-core/src/widget_info.rs @@ -14,7 +14,7 @@ use crate::{ var::{Var, VarValue, VarsRead, WithVarsRead}, widget_base::Visibility, window::WindowId, - Widget, WidgetId, + UiNode, Widget, WidgetId, }; unique_id_64! { @@ -233,17 +233,43 @@ impl WidgetLayout { keep_previous: bool, translate: impl FnOnce(&mut WidgetLayoutTranslation, &mut W) -> R, ) -> R { - let prev_outer = widget.bounds_info().outer_offset(); + self.with_outer_impl(widget.bounds_info().clone(), widget, keep_previous, translate) + } + + /// Applies [`with_outer`] to the `node` if it is a full widget. + /// + /// Returns `Some(_)` if `translate` was called, or `None` if the `node` was not a full widget. + /// + /// [`with_outer`]: Self::with_outer + pub fn try_with_outer( + &mut self, + node: &mut N, + keep_previous: bool, + translate: impl FnOnce(&mut WidgetLayoutTranslation, &mut N) -> R, + ) -> Option { + node.try_bounds_info() + .cloned() + .map(|info| self.with_outer_impl(info, node, keep_previous, translate)) + } + + fn with_outer_impl( + &mut self, + bounds: WidgetBoundsInfo, + target: &mut T, + keep_previous: bool, + translate: impl FnOnce(&mut WidgetLayoutTranslation, &mut T) -> R, + ) -> R { + let prev_outer = bounds.outer_offset(); if !keep_previous { - widget.bounds_info().set_outer_offset(PxVector::zero()); + bounds.set_outer_offset(PxVector::zero()); } let mut wl = WidgetLayout { t: WidgetLayoutTranslation { offset_buf: PxVector::zero(), baseline: Px(0), - known: Some(widget.bounds_info().clone()), + known: Some(bounds), known_target: KnownTarget::Outer, }, known_prev_offsets: [PxVector::zero(); 3], @@ -252,10 +278,12 @@ impl WidgetLayout { child_offset_changed: false, }; - let size = translate(&mut wl, widget); + let size = translate(&mut wl, target); - if prev_outer != widget.bounds_info().outer_offset() { - widget.bounds_info().update_offsets_version(); + let bounds = wl.t.known.unwrap(); + + if prev_outer != bounds.outer_offset() { + bounds.update_offsets_version(); self.child_offset_changed = true; } diff --git a/zero-ui/src/widgets/layouts/stacks.rs b/zero-ui/src/widgets/layouts/stacks.rs index 7003303aa..58a600bfe 100644 --- a/zero-ui/src/widgets/layouts/stacks.rs +++ b/zero-ui/src/widgets/layouts/stacks.rs @@ -118,7 +118,7 @@ pub mod h_stack { |ctx| { size.width = Px(0); for i in 0..self.children.len() { - let o_size = self.children.widget_bounds_info(i).outer_size(); + let o_size = self.children.item_bounds_info(i).outer_size(); if Some(o_size.height) != ctx.constrains().y.max() { // only need second pass for items that don't fill let (a_size, _) = wl.with_child(ctx, |ctx, wl| { @@ -133,7 +133,7 @@ pub mod h_stack { } } else { // item already fills width, but may have moved due to sibling new fill size - self.children.widget_outer(i, wl, false, |wlt, _| { + self.children.item_outer(i, wl, false, |wlt, _| { wlt.translate(PxVector::new(size.width, Px(0))); if o_size.width > Px(0) { @@ -302,7 +302,7 @@ pub mod v_stack { |ctx| { size.height = Px(0); for i in 0..self.children.len() { - let o_size = self.children.widget_bounds_info(i).outer_size(); + let o_size = self.children.item_bounds_info(i).outer_size(); if Some(o_size.width) != ctx.constrains().x.max() { // only need second pass for items that don't fill let (a_size, _) = wl.with_child(ctx, |ctx, wl| { @@ -317,7 +317,7 @@ pub mod v_stack { } } else { // item already fills width, but may have moved due to sibling new fill size - self.children.widget_outer(i, wl, false, |wlt, _| { + self.children.item_outer(i, wl, false, |wlt, _| { wlt.translate(PxVector::new(Px(0), size.height)); if o_size.height > Px(0) {