zng/examples/focus.rs

474 lines
16 KiB
Rust
Raw Normal View History

#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
use zero_ui::core::focus::{FocusRequest, FocusTarget, FOCUS_CHANGED_EVENT};
2023-02-25 12:16:05 +08:00
use zero_ui::prelude::new_widget::WINDOW;
use zero_ui::prelude::*;
use zero_ui::widgets::window::{LayerIndex, LAYERS};
use zero_ui_view_prebuilt as zero_ui_view;
fn main() {
examples_util::print_info();
zero_ui_view::init();
// let rec = examples_util::record_profile("focus");
// zero_ui_view::run_same_process(app_main);
app_main();
// rec.finish();
}
fn app_main() {
App::default().run_window(async {
2023-02-25 12:16:05 +08:00
WINDOW.id().set_name("main").unwrap();
2022-10-09 12:11:51 +08:00
trace_focus();
let window_enabled = var(true);
window! {
title = "Focus Example";
enabled = window_enabled.clone();
background = commands();
2022-12-28 11:57:28 +08:00
child = stack! {
direction = StackDirection::top_to_bottom();
2022-12-29 04:37:14 +08:00
children = ui_vec![
alt_scope(),
2022-12-28 11:57:28 +08:00
stack! {
direction = StackDirection::left_to_right();
2021-05-14 12:12:14 +08:00
margin = (50, 0, 0, 0);
align = Align::CENTER;
2021-05-14 12:12:14 +08:00
spacing = 10;
2022-12-29 04:37:14 +08:00
children = ui_vec![
2021-05-17 02:24:30 +08:00
tab_index(),
functions(window_enabled),
delayed_focus(),
2021-05-14 12:12:14 +08:00
]
}
2021-03-31 06:49:36 +08:00
];
};
2022-07-03 09:39:44 +08:00
// zero_ui::properties::inspector::show_center_points = true;
// zero_ui::properties::inspector::show_bounds = true;
// zero_ui::properties::inspector::show_hit_test = true;
// zero_ui::properties::inspector::show_directional_query = Some(zero_ui::core::units::Orientation2D::Below);
}
})
}
2022-10-21 23:07:26 +08:00
fn alt_scope() -> impl UiNode {
2022-12-28 11:57:28 +08:00
stack! {
direction = StackDirection::left_to_right();
alt_focus_scope = true;
button::vis::extend_style = style_fn!(|_| style! {
border = unset!;
corner_radius = unset!;
});
2022-12-29 04:37:14 +08:00
children = ui_vec![
button("alt", TabIndex::AUTO),
button("scope", TabIndex::AUTO),
];
}
}
2022-10-21 23:07:26 +08:00
fn tab_index() -> impl UiNode {
2022-12-28 11:57:28 +08:00
stack! {
direction = StackDirection::top_to_bottom();
spacing = 5;
2021-05-18 02:14:52 +08:00
focus_shortcut = shortcut!(T);
2022-12-29 04:37:14 +08:00
children = ui_vec![
2021-05-14 12:12:14 +08:00
title("TabIndex (T)"),
button("Button A5", 5),
button("Button A4", 3),
button("Button A3", 2),
button("Button A1", 0),
button("Button A2", 0),
2021-03-31 06:49:36 +08:00
];
}
}
fn functions(window_enabled: ArcVar<bool>) -> impl UiNode {
2022-12-28 11:57:28 +08:00
stack! {
direction = StackDirection::top_to_bottom();
2021-05-14 12:12:14 +08:00
spacing = 5;
2021-05-17 02:24:30 +08:00
focus_shortcut = shortcut!(F);
2022-12-29 04:37:14 +08:00
children = ui_vec![
2021-05-14 12:12:14 +08:00
title("Functions (F)"),
// New Window
2021-05-14 12:12:14 +08:00
button! {
child = text!("New Window");
2023-02-27 03:59:36 +08:00
on_click = hn!(|_| {
WINDOWS.open(async {
2023-02-25 12:16:05 +08:00
let _ = WINDOW.id().set_name("other");
window! {
title = "Other Window";
focus_shortcut = shortcut!(W);
2022-12-28 11:57:28 +08:00
child = stack! {
direction = StackDirection::top_to_bottom();
align = Align::CENTER;
spacing = 5;
2022-12-29 04:37:14 +08:00
children = ui_vec![
title("Other Window (W)"),
button("Button B5", 5),
button("Button B4", 3),
button("Button B3", 2),
button("Button B1", 0),
button("Button B2", 0),
]
};
}
});
2021-06-22 10:00:40 +08:00
});
2021-05-18 07:19:01 +08:00
},
// Detach Button
2021-05-18 02:14:52 +08:00
{
let detach_focused = ArcNode::new_cyclic(|wk| {
2021-05-21 00:01:07 +08:00
let btn = button! {
child = text!("Detach Button");
2022-09-07 05:25:30 +08:00
// focus_on_init = true;
2023-02-27 03:59:36 +08:00
on_click = hn!(|_| {
let wwk = wk.clone();
WINDOWS.open(async move {
window! {
title = "Detached Button";
2022-10-25 11:56:12 +08:00
child_align = Align::CENTER;
child = wwk.upgrade().unwrap().take_when(true);
}
});
2021-06-22 10:00:40 +08:00
});
2021-05-21 00:01:07 +08:00
};
btn.boxed()
2021-05-15 09:45:49 +08:00
});
2022-10-05 11:57:59 +08:00
detach_focused.take_when(true).into_widget()
},
// Disable Window
disable_window(window_enabled.clone()),
2022-02-01 12:18:58 +08:00
// Overlay Scope
button! {
child = text!("Overlay Scope");
2023-02-27 03:59:36 +08:00
on_click = hn!(|_| {
LAYERS.insert(LayerIndex::TOP_MOST, overlay(window_enabled.clone()));
});
},
nested_focusables(),
2021-05-14 12:12:14 +08:00
]
}
}
fn disable_window(window_enabled: ArcVar<bool>) -> impl UiNode {
button! {
child = text!(window_enabled.map(|&e| if e { "Disable Window" } else { "Enabling in 1s..." }.into()));
min_width = 140;
2023-02-27 03:59:36 +08:00
on_click = async_hn!(window_enabled, |_| {
window_enabled.set(false);
task::deadline(1.secs()).await;
window_enabled.set(true);
});
}
}
fn overlay(window_enabled: ArcVar<bool>) -> impl UiNode {
container! {
id = "overlay";
2022-02-01 10:31:20 +08:00
modal = true;
2022-10-25 11:56:12 +08:00
child_align = Align::CENTER;
child = container! {
focus_scope = true;
tab_nav = TabNav::Cycle;
directional_nav = DirectionalNav::Cycle;
background_color = color_scheme_map(colors::BLACK.with_alpha(90.pct()), colors::WHITE.with_alpha(90.pct()));
2022-02-01 12:18:58 +08:00
drop_shadow = (0, 0), 4, colors::BLACK;
padding = 2;
2022-12-28 11:57:28 +08:00
child = stack! {
direction = StackDirection::top_to_bottom();
2022-10-25 11:56:12 +08:00
children_align = Align::RIGHT;
2022-12-29 04:37:14 +08:00
children = ui_vec![
text! {
txt = "Window scope is disabled so the overlay scope is the root scope.";
margin = 15;
},
2022-12-28 11:57:28 +08:00
stack! {
direction = StackDirection::left_to_right();
spacing = 2;
2022-12-29 04:37:14 +08:00
children = ui_vec![
disable_window(window_enabled),
button! {
child = text!("Close");
2023-02-27 03:59:36 +08:00
on_click = hn!(|_| {
LAYERS.remove("overlay");
})
}
]
}
]
}
}
}
}
2021-05-14 12:12:14 +08:00
2022-10-21 23:07:26 +08:00
fn delayed_focus() -> impl UiNode {
2022-12-28 11:57:28 +08:00
stack! {
direction = StackDirection::top_to_bottom();
spacing = 5;
focus_shortcut = shortcut!(D);
2022-12-29 04:37:14 +08:00
children = ui_vec![
title("Delayed 4s (D)"),
2023-02-27 03:59:36 +08:00
delayed_btn("Force Focus", || {
FOCUS.focus(FocusRequest {
target: FocusTarget::Direct(WidgetId::named("target")),
highlight: true,
force_window_focus: true,
window_indicator: None,
});
}),
2023-02-27 03:59:36 +08:00
delayed_btn("Info Indicator", || {
FOCUS.focus(FocusRequest {
target: FocusTarget::Direct(WidgetId::named("target")),
highlight: true,
force_window_focus: false,
window_indicator: Some(FocusIndicator::Info),
});
}),
2023-02-27 03:59:36 +08:00
delayed_btn("Critical Indicator", || {
FOCUS.focus(FocusRequest {
target: FocusTarget::Direct(WidgetId::named("target")),
highlight: true,
force_window_focus: false,
window_indicator: Some(FocusIndicator::Critical),
});
}),
text! {
id = "target";
padding = (7, 15);
corner_radius = 4;
txt = "delayed target";
font_style = FontStyle::Italic;
2022-12-26 02:40:10 +08:00
txt_align = Align::CENTER;
background_color = color_scheme_map(rgb(30, 30, 30), rgb(225, 225, 225));
focusable = true;
2022-10-21 23:07:26 +08:00
when *#is_focused {
txt = "focused";
background_color = color_scheme_map(colors::DARK_GREEN, colors::LIGHT_GREEN);
}
},
]
}
}
2023-02-27 03:59:36 +08:00
fn delayed_btn(content: impl Into<Text>, on_timeout: impl FnMut() + Send + 'static) -> impl UiNode {
use std::sync::Arc;
use zero_ui::core::task::parking_lot::Mutex;
let on_timeout = Arc::new(Mutex::new(Box::new(on_timeout)));
let enabled = var(true);
button! {
child = text!(content.into());
2023-02-27 03:59:36 +08:00
on_click = async_hn!(enabled, on_timeout, |_| {
enabled.set(false);
task::deadline(4.secs()).await;
2023-02-27 03:59:36 +08:00
let mut on_timeout = on_timeout.lock();
on_timeout();
enabled.set(true);
});
enabled;
}
}
fn title(txt: impl IntoVar<Text>) -> impl UiNode {
text! { txt; font_weight = FontWeight::BOLD; align = Align::CENTER; }
2021-05-14 12:12:14 +08:00
}
2022-10-21 23:07:26 +08:00
fn button(content: impl Into<Text>, tab_index: impl Into<TabIndex>) -> impl UiNode {
let content = content.into();
let tab_index = tab_index.into();
button! {
child = text!(content.clone());
tab_index;
2023-02-27 03:59:36 +08:00
on_click = hn!(|_| {
println!("Clicked {content} {tab_index:?}")
2021-06-22 10:00:40 +08:00
});
}
}
2022-10-21 23:07:26 +08:00
fn commands() -> impl UiNode {
use zero_ui::core::focus::commands::*;
let cmds = [
FOCUS_NEXT_CMD,
FOCUS_PREV_CMD,
FOCUS_UP_CMD,
FOCUS_RIGHT_CMD,
FOCUS_DOWN_CMD,
FOCUS_LEFT_CMD,
FOCUS_ALT_CMD,
FOCUS_ENTER_CMD,
FOCUS_EXIT_CMD,
];
2022-12-28 11:57:28 +08:00
stack! {
direction = StackDirection::top_to_bottom();
align = Align::BOTTOM_RIGHT;
padding = 10;
spacing = 8;
2022-10-25 11:56:12 +08:00
children_align = Align::RIGHT;
font_family = FontName::monospace();
txt_color = colors::GRAY;
2022-10-25 11:56:12 +08:00
children = cmds.into_iter().map(|cmd| {
text! {
txt = cmd.name_with_shortcut();
when *#{cmd.is_enabled()} {
txt_color = color_scheme_map(colors::WHITE, colors::BLACK);
}
2022-10-25 11:56:12 +08:00
}.boxed()
}).collect::<Vec<_>>();
}
}
2022-10-09 12:11:51 +08:00
fn trace_focus() {
FOCUS_CHANGED_EVENT
2023-02-25 10:36:48 +08:00
.on_pre_event(app_hn!(|args: &FocusChangedArgs, _| {
2022-10-09 12:11:51 +08:00
if args.is_hightlight_changed() {
println!("highlight: {}", args.highlight);
} else if args.is_widget_move() {
println!("focused {:?} moved", args.new_focus.as_ref().unwrap());
} else if args.is_enabled_change() {
println!("focused {:?} enabled/disabled", args.new_focus.as_ref().unwrap());
} else {
println!("{} -> {}", inspect::focus(&args.prev_focus), inspect::focus(&args.new_focus));
2022-10-09 12:11:51 +08:00
}
}))
.perm();
}
2022-10-21 23:07:26 +08:00
fn nested_focusables() -> impl UiNode {
button! {
child = text!("Nested Focusables");
2023-02-27 03:59:36 +08:00
on_click = hn!(|args: &ClickArgs| {
args.propagation().stop();
WINDOWS.focus_or_open("nested-focusables", async {
2022-06-22 07:27:33 +08:00
window! {
title = "Focus Example - Nested Focusables";
color_scheme = ColorScheme::Dark;
background_color = colors::DIM_GRAY;
2022-07-03 09:39:44 +08:00
// zero_ui::properties::inspector::show_center_points = true;
2022-10-25 11:56:12 +08:00
child_align = Align::CENTER;
2022-12-28 11:57:28 +08:00
child = stack! {
direction = StackDirection::top_to_bottom();
2022-06-22 07:27:33 +08:00
spacing = 10;
2022-12-29 04:37:14 +08:00
children = ui_vec![
2022-06-22 07:27:33 +08:00
nested_focusables_group('a'),
nested_focusables_group('b'),
];
}
2022-06-22 07:27:33 +08:00
}
});
})
}
}
2022-10-21 23:07:26 +08:00
fn nested_focusables_group(g: char) -> impl UiNode {
2022-12-28 11:57:28 +08:00
stack! {
direction = StackDirection::left_to_right();
align = Align::TOP;
spacing = 10;
2022-10-25 11:56:12 +08:00
children = (0..5).map(|n| nested_focusable(g, n, 0).boxed()).collect::<Vec<_>>()
}
}
2022-10-21 23:07:26 +08:00
fn nested_focusable(g: char, column: u8, row: u8) -> impl UiNode {
let nested = text! {
txt = format!("Focusable {column}, {row}");
margin = 5;
};
2022-12-28 11:57:28 +08:00
stack! {
2022-06-22 12:12:02 +08:00
id = formatx!("focusable-{g}-{column}-{row}");
padding = 2;
2022-12-28 11:57:28 +08:00
direction = StackDirection::top_to_bottom();
2022-10-25 11:56:12 +08:00
children = if row == 5 {
2022-12-29 04:37:14 +08:00
ui_vec![nested]
} else {
2022-12-29 04:37:14 +08:00
ui_vec![
nested,
nested_focusable(g, column, row + 1),
]
};
focusable = true;
corner_radius = 5;
2022-06-22 07:56:41 +08:00
border = 1, colors::RED.with_alpha(30.pct());
background_color = colors::RED.with_alpha(20.pct());
2022-10-21 23:07:26 +08:00
when *#is_focused {
background_color = colors::GREEN;
}
2022-10-21 23:07:26 +08:00
when *#is_return_focus {
2022-06-22 07:56:41 +08:00
border = 1, colors::LIME_GREEN;
}
}
}
#[cfg(debug_assertions)]
mod inspect {
use super::*;
use zero_ui::core::focus::WidgetInfoFocusExt;
2022-10-26 06:02:57 +08:00
use zero_ui::core::inspector::WidgetInfoInspectorExt;
pub fn focus(path: &Option<InteractionPath>) -> String {
path.as_ref()
2022-10-26 06:02:57 +08:00
.map(|p| {
let frame = if let Ok(w) = WINDOWS.widget_tree(p.window_id()) {
w
} else {
return format!("<{p}>");
};
2022-07-09 05:59:32 +08:00
let widget = if let Some(w) = frame.get(p.widget_id()) {
w
} else {
return format!("<{p}>");
};
2022-10-29 10:22:58 +08:00
let wgt_mod = if let Some(b) = widget.inspector_info() {
b.builder.widget_mod()
2022-10-26 06:02:57 +08:00
} else {
return format!("<{p}>");
};
if wgt_mod.path.ends_with("button") {
let txt = widget
.inspect_descendant("text")
.expect("expected text in button")
2022-11-09 05:55:11 +08:00
.inspect_property("txt")
.expect("expected txt property in text")
2022-10-26 06:02:57 +08:00
.live_debug(0)
.get();
format!("button({txt})")
} else if wgt_mod.path.ends_with("window") {
let title = widget.inspect_property("title").map(|p| p.live_debug(0).get()).unwrap_or_default();
format!("window({title})")
} else {
let focus_info = widget.into_focus_info(true, true);
if focus_info.is_alt_scope() {
2022-10-26 06:02:57 +08:00
format!("{}(is_alt_scope)", wgt_mod.name())
} else if focus_info.is_scope() {
2022-10-26 06:02:57 +08:00
format!("{}(is_scope)", wgt_mod.name())
} else {
2022-10-26 06:02:57 +08:00
format!("{}({})", wgt_mod.name(), p.widget_id())
}
}
})
.unwrap_or_else(|| "<none>".to_owned())
}
}
#[cfg(not(debug_assertions))]
mod inspect {
use super::*;
2022-11-25 07:11:33 +08:00
pub fn focus(_: &mut Services, path: &Option<InteractionPath>) -> String {
path.as_ref()
.map(|p| format!("{:?}", p.widget_id()))
.unwrap_or_else(|| "<none>".to_owned())
}
}