Fixed bugs when running two sequential headless apps in the same thread.
This commit is contained in:
parent
cdccd899a7
commit
4173c13f66
|
@ -5,38 +5,51 @@ use zero_ui::core::window::{HeadlessAppOpenWindowExt, WindowId};
|
|||
use zero_ui::prelude::*;
|
||||
|
||||
#[test]
|
||||
pub fn window_tab_cycle() {
|
||||
pub fn window_tab_cycle_index_auto() {
|
||||
// default window! cycles TAB navigation
|
||||
t(|_| TabIndex::AUTO);
|
||||
t(TabIndex);
|
||||
t(|i| TabIndex(TabIndex::AUTO.0 - i - 1));
|
||||
|
||||
let buttons = widgets![
|
||||
button! { content = text("Button 0") },
|
||||
button! { content = text("Button 1") },
|
||||
button! { content = text("Button 2") },
|
||||
];
|
||||
let ids: Vec<_> = (0..3).map(|i| buttons.widget_id(i)).collect();
|
||||
fn t(make_index: impl FnMut(u32) -> TabIndex) {
|
||||
// all TAB navigation must respect the `tab_index` value
|
||||
// that by default is AUTO, but can be not in the same order
|
||||
// as the widgets are declared.
|
||||
let tab_ids: Vec<_> = (0..3).map(make_index).collect();
|
||||
|
||||
let mut app = TestApp::new(v_stack(buttons));
|
||||
let buttons = widgets![
|
||||
button! { content = text("Button 0"); tab_index = tab_ids[0] },
|
||||
button! { content = text("Button 1"); tab_index = tab_ids[1] },
|
||||
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();
|
||||
ids.sort_by_key(|(_, ti)| *ti);
|
||||
let ids: Vec<_> = ids.into_iter().map(|(id, _)| id).collect();
|
||||
|
||||
// advance normally..
|
||||
assert_eq!(Some(ids[0]), app.focused());
|
||||
app.press_tab();
|
||||
assert_eq!(Some(ids[1]), app.focused());
|
||||
app.press_tab();
|
||||
assert_eq!(Some(ids[2]), app.focused());
|
||||
// then cycles back.
|
||||
app.press_tab();
|
||||
assert_eq!(Some(ids[0]), app.focused());
|
||||
let mut app = TestApp::new(v_stack(buttons));
|
||||
|
||||
// same backwards.
|
||||
app.press_shift_tab();
|
||||
assert_eq!(Some(ids[2]), app.focused());
|
||||
app.press_shift_tab();
|
||||
assert_eq!(Some(ids[1]), app.focused());
|
||||
app.press_shift_tab();
|
||||
assert_eq!(Some(ids[0]), app.focused());
|
||||
// cycles back.
|
||||
app.press_shift_tab();
|
||||
assert_eq!(Some(ids[2]), app.focused());
|
||||
// advance normally..
|
||||
assert_eq!(Some(ids[0]), app.focused());
|
||||
app.press_tab();
|
||||
assert_eq!(Some(ids[1]), app.focused());
|
||||
app.press_tab();
|
||||
assert_eq!(Some(ids[2]), app.focused());
|
||||
// then cycles back.
|
||||
app.press_tab();
|
||||
assert_eq!(Some(ids[0]), app.focused());
|
||||
|
||||
// same backwards.
|
||||
app.press_shift_tab();
|
||||
assert_eq!(Some(ids[2]), app.focused());
|
||||
app.press_shift_tab();
|
||||
assert_eq!(Some(ids[1]), app.focused());
|
||||
app.press_shift_tab();
|
||||
assert_eq!(Some(ids[0]), app.focused());
|
||||
// cycles back.
|
||||
app.press_shift_tab();
|
||||
assert_eq!(Some(ids[2]), app.focused());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -241,7 +241,6 @@ state_key! {
|
|||
/// The index is zero based, zero first.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
|
||||
pub struct TabIndex(pub u32);
|
||||
|
||||
impl TabIndex {
|
||||
/// Widget is skipped during tab navigation.
|
||||
///
|
||||
|
|
|
@ -75,21 +75,24 @@ mod profiler_impl {
|
|||
}
|
||||
|
||||
fn register_thread(&mut self) {
|
||||
let id = ThreadId(self.threads.len());
|
||||
let name = match thread::current().name() {
|
||||
Some(s) => s.to_string(),
|
||||
None => format!("<unnamed-{}>", id.0),
|
||||
};
|
||||
let registered_name = THREAD_PROFILER.with(|profiler| {
|
||||
if profiler.borrow().is_none() {
|
||||
let id = ThreadId(self.threads.len());
|
||||
|
||||
self.threads.push(ThreadInfo { name });
|
||||
let thread_profiler = ThreadProfiler { id, tx: self.tx.clone() };
|
||||
*profiler.borrow_mut() = Some(thread_profiler);
|
||||
|
||||
THREAD_PROFILER.with(|profiler| {
|
||||
assert!(profiler.borrow().is_none());
|
||||
|
||||
let thread_profiler = ThreadProfiler { id, tx: self.tx.clone() };
|
||||
|
||||
*profiler.borrow_mut() = Some(thread_profiler);
|
||||
Some(match thread::current().name() {
|
||||
Some(s) => s.to_string(),
|
||||
None => format!("<unnamed-{}>", id.0),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
if let Some(name) = registered_name {
|
||||
self.threads.push(ThreadInfo { name });
|
||||
}
|
||||
}
|
||||
|
||||
fn write_profile(&self, filename: &str, ignore_0ms: bool) {
|
||||
|
@ -189,6 +192,8 @@ mod profiler_impl {
|
|||
}
|
||||
|
||||
/// Registers the current thread with the global profiler.
|
||||
///
|
||||
/// Does nothing if the thread is already registered.
|
||||
#[inline]
|
||||
pub fn register_thread_with_profiler() {
|
||||
GLOBAL_PROFILER.lock().unwrap().register_thread();
|
||||
|
|
|
@ -33,7 +33,11 @@ impl AppServicesInit {
|
|||
let mut service = Box::new(service);
|
||||
let prev = S::thread_local_entry().init(service.as_mut() as _);
|
||||
if prev.is_null() {
|
||||
self.m.services.push(service);
|
||||
let deiniter = Box::new(|| S::thread_local_entry().deinit());
|
||||
self.m.services.push(ServiceEntry {
|
||||
_instance: service,
|
||||
deiniter,
|
||||
});
|
||||
Ok(())
|
||||
} else {
|
||||
S::thread_local_entry().init(prev);
|
||||
|
@ -50,7 +54,7 @@ impl AppServicesInit {
|
|||
/// Panics if another instance of the service is already registered.
|
||||
#[track_caller]
|
||||
pub fn register<S: AppService + Sized>(&mut self, service: S) {
|
||||
self.try_register(service).unwrap()
|
||||
self.try_register(service).expect("service already registered")
|
||||
}
|
||||
|
||||
/// Reference the [`AppServices`].
|
||||
|
@ -59,9 +63,19 @@ impl AppServicesInit {
|
|||
}
|
||||
}
|
||||
|
||||
struct ServiceEntry {
|
||||
_instance: Box<dyn AppService>,
|
||||
deiniter: Box<dyn Fn()>,
|
||||
}
|
||||
impl Drop for ServiceEntry {
|
||||
fn drop(&mut self) {
|
||||
(self.deiniter)();
|
||||
}
|
||||
}
|
||||
|
||||
/// Access to application services.
|
||||
pub struct AppServices {
|
||||
services: Vec<Box<dyn AppService>>,
|
||||
services: Vec<ServiceEntry>,
|
||||
}
|
||||
impl AppServices {
|
||||
/// Gets a service reference if the service is registered in the application.
|
||||
|
@ -83,7 +97,7 @@ impl AppServices {
|
|||
/// If the service is not registered in the application.
|
||||
pub fn req<S: AppService>(&mut self) -> &mut S {
|
||||
self.get::<S>()
|
||||
.unwrap_or_else(|| panic!("service `{}` is required", type_name::<S>()))
|
||||
.unwrap_or_else(|| panic!("app service `{}` is required", type_name::<S>()))
|
||||
}
|
||||
|
||||
/// Gets multiple service references if all services are registered in the application.
|
||||
|
@ -146,7 +160,7 @@ impl WindowServicesInit {
|
|||
let _ = S::thread_local_entry().init(service_ptr);
|
||||
});
|
||||
let unloader = Box::new(|| {
|
||||
let _ = S::thread_local_entry().init(ptr::null_mut());
|
||||
let _ = S::thread_local_entry().deinit();
|
||||
});
|
||||
(service, loader, unloader)
|
||||
}));
|
||||
|
@ -166,7 +180,7 @@ impl WindowServicesInit {
|
|||
/// Panics if the window service type is already registered.
|
||||
#[track_caller]
|
||||
pub fn register<S: WindowService>(&mut self, new: impl Fn(&WindowContext) -> S + 'static) {
|
||||
self.try_register(new).unwrap()
|
||||
self.try_register(new).expect("window service type already registered")
|
||||
}
|
||||
|
||||
/// Schedules a visitor that is called once for each open window.
|
||||
|
@ -451,6 +465,10 @@ macro_rules! service_types {
|
|||
self.local.with(move |l| l.value.replace(service))
|
||||
}
|
||||
|
||||
fn deinit(&self) {
|
||||
self.init(ptr::null_mut());
|
||||
}
|
||||
|
||||
fn get(&self) -> *mut S {
|
||||
self.local.with(|l| l.value.get())
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue