zng/examples/focus.rs

470 lines
16 KiB
Rust
Raw Normal View History

#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
use zero_ui::core::focus::{FocusChangedEvent, FocusRequest, FocusTarget};
use zero_ui::prelude::*;
2022-02-01 03:18:23 +08:00
use zero_ui::widgets::window::{LayerIndex, WindowLayers};
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("profile-focus.json.gz", &[("example", &"focus")], |_| true);
// zero_ui_view::run_same_process(app_main);
app_main();
// rec.finish();
}
fn app_main() {
App::default().run_window(|ctx| {
ctx.window_id.set_name("main").unwrap();
trace_focus(ctx.events);
let window_enabled = var(true);
window! {
title = "Focus Example";
enabled = window_enabled.clone();
background = commands();
content = v_stack! {
2021-03-31 06:49:36 +08:00
items = widgets![
alt_scope(),
2021-05-14 12:12:14 +08:00
h_stack! {
margin = (50, 0, 0, 0);
align = Align::CENTER;
2021-05-14 12:12:14 +08:00
spacing = 10;
items = widgets![
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
];
};
// zero_ui::widgets::inspector::show_center_points = true;
// zero_ui::widgets::inspector::show_bounds = true;
}
})
}
fn alt_scope() -> impl Widget {
h_stack! {
alt_focus_scope = true;
button::theme::border = 0, BorderStyle::Solid;
button::theme::corner_radius = 0;
2021-03-31 06:49:36 +08:00
items = widgets![
button("alt", TabIndex::AUTO),
button("scope", TabIndex::AUTO),
];
}
}
2021-05-17 02:24:30 +08:00
fn tab_index() -> impl Widget {
v_stack! {
spacing = 5;
2021-05-18 02:14:52 +08:00
focus_shortcut = shortcut!(T);
2021-03-31 06:49:36 +08:00
items = widgets![
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: RcVar<bool>) -> impl Widget {
2021-05-14 12:12:14 +08:00
v_stack! {
spacing = 5;
2021-05-17 02:24:30 +08:00
focus_shortcut = shortcut!(F);
2021-05-14 12:12:14 +08:00
items = widgets![
title("Functions (F)"),
// New Window
2021-05-14 12:12:14 +08:00
button! {
content = text("New Window");
2021-06-22 10:00:40 +08:00
on_click = hn!(|ctx, _| {
ctx.services.windows().open(|ctx| {
let _ = ctx.window_id.set_name("other");
window! {
title = "Other Window";
focus_shortcut = shortcut!(W);
content = v_stack! {
align = Align::CENTER;
spacing = 5;
items = widgets![
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 = RcNode::new_cyclic(|wk| {
2021-05-21 00:01:07 +08:00
let btn = button! {
content = text("Detach Button");
2021-06-22 10:00:40 +08:00
on_click = hn!(|ctx, _| {
let wwk = wk.clone();
ctx.services.windows().open(move |_| {
window! {
title = "Detached Button";
2022-05-21 05:41:56 +08:00
content_align = Align::CENTER;
content = slot(wwk.upgrade().unwrap(), slot::take_on_init());
}
});
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
});
slot(detach_focused, slot::take_on_init())
},
// Disable Window
disable_window(window_enabled.clone()),
2022-02-01 12:18:58 +08:00
// Overlay Scope
button! {
content = text("Overlay Scope");
on_click = hn!(|ctx, _| {
WindowLayers::insert(ctx, LayerIndex::TOP_MOST, overlay(window_enabled.clone()));
});
},
nested_focusables(),
2021-05-14 12:12:14 +08:00
]
}
}
fn disable_window(window_enabled: RcVar<bool>) -> impl Widget {
button! {
content = text(window_enabled.map(|&e| if e { "Disable Window" } else { "Enabling in 1s..." }.into()));
min_width = 140;
on_click = async_hn!(window_enabled, |ctx, _| {
window_enabled.set(&ctx, false);
task::timeout(1.secs()).await;
window_enabled.set(&ctx, true);
});
}
}
fn overlay(window_enabled: RcVar<bool>) -> impl Widget {
container! {
id = "overlay";
2022-02-01 10:31:20 +08:00
modal = true;
2022-05-24 09:57:04 +08:00
content_align = Align::CENTER;
content = container! {
focus_scope = true;
tab_nav = TabNav::Cycle;
directional_nav = DirectionalNav::Cycle;
background_color = rgb(0.05, 0.05, 0.05);
2022-02-01 12:18:58 +08:00
drop_shadow = (0, 0), 4, colors::BLACK;
padding = 2;
content = v_stack! {
items_align = Align::RIGHT;
items = widgets![
text! {
text = "Window scope is disabled so the overlay scope is the root scope.";
margin = 15;
},
h_stack! {
spacing = 2;
items = widgets![
disable_window(window_enabled),
button! {
content = text("Close");
on_click = hn!(|ctx, _| {
WindowLayers::remove(ctx, "overlay");
})
}
]
}
]
}
}
}
}
2021-05-14 12:12:14 +08:00
fn delayed_focus() -> impl Widget {
v_stack! {
spacing = 5;
focus_shortcut = shortcut!(D);
items = widgets![
title("Delayed 4s (D)"),
delayed_btn("Force Focus", |ctx| {
ctx.services.focus().focus(FocusRequest {
target: FocusTarget::Direct(WidgetId::named("target")),
highlight: true,
force_window_focus: true,
window_indicator: None,
});
}),
delayed_btn("Info Indicator", |ctx| {
ctx.services.focus().focus(FocusRequest {
target: FocusTarget::Direct(WidgetId::named("target")),
highlight: true,
force_window_focus: false,
window_indicator: Some(FocusIndicator::Info),
});
}),
delayed_btn("Critical Indicator", |ctx| {
ctx.services.focus().focus(FocusRequest {
target: FocusTarget::Direct(WidgetId::named("target")),
highlight: true,
force_window_focus: false,
window_indicator: Some(FocusIndicator::Critical),
});
}),
text! {
id = "target";
padding = zero_ui::widgets::button::theme::PaddingVar;
corner_radius = zero_ui::widgets::button::theme::CornerRadiusVar;
text = "delayed target";
font_style = FontStyle::Italic;
2022-06-14 04:35:00 +08:00
text_align = TextAlign::CENTER_MIDDLE;
background_color = rgb(30, 30, 30);
focusable = true;
when self.is_focused {
text = "focused";
background_color = colors::DARK_GREEN;
}
},
]
}
}
fn delayed_btn(content: impl Into<Text>, on_timeout: impl FnMut(&mut WidgetContext) + 'static) -> impl Widget {
let on_timeout = std::rc::Rc::new(std::cell::RefCell::new(Box::new(on_timeout)));
let enabled = var(true);
button! {
content = text(content.into());
on_click = async_hn!(enabled, on_timeout, |ctx, _| {
enabled.set(&ctx, false);
task::timeout(4.secs()).await;
ctx.with(|ctx| {
let mut on_timeout = on_timeout.borrow_mut();
on_timeout(ctx);
});
enabled.set(&ctx, true);
});
enabled;
}
}
2021-05-14 12:12:14 +08:00
fn title(text: impl IntoVar<Text>) -> impl Widget {
text! { text; font_weight = FontWeight::BOLD; align = Align::CENTER; }
2021-05-14 12:12:14 +08:00
}
fn button(content: impl Into<Text>, tab_index: impl Into<TabIndex>) -> impl Widget {
let content = content.into();
let tab_index = tab_index.into();
button! {
content = text(content.clone());
tab_index;
2021-06-22 10:00:40 +08:00
on_click = hn!(|_, _| {
println!("Clicked {content} {tab_index:?}")
2021-06-22 10:00:40 +08:00
});
}
}
fn commands() -> impl Widget {
use zero_ui::core::focus::commands::*;
let cmds = [
FocusNextCommand.as_any(),
FocusPrevCommand.as_any(),
FocusUpCommand.as_any(),
FocusRightCommand.as_any(),
FocusDownCommand.as_any(),
FocusLeftCommand.as_any(),
FocusAltCommand.as_any(),
EscapeAltCommand.as_any(),
FocusChildCommand.as_any(),
FocusParentCommand.as_any(),
];
v_stack! {
align = Align::BOTTOM_RIGHT;
padding = 10;
spacing = 8;
items_align = Align::RIGHT;
font_family = FontName::monospace();
text_color = colors::GRAY;
items = cmds.into_iter().map(|cmd| {
text! {
text = cmd.name_with_shortcut();
when *#{cmd.enabled()} {
color = colors::WHITE;
}
}.boxed_wgt()
}).collect::<WidgetVec>();
}
}
2021-06-04 05:07:54 +08:00
fn trace_focus(events: &mut Events) {
events
2021-06-25 07:20:10 +08:00
.on_pre_event(
FocusChangedEvent,
app_hn!(|ctx, args: &FocusChangedArgs, _| {
if args.is_hightlight_changed() {
println!("highlight: {}", args.highlight);
} else if args.is_widget_move() {
println!("focused {:?} moved", args.new_focus.as_ref().unwrap());
2022-06-05 12:25:21 +08:00
} else if args.is_enabled_change() {
println!("focused {:?} enabled/disabled", args.new_focus.as_ref().unwrap());
2021-06-25 07:20:10 +08:00
} else {
println!(
"{} -> {}",
inspect::focus(&args.prev_focus, ctx.services),
inspect::focus(&args.new_focus, ctx.services)
);
}
}),
)
.perm();
}
fn nested_focusables() -> impl Widget {
let opening = var(false);
button! {
content = text("Nested Focusables");
enabled = opening.map(|&o|!o);
on_click = hn!(|ctx, args: &ClickArgs| {
args.propagation().stop();
let windows = ctx.services.windows();
if windows.focus("nested-focusables".into()).is_err() {
opening.set(ctx.vars, true);
windows.open(clone_move!(opening, |ctx| {
// ctx.window_id.set_name("nested-focusables").unwrap();
window! {
title = "Focus Example - Nested Focusables";
on_open = hn!(|ctx, _| opening.set(ctx, false));
// zero_ui::widgets::inspector::show_center_points = true;
content = v_stack! {
margin = (30, 0, 0, 0);
spacing = 10;
items = widgets![
nested_focusables_group('a'),
nested_focusables_group('b'),
];
};
}
}));
}
})
}
}
fn nested_focusables_group(g: char) -> impl Widget {
h_stack! {
align = Align::TOP;
spacing = 10;
items = (0..5).map(|n| nested_focusable(g, n, 0).boxed_wgt()).collect::<WidgetVec>()
}
}
fn nested_focusable(g: char, column: u8, row: u8) -> impl Widget {
let nested = text! {
text = format!("Focusable {column}, {row}");
margin = 5;
};
v_stack! {
id = formatx!("focusable{g}-{column}-{row}");
padding = 2;
items = if row == 5 {
widget_vec![nested]
} else {
widget_vec![
nested,
nested_focusable(g, column, row + 1),
]
};
focusable = true;
corner_radius = 5;
background_color = colors::RED.with_alpha(20.pct());
when self.is_focused {
background_color = colors::GREEN;
}
}
}
#[cfg(debug_assertions)]
mod inspect {
use super::*;
use zero_ui::core::focus::WidgetInfoFocusExt;
2022-02-26 05:09:47 +08:00
use zero_ui::core::inspector::WidgetInspectorInfo;
pub fn focus(path: &Option<InteractionPath>, services: &mut Services) -> String {
path.as_ref()
.map(|p| {
2021-11-28 02:24:22 +08:00
let frame = if let Ok(w) = services.windows().widget_tree(p.window_id()) {
w
} else {
return format!("<{p}>");
};
let widget = if let Some(w) = frame.get(p) {
w
} else {
return format!("<{p}>");
};
let info = widget.instance().expect("expected debug info").borrow();
if info.widget_name == "button" {
format!(
"button({})",
2022-04-06 01:26:44 +08:00
widget
.descendant_instance("text")
.expect("expected text in button")
.property("text")
.expect("expected text property")
.borrow()
.arg(0)
.value
.debug
)
} else if info.widget_name == "window" {
let title = widget
.properties()
.iter()
.find(|p| p.borrow().property_name == "title")
.map(|p| p.borrow().args[0].value.debug.clone())
.unwrap_or_default();
format!("window({title})")
} else {
let focus_info = widget.as_focus_info(true);
if focus_info.is_alt_scope() {
format!("{}(is_alt_scope)", info.widget_name)
} else if focus_info.is_scope() {
format!("{}(is_scope)", info.widget_name)
} else {
format!("{}({})", info.widget_name, p.widget_id())
}
}
})
.unwrap_or_else(|| "<none>".to_owned())
}
}
#[cfg(not(debug_assertions))]
mod inspect {
use super::*;
2021-05-21 00:01:07 +08:00
pub fn focus(path: &Option<WidgetPath>, _: &mut Services) -> String {
path.as_ref()
.map(|p| format!("{:?}", p.widget_id()))
.unwrap_or_else(|| "<none>".to_owned())
}
}