use super::controls;
use super::views::MainWindow;
use super::{
ClassicDesktopStyleApplicationLifetimeTopLevelGroup, DevToolsOptions, IDevToolsTopLevelGroup, KeyGestureExtesions,
SingleViewTopLevelGroup,
};
use ferroui_base::input::raw::{IRawInputEventArgs, RawKeyEventArgs, RawKeyEventType};
use ferroui_base::input::{IInputManager, InputElement, KeyEventArgs, KeyGesture};
use ferroui_base::interactivity::RoutingStrategies;
use ferroui_base::reactive::{CompositeDisposable, Disposable, IDisposable, ObservableExt, SerialDisposable};
use ferroui_base::{BoxedValue, FerroObject, Ref, Visual};
use ferroui_controls::primitives::PopupRoot;
use ferroui_controls::{Control, Design, TopLevel, Window};
use std::cell::RefCell;
use std::rc::Rc;
ferroui_base::ferro_thread_local! {
static S_OPEN: RefCell<Vec<(Rc<dyn IDevToolsTopLevelGroup>, Ref<MainWindow>)>> = RefCell::new(Vec::new());
}
pub struct DevTools;
impl DevTools {
pub fn attach_with_gesture(root: &TopLevel, gesture: KeyGesture) -> Rc<dyn IDisposable> {
Self::attach(root, DevToolsOptions::new().with_gesture(gesture))
}
pub fn attach(root: &TopLevel, options: Rc<DevToolsOptions>) -> Rc<dyn IDisposable> {
let weak_root = root.to_ref().downgrade();
root.add_disposable_handler_with(
InputElement::key_down_event(),
move |_, e: &KeyEventArgs| {
if options.gesture().matches(Some(e)) {
if let Some(root) = weak_root.upgrade() {
Self::open(&root, &options);
}
}
},
RoutingStrategies::TUNNEL,
false,
)
}
pub fn open(root: &TopLevel, options: &Rc<DevToolsOptions>) -> Rc<dyn IDisposable> {
Self::open_core(
Rc::new(SingleViewTopLevelGroup::new(root.to_ref())),
options,
root.to_ref().cast::<Window>(),
None,
)
}
pub fn open_group(group: Rc<dyn IDevToolsTopLevelGroup>, options: &Rc<DevToolsOptions>) -> Rc<dyn IDisposable> {
Self::open_core(group, options, None, None)
}
pub fn attach_application(
application: &ferroui_controls::Application,
options: Rc<DevToolsOptions>,
) -> Rc<dyn IDisposable> {
let opened_disposable = Rc::new(SerialDisposable::new());
let result = Rc::new(CompositeDisposable::with_capacity(2));
result.add(opened_disposable.clone());
if !Design::is_design_mode() {
let life_time = application
.application_lifetime()
.filter(|lifetime| lifetime.as_classic_desktop_style_application_lifetime().is_some());
let Some(life_time) = life_time else {
panic!(
"DevTools can only attach to applications that support IClassicDesktopStyleApplicationLifetime. (Parameter 'application')"
);
};
if let Some(input_manager) = application.input_manager() {
let input_manager: Rc<dyn IInputManager> = input_manager;
let weak_application = application.to_ref().downgrade();
result.add(input_manager.pre_process().subscribe_fn(move |e: Rc<dyn IRawInputEventArgs>| {
let owner = life_time
.as_classic_desktop_style_application_lifetime()
.and_then(|lifetime| lifetime.main_window());
if let Some(key_event_args) = e.downcast_ref::<RawKeyEventArgs>() {
if key_event_args.type_() == RawKeyEventType::KeyUp
&& options.gesture().matches_raw(key_event_args)
{
opened_disposable.set(Some(Self::open_core(
Rc::new(ClassicDesktopStyleApplicationLifetimeTopLevelGroup::new(life_time.clone())),
&options,
owner,
weak_application.upgrade(),
)));
e.set_handled(true);
}
}
}));
}
}
result
}
fn open_core(
top_level_group: Rc<dyn IDevToolsTopLevelGroup>,
options: &Rc<DevToolsOptions>,
owner: Option<Ref<Window>>,
app: Option<Ref<ferroui_controls::Application>>,
) -> Rc<dyn IDisposable> {
let focused_control = owner
.as_ref()
.and_then(|owner| owner.focus_manager().get_focused_element())
.and_then(|element| element.cast::<Control>());
let group = top_level_group.as_any();
let root: Ref<FerroObject> = if group.is::<ClassicDesktopStyleApplicationLifetimeTopLevelGroup>() {
let application = app
.or_else(ferroui_controls::Application::current)
.expect("an application is running when DevTools opens for its windows");
controls::Application::new(top_level_group.clone(), application).upcast()
} else if group.is::<SingleViewTopLevelGroup>() {
top_level_group.items().into_iter().next().expect("a single view group has its top level").upcast()
} else {
controls::TopLevelGroup::new(top_level_group.clone()).upcast()
};
let main_window = S_OPEN.with(|open| {
open.borrow().iter().find(|(group, _)| **group == *top_level_group).map(|(_, window)| window.clone())
});
if let Some(main_window) = main_window {
main_window.activate();
main_window.selected_control(focused_control);
return Disposable::empty();
}
let items = top_level_group.items();
if items.len() == 1 && top_level_group.observable_items().is_none() {
let single_top_level = items[0].clone();
let main_window = S_OPEN.with(|open| {
open.borrow()
.iter()
.find(|(group, _)| group.items().contains(&single_top_level))
.map(|(_, window)| window.clone())
});
if let Some(main_window) = main_window {
main_window.activate();
main_window.selected_control(focused_control);
return Disposable::empty();
}
}
let window = MainWindow::new();
window.set_root(Some(root));
window.set_width(options.size().width);
window.set_height(options.size().height);
window.set_tag(Some(Rc::new(top_level_group.clone()) as BoxedValue));
window.set_options(options);
window.selected_control(focused_control);
let closed_subscription: Rc<RefCell<Option<Rc<dyn IDisposable>>>> = Rc::new(RefCell::new(None));
let handler_subscription = closed_subscription.clone();
let weak_window = window.downgrade();
let closed = window.closed(move || {
if let Some(window) = weak_window.upgrade() {
Self::dev_tools_closed(&window, &handler_subscription);
}
});
*closed_subscription.borrow_mut() = Some(closed);
S_OPEN.with(|open| open.borrow_mut().push((top_level_group, window.clone())));
match owner.filter(|_| options.show_as_child_window()) {
Some(owner) => window.show_with_owner(&owner),
None => window.show(),
}
Disposable::create(move || window.close())
}
fn dev_tools_closed(window: &Ref<MainWindow>, subscription: &RefCell<Option<Rc<dyn IDisposable>>>) {
let own = subscription.borrow_mut().take();
if let Some(own) = own {
own.dispose();
}
let group = window.tag().and_then(|tag| tag.downcast_ref::<Rc<dyn IDevToolsTopLevelGroup>>().cloned());
if let Some(group) = group {
S_OPEN.with(|open| open.borrow_mut().retain(|(open_group, _)| **open_group != *group));
}
}
pub fn does_belong_to_dev_tool(v: &Visual) -> bool {
let mut top_level = TopLevel::get_top_level(Some(v));
while let Some(current) = top_level {
if current.is::<MainWindow>() {
break;
}
match current.cast::<PopupRoot>() {
Some(pr) => top_level = pr.parent_top_level(),
None => return false,
}
}
true
}
}
pub trait DevToolsVisualExtensions {
fn does_belong_to_dev_tool(&self) -> bool;
}
impl DevToolsVisualExtensions for Visual {
fn does_belong_to_dev_tool(&self) -> bool {
DevTools::does_belong_to_dev_tool(self)
}
}