ferroui_diagnostics/diagnostics/views/
main_window.rs1use crate::diagnostics::controls::Application;
5use crate::diagnostics::view_models::MainViewModel;
6use crate::diagnostics::{DevToolsOptions, HotKeyConfiguration};
7use ferroui_base::controls::ResourceKey;
8use ferroui_base::input::raw::{RawKeyEventArgs, RawKeyEventType, RawPointerEventArgs};
9use ferroui_base::input::{ICloseable, IInputRoot, InputElement, InputElementImpl, InputManager, Key, KeyGesture, KeyModifiers};
10use ferroui_base::interactivity::InteractiveImpl;
11use ferroui_base::layout::LayoutableImpl;
12use ferroui_base::metadata::{from_markup_value, into_markup_value};
13use ferroui_base::reactive::{IDisposable, ObservableExt};
14use ferroui_base::styling::{ControlTheme, StyleHostRef};
15use ferroui_base::utilities::CancelEventArgs;
16use ferroui_base::{
17 ferro_class, ferro_class_info, ferro_impl_classes, instantiate, FerroObject, FerroObjectImpl, PixelPoint, Ref,
18 StyledElementImpl, Visual, VisualImpl,
19};
20use ferroui_controls::diagnostics::{IPopupHostProvider, ToolTipDiagnostics};
21use ferroui_controls::platform::PlatformManager;
22use ferroui_controls::primitives::{
23 AdornerLayer, FlyoutBase, Popup, PopupFlyoutBase, PopupRoot, TemplatedControlImpl,
24};
25use ferroui_controls::{
26 Button, ContentControlImpl, Control, ControlImpl, TopLevel, TopLevelImpl, TopLevelImplExt, Window,
27 WindowBaseImpl, WindowImpl, WindowState,
28};
29use std::cell::{Cell, RefCell};
30use std::rc::Rc;
31
32fn popup_root_of(popup_provider: Option<Rc<dyn IPopupHostProvider>>) -> Option<Ref<PopupRoot>> {
35 popup_provider.and_then(|popup_provider| popup_provider.popup_host()).and_then(|popup_host| popup_host.as_popup_root())
36}
37
38fn flyout_popup_host_provider(flyout: Option<Ref<FlyoutBase>>) -> Option<Rc<dyn IPopupHostProvider>> {
41 flyout.and_then(|flyout| flyout.cast::<PopupFlyoutBase>()).map(|flyout| flyout.to_popup_host_provider())
42}
43
44#[repr(C)]
48pub struct MainWindow {
49 base: Window,
50 input_subscription: RefCell<Option<Rc<dyn IDisposable>>>,
51 frozen_popup_states: RefCell<Vec<(Ref<Popup>, Rc<dyn IDisposable>)>>,
53 root: RefCell<Option<Ref<FerroObject>>>,
54 root_closed_subscription: RefCell<Option<Rc<dyn IDisposable>>>,
56 last_pointer_position: Cell<PixelPoint>,
57 hot_keys: RefCell<Option<Rc<HotKeyConfiguration>>>,
58}
59
60ferro_class!(MainWindow: Window);
61ferro_impl_classes!(
62 MainWindow: FerroObjectImpl,
63 VisualImpl,
64 LayoutableImpl,
65 InteractiveImpl,
66 InputElementImpl,
67 ControlImpl,
68 TemplatedControlImpl,
69 ContentControlImpl,
70 WindowBaseImpl,
71 WindowImpl
72);
73ferro_class_info!(MainWindow { new: MainWindow::new });
74
75impl StyledElementImpl for MainWindow {
76 fn styling_parent(_this: &Self) -> Option<StyleHostRef> {
79 None
80 }
81}
82
83impl TopLevelImpl for MainWindow {
84 fn on_closed(this: &Self) {
85 Self::parent_on_closed(this);
86 let input_subscription = this.input_subscription.borrow().clone();
87 if let Some(input_subscription) = input_subscription {
88 input_subscription.dispose();
89 }
90
91 let frozen_popup_states: Vec<_> = this.frozen_popup_states.borrow_mut().drain(..).collect();
92 for (_, closing_subscription) in frozen_popup_states {
93 closing_subscription.dispose();
94 }
95
96 let root = this.root.borrow().clone();
97 if root.as_ref().and_then(Self::closeable_of).is_some() {
98 let root_closed_subscription = this.root_closed_subscription.borrow_mut().take();
99 if let Some(root_closed_subscription) = root_closed_subscription {
100 root_closed_subscription.dispose();
101 }
102 *this.root.borrow_mut() = None;
103 }
104
105 if let Some(view_model) = this.view_model() {
106 view_model.dispose();
107 }
108 }
109}
110
111impl MainWindow {
112 pub const DOCUMENT_URI: &'static str = "ferres://FerroUI.Diagnostics/diagnostics/views/MainWindow.xaml";
114
115 pub fn construct() -> Self {
116 Self {
117 base: Window::construct(PlatformManager::create_window()),
118 input_subscription: RefCell::new(None),
119 frozen_popup_states: RefCell::new(Vec::new()),
120 root: RefCell::new(None),
121 root_closed_subscription: RefCell::new(None),
122 last_pointer_position: Cell::new(PixelPoint::default()),
123 hot_keys: RefCell::new(None),
124 }
125 }
126
127 pub fn new() -> Ref<Self> {
138 crate::register_types();
139 let this = instantiate(Self::construct());
140 Self::initialize_component(&this);
141
142 let platform = if cfg!(target_os = "macos") {
145 "macos"
146 } else if cfg!(windows) {
147 "windows"
148 } else {
149 "other"
150 };
151 this.classes().add(platform);
152
153 if this.theme().is_none() {
158 let window_theme = this
159 .find_resource(&ResourceKey::Type(Window::TYPE))
160 .and_then(|value| value.downcast_ref::<Ref<ControlTheme>>().cloned());
161 if let Some(window_theme) = window_theme {
162 this.set_theme(window_theme);
163 }
164 }
165
166 let weak = this.downgrade();
167 let input_subscription = InputManager::instance().map(|input_manager| {
168 input_manager.process().subscribe_fn(move |x| {
169 let Some(this) = weak.upgrade() else {
170 return;
171 };
172 if let Some(pointer_event_args) = x.downcast_ref::<RawPointerEventArgs>() {
173 if let Some(root) = x.root().try_root_element() {
174 this.last_pointer_position.set(root.point_to_screen(pointer_event_args.position()));
175 }
176 } else if let Some(key_event_args) = x.downcast_ref::<RawKeyEventArgs>() {
177 if key_event_args.type_() == RawKeyEventType::KeyDown {
178 this.raw_key_down(key_event_args);
179 }
180 }
181 })
182 });
183 *this.input_subscription.borrow_mut() = input_subscription;
184
185 let opened_subscription: Rc<RefCell<Option<Rc<dyn IDisposable>>>> = Rc::new(RefCell::new(None));
187 let handler_subscription = opened_subscription.clone();
188 let weak = this.downgrade();
189 let subscription = this.opened(move || {
190 let own = handler_subscription.borrow_mut().take();
191 if let Some(own) = own {
192 own.dispose();
193 }
194 let Some(this) = weak.upgrade() else {
195 return;
196 };
197 if let Some(index) = this.view_model().and_then(|view_model| view_model.startup_screen_index()) {
198 let screens = this.screens();
199 if index > -1 && index < screens.screen_count() {
200 let screen = screens.all()[index as usize].clone();
201 this.set_position(screen.bounds().top_left());
202 this.set_window_state(WindowState::Maximized);
203 }
204 }
205 });
206 *opened_subscription.borrow_mut() = Some(subscription);
207
208 this
209 }
210
211 fn initialize_component(this: &Ref<Self>) {
213 if let Err(error) = crate::compiled_diagnostics_views_main_window::populate(None, this) {
214 panic!("{}: {error}", Self::DOCUMENT_URI);
215 }
216 }
217
218 fn view_model(&self) -> Option<Rc<MainViewModel>> {
220 from_markup_value::<Rc<MainViewModel>>(&self.data_context())
221 }
222
223 fn closeable_of(root: &Ref<FerroObject>) -> Option<Rc<dyn ICloseable>> {
226 if let Some(top_level) = root.cast::<TopLevel>() {
227 return Some(top_level.as_closeable());
228 }
229 if let Some(application) = root.cast::<Application>() {
230 return Some(Application::as_closeable(application));
231 }
232 None
233 }
234
235 pub fn root(&self) -> Option<Ref<FerroObject>> {
238 self.root.borrow().clone()
239 }
240
241 pub fn set_root(&self, value: Option<Ref<FerroObject>>) {
244 if *self.root.borrow() != value {
245 let old_closable = self.root.borrow().as_ref().and_then(Self::closeable_of);
246 if old_closable.is_some() {
247 let root_closed_subscription = self.root_closed_subscription.borrow_mut().take();
248 if let Some(root_closed_subscription) = root_closed_subscription {
249 root_closed_subscription.dispose();
250 }
251 }
252
253 *self.root.borrow_mut() = value.clone();
254
255 match value.as_ref().and_then(|root| Self::closeable_of(root).map(|closeable| (root.clone(), closeable))) {
256 Some((root, new_closable)) => {
257 let weak = self.to_ref().downgrade();
258 let subscription = new_closable.closed(Rc::new(move || {
259 if let Some(this) = weak.upgrade() {
260 this.root_closed();
261 }
262 }));
263 *self.root_closed_subscription.borrow_mut() = Some(subscription);
264 self.set_data_context(into_markup_value(MainViewModel::new(root)));
265 }
266 None => self.set_data_context(None),
267 }
268 }
269 }
270
271 fn get_hovered_control(&self, top_level: &TopLevel) -> Option<Ref<Control>> {
274 let point = top_level.point_to_client(self.last_pointer_position.get());
275
276 top_level
277 .get_visuals_at_filtered(point, &|x: &Visual| {
278 if x.downcast_ref::<AdornerLayer>().is_some() || !x.is_visible() {
279 return false;
280 }
281
282 x.downcast_ref::<InputElement>().is_none_or(|ie| ie.is_hit_test_visible())
283 })
284 .into_iter()
285 .next()
286 .and_then(|visual| visual.cast::<Control>())
288 }
289
290 fn get_popup_roots(root: &TopLevel) -> Vec<Ref<PopupRoot>> {
294 let mut popup_roots = Vec::new();
295
296 fn process_property(popup_roots: &mut Vec<Ref<PopupRoot>>, popup_provider: Option<Rc<dyn IPopupHostProvider>>) {
297 if let Some(popup_root) = popup_root_of(popup_provider) {
298 popup_roots.push(popup_root);
299 }
300 }
301
302 for control in root.get_visual_descendants().filter_map(|visual| visual.cast::<Control>()) {
303 if let Some(p) = control.cast::<Popup>() {
304 if let Some(popup_root) = p.host().and_then(|host| host.as_popup_root()) {
305 popup_roots.push(popup_root);
306 }
307 }
308
309 process_property(
310 &mut popup_roots,
311 flyout_popup_host_provider(control.get_value(Control::context_flyout_property())),
312 );
313 process_property(
314 &mut popup_roots,
315 control.get_value(Control::context_menu_property()).map(|context_menu| context_menu.to_popup_host_provider()),
316 );
317 process_property(
318 &mut popup_roots,
319 flyout_popup_host_provider(control.get_value(FlyoutBase::attached_flyout_property())),
320 );
321 process_property(
322 &mut popup_roots,
323 control.get_value(ToolTipDiagnostics::tool_tip_property()).map(|tool_tip| tool_tip.to_popup_host_provider()),
324 );
325 process_property(&mut popup_roots, flyout_popup_host_provider(control.get_value(Button::flyout_property())));
326 }
327
328 popup_roots
329 }
330
331 fn raw_key_down(&self, e: &RawKeyEventArgs) {
332 let hot_keys = self.hot_keys.borrow().clone();
333 let Some(hot_keys) = hot_keys else {
334 return;
335 };
336 let Some(vm) = self.view_model() else {
337 return;
338 };
339 let Some(mut root) = vm.pointer_over_root().and_then(|pointer_over_root| top_level_of_root(&*pointer_over_root))
341 else {
342 return;
343 };
344
345 if let Some(parent_top_level) = root.cast::<PopupRoot>().and_then(|pr| pr.parent_top_level()) {
346 root = parent_top_level;
347 }
348
349 let modifiers = Self::merge_modifiers(e.key(), e.modifiers().to_key_modifiers());
350
351 if Self::is_matched(&hot_keys.value_frames_freeze(), e.key(), modifiers) {
352 self.freeze_value_frames(&vm);
353 } else if Self::is_matched(&hot_keys.value_frames_unfreeze(), e.key(), modifiers) {
354 self.unfreeze_value_frames(&vm);
355 } else if Self::is_matched(&hot_keys.toggle_popup_freeze(), e.key(), modifiers) {
356 self.toggle_freeze_popups(&root, &vm);
357 } else if Self::is_matched(&hot_keys.screenshot_selected_control(), e.key(), modifiers) {
358 self.screenshot_selected_control(&vm);
359 } else if Self::is_matched(&hot_keys.inspect_hovered_control(), e.key(), modifiers) {
360 self.inspect_hovered_control(&root, &vm);
361 }
362 }
363
364 fn is_matched(gesture: &KeyGesture, key: Key, modifiers: KeyModifiers) -> bool {
365 (gesture.key() == key || gesture.key() == Key::None) && modifiers.contains(gesture.key_modifiers())
366 }
367
368 fn merge_modifiers(key: Key, modifiers: KeyModifiers) -> KeyModifiers {
371 match key {
372 Key::LeftCtrl | Key::RightCtrl => modifiers | KeyModifiers::CONTROL,
373 Key::LeftShift | Key::RightShift => modifiers | KeyModifiers::SHIFT,
374 Key::LeftAlt | Key::RightAlt => modifiers | KeyModifiers::ALT,
375 _ => modifiers,
376 }
377 }
378
379 fn freeze_value_frames(&self, vm: &MainViewModel) {
380 vm.enable_snapshot_styles(true);
381 }
382
383 fn unfreeze_value_frames(&self, vm: &MainViewModel) {
384 vm.enable_snapshot_styles(false);
385 }
386
387 fn toggle_freeze_popups(&self, root: &TopLevel, vm: &MainViewModel) {
388 vm.set_freeze_popups(!vm.freeze_popups());
389
390 for popup_root in Self::get_popup_roots(root) {
391 if let Some(popup) = popup_root.parent().and_then(|parent| parent.cast::<Popup>()) {
392 if vm.freeze_popups() {
393 let weak = self.to_ref().downgrade();
394 let closing_subscription = popup.closing(move |e| {
395 if let Some(this) = weak.upgrade() {
396 this.popup_on_closing(e);
397 }
398 });
399 self.frozen_popup_states.borrow_mut().push((popup, closing_subscription));
400 } else {
401 let removed: Vec<_> = {
402 let mut frozen_popup_states = self.frozen_popup_states.borrow_mut();
403 let (removed, kept) =
404 frozen_popup_states.drain(..).partition(|(frozen, _)| frozen.ptr_eq(&popup));
405 *frozen_popup_states = kept;
406 removed
407 };
408 for (_, closing_subscription) in removed {
409 closing_subscription.dispose();
410 }
411 }
412 }
413 }
414 }
415
416 fn screenshot_selected_control(&self, vm: &MainViewModel) {
417 vm.shot(None);
418 }
419
420 fn inspect_hovered_control(&self, root: &TopLevel, vm: &MainViewModel) {
421 let mut control = None;
422
423 for popup_root in Self::get_popup_roots(root) {
424 control = self.get_hovered_control(&popup_root);
425
426 if control.is_some() {
427 break;
428 }
429 }
430
431 let control = control.or_else(|| self.get_hovered_control(root));
432
433 if let Some(control) = control {
434 vm.select_control(&control);
435 }
436 }
437
438 fn popup_on_closing(&self, e: &CancelEventArgs) {
439 if self.view_model().is_some_and(|vm| vm.freeze_popups()) {
440 e.set_cancel(true);
441 }
442 }
443
444 fn root_closed(&self) {
445 self.close();
446 }
447
448 pub fn set_options(&self, options: &DevToolsOptions) {
451 *self.hot_keys.borrow_mut() = Some(options.hot_keys());
452
453 if let Some(view_model) = self.view_model() {
454 view_model.set_options(options);
455 }
456 if let Some(theme_variant) = options.theme_variant() {
457 self.set_requested_theme_variant(Some(theme_variant));
458 }
459 }
460
461 pub fn selected_control(&self, control: Option<Ref<Control>>) {
463 if let Some(control) = control {
464 if let Some(view_model) = self.view_model() {
465 view_model.select_control(&control);
466 }
467 }
468 }
469}
470
471pub(crate) fn top_level_of_root(root: &dyn IInputRoot) -> Option<Ref<TopLevel>> {
479 let element = root.try_root_element()?;
480 if let Some(top_level) = element.clone().cast::<TopLevel>() {
481 return Some(top_level);
482 }
483 element.visual_children().to_vec().into_iter().find_map(|child| child.cast::<TopLevel>())
484}