core: Add debug feature to find display objects by mouse
This commit is contained in:
parent
898b2c8948
commit
5e608764ec
|
@ -7,7 +7,7 @@ mod movie;
|
||||||
use crate::context::{RenderContext, UpdateContext};
|
use crate::context::{RenderContext, UpdateContext};
|
||||||
use crate::debug_ui::avm1::Avm1ObjectWindow;
|
use crate::debug_ui::avm1::Avm1ObjectWindow;
|
||||||
use crate::debug_ui::avm2::Avm2ObjectWindow;
|
use crate::debug_ui::avm2::Avm2ObjectWindow;
|
||||||
use crate::debug_ui::display_object::DisplayObjectWindow;
|
use crate::debug_ui::display_object::{DisplayObjectSearchWindow, DisplayObjectWindow};
|
||||||
use crate::debug_ui::handle::{AVM1ObjectHandle, AVM2ObjectHandle, DisplayObjectHandle};
|
use crate::debug_ui::handle::{AVM1ObjectHandle, AVM2ObjectHandle, DisplayObjectHandle};
|
||||||
use crate::debug_ui::movie::{MovieListWindow, MovieWindow};
|
use crate::debug_ui::movie::{MovieListWindow, MovieWindow};
|
||||||
use crate::display_object::TDisplayObject;
|
use crate::display_object::TDisplayObject;
|
||||||
|
@ -30,6 +30,7 @@ pub struct DebugUi {
|
||||||
queued_messages: Vec<Message>,
|
queued_messages: Vec<Message>,
|
||||||
items_to_save: Vec<ItemToSave>,
|
items_to_save: Vec<ItemToSave>,
|
||||||
movie_list: Option<MovieListWindow>,
|
movie_list: Option<MovieListWindow>,
|
||||||
|
display_object_search: Option<DisplayObjectSearchWindow>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -42,10 +43,16 @@ pub enum Message {
|
||||||
TrackTopLevelMovie,
|
TrackTopLevelMovie,
|
||||||
ShowKnownMovies,
|
ShowKnownMovies,
|
||||||
SaveFile(ItemToSave),
|
SaveFile(ItemToSave),
|
||||||
|
SearchForDisplayObject,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DebugUi {
|
impl DebugUi {
|
||||||
pub(crate) fn show(&mut self, egui_ctx: &egui::Context, context: &mut UpdateContext) {
|
pub(crate) fn show(
|
||||||
|
&mut self,
|
||||||
|
egui_ctx: &egui::Context,
|
||||||
|
context: &mut UpdateContext,
|
||||||
|
movie_offset: f64,
|
||||||
|
) {
|
||||||
let mut messages = std::mem::take(&mut self.queued_messages);
|
let mut messages = std::mem::take(&mut self.queued_messages);
|
||||||
|
|
||||||
self.display_objects.retain(|object, window| {
|
self.display_objects.retain(|object, window| {
|
||||||
|
@ -72,6 +79,12 @@ impl DebugUi {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(mut search) = self.display_object_search.take() {
|
||||||
|
if search.show(egui_ctx, context, &mut messages, movie_offset) {
|
||||||
|
self.display_object_search = Some(search);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for message in messages {
|
for message in messages {
|
||||||
match message {
|
match message {
|
||||||
Message::TrackDisplayObject(object) => {
|
Message::TrackDisplayObject(object) => {
|
||||||
|
@ -98,9 +111,16 @@ impl DebugUi {
|
||||||
Message::ShowKnownMovies => {
|
Message::ShowKnownMovies => {
|
||||||
self.movie_list = Some(Default::default());
|
self.movie_list = Some(Default::default());
|
||||||
}
|
}
|
||||||
|
Message::SearchForDisplayObject => {
|
||||||
|
self.display_object_search = Some(Default::default());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn should_suspend_player(&self) -> bool {
|
||||||
|
self.display_object_search.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn items_to_save(&mut self) -> Vec<ItemToSave> {
|
pub fn items_to_save(&mut self) -> Vec<ItemToSave> {
|
||||||
std::mem::take(&mut self.items_to_save)
|
std::mem::take(&mut self.items_to_save)
|
||||||
|
@ -137,6 +157,15 @@ impl DebugUi {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(window) = &self.display_object_search {
|
||||||
|
for (color, object) in window.hovered_debug_rects() {
|
||||||
|
let object = object.fetch(dynamic_root_set);
|
||||||
|
let bounds = world_matrix * object.world_bounds();
|
||||||
|
|
||||||
|
draw_debug_rect(context, color, bounds, 5.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (_object, window) in self.avm1_objects.iter() {
|
for (_object, window) in self.avm1_objects.iter() {
|
||||||
if let Some(object) = window.hovered_debug_rect() {
|
if let Some(object) = window.hovered_debug_rect() {
|
||||||
let object = object.fetch(dynamic_root_set);
|
let object = object.fetch(dynamic_root_set);
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
mod search;
|
||||||
|
|
||||||
|
pub use search::DisplayObjectSearchWindow;
|
||||||
|
|
||||||
use crate::avm1::TObject as _;
|
use crate::avm1::TObject as _;
|
||||||
use crate::avm2::object::TObject as _;
|
use crate::avm2::object::TObject as _;
|
||||||
use crate::context::UpdateContext;
|
use crate::context::UpdateContext;
|
||||||
|
|
|
@ -0,0 +1,209 @@
|
||||||
|
use crate::context::UpdateContext;
|
||||||
|
use crate::debug_ui::display_object::{open_display_object_button, DEFAULT_DEBUG_COLORS};
|
||||||
|
use crate::debug_ui::handle::DisplayObjectHandle;
|
||||||
|
use crate::debug_ui::Message;
|
||||||
|
use crate::display_object::{
|
||||||
|
DisplayObject, TDisplayObject, TDisplayObjectContainer, TInteractiveObject,
|
||||||
|
};
|
||||||
|
use egui::collapsing_header::CollapsingState;
|
||||||
|
use egui::color_picker::show_color;
|
||||||
|
use egui::{Rgba, Ui, Vec2, Window};
|
||||||
|
use fnv::FnvHashMap;
|
||||||
|
use swf::{Point, Twips};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct DisplayObjectTree {
|
||||||
|
handle: DisplayObjectHandle,
|
||||||
|
children: Vec<DisplayObjectTree>,
|
||||||
|
color: [f32; 3],
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct DisplayObjectSearchWindow {
|
||||||
|
finding: bool,
|
||||||
|
results: Vec<DisplayObjectTree>,
|
||||||
|
unique_results: FnvHashMap<DisplayObjectHandle, swf::Color>,
|
||||||
|
hovered_debug_rect: Option<DisplayObjectHandle>,
|
||||||
|
include_hidden: bool,
|
||||||
|
only_mouse_enabled: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DisplayObjectSearchWindow {
|
||||||
|
pub fn hovered_debug_rects(&self) -> Vec<(swf::Color, DisplayObjectHandle)> {
|
||||||
|
if let Some(hovered_debug_rect) = &self.hovered_debug_rect {
|
||||||
|
vec![(swf::Color::RED, hovered_debug_rect.clone())]
|
||||||
|
} else {
|
||||||
|
self.unique_results
|
||||||
|
.iter()
|
||||||
|
.map(|(k, v)| (*v, k.clone()))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn show(
|
||||||
|
&mut self,
|
||||||
|
egui_ctx: &egui::Context,
|
||||||
|
context: &mut UpdateContext,
|
||||||
|
messages: &mut Vec<Message>,
|
||||||
|
movie_offset: f64,
|
||||||
|
) -> bool {
|
||||||
|
let mut keep_open = true;
|
||||||
|
self.hovered_debug_rect = None;
|
||||||
|
|
||||||
|
if self.finding {
|
||||||
|
self.generate_results(egui_ctx, context, movie_offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
Window::new("Display Object Picker")
|
||||||
|
.open(&mut keep_open)
|
||||||
|
.scroll2([true, true])
|
||||||
|
.show(egui_ctx, |ui| {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.checkbox(&mut self.include_hidden, "Include Hidden");
|
||||||
|
ui.checkbox(&mut self.only_mouse_enabled, "Only Mouse Enabled Objects");
|
||||||
|
});
|
||||||
|
if self.finding {
|
||||||
|
ui.label("Click somewhere to finish searching");
|
||||||
|
} else if ui.button("Start Searching").clicked() {
|
||||||
|
self.finding = true;
|
||||||
|
}
|
||||||
|
if !self.results.is_empty() {
|
||||||
|
ui.separator();
|
||||||
|
ui.heading("Results");
|
||||||
|
for tree in &self.results {
|
||||||
|
show_object_tree(ui, context, tree, messages, &mut self.hovered_debug_rect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
keep_open
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_results(
|
||||||
|
&mut self,
|
||||||
|
egui_ctx: &egui::Context,
|
||||||
|
context: &mut UpdateContext,
|
||||||
|
movie_offset: f64,
|
||||||
|
) {
|
||||||
|
self.results.clear();
|
||||||
|
self.unique_results.clear();
|
||||||
|
|
||||||
|
if let Some(pointer) = egui_ctx.pointer_latest_pos() {
|
||||||
|
let inverse_view_matrix = context.stage.inverse_view_matrix();
|
||||||
|
let pos = inverse_view_matrix
|
||||||
|
* Point::from_pixels(pointer.x as f64, pointer.y as f64 - movie_offset);
|
||||||
|
|
||||||
|
let mut results = vec![];
|
||||||
|
for child in context.stage.iter_render_list() {
|
||||||
|
self.create_result_tree(context, pos, child, &mut results);
|
||||||
|
}
|
||||||
|
self.results = results;
|
||||||
|
}
|
||||||
|
|
||||||
|
if egui_ctx.input_mut(|input| input.pointer.any_click()) {
|
||||||
|
self.finding = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn object_matches(&self, object: DisplayObject, cursor: Point<Twips>) -> bool {
|
||||||
|
if !self.include_hidden && !object.visible() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if self.only_mouse_enabled
|
||||||
|
&& !object
|
||||||
|
.as_interactive()
|
||||||
|
.map(|i| i.mouse_enabled())
|
||||||
|
.unwrap_or_default()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
object.world_bounds().contains(cursor)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_result_tree<'gc>(
|
||||||
|
&mut self,
|
||||||
|
context: &mut UpdateContext<'_, 'gc>,
|
||||||
|
cursor: Point<Twips>,
|
||||||
|
object: DisplayObject<'gc>,
|
||||||
|
add_to: &mut Vec<DisplayObjectTree>,
|
||||||
|
) {
|
||||||
|
if self.object_matches(object, cursor) {
|
||||||
|
let handle = DisplayObjectHandle::new(context, object);
|
||||||
|
let color =
|
||||||
|
DEFAULT_DEBUG_COLORS[self.unique_results.len() % DEFAULT_DEBUG_COLORS.len()];
|
||||||
|
let mut tree = DisplayObjectTree {
|
||||||
|
handle: handle.clone(),
|
||||||
|
children: vec![],
|
||||||
|
color,
|
||||||
|
};
|
||||||
|
self.unique_results.insert(
|
||||||
|
handle,
|
||||||
|
swf::Color {
|
||||||
|
r: (color[0] * 255.0) as u8,
|
||||||
|
g: (color[1] * 255.0) as u8,
|
||||||
|
b: (color[2] * 255.0) as u8,
|
||||||
|
a: 255,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if let Some(container) = object.as_container() {
|
||||||
|
for child in container.iter_render_list() {
|
||||||
|
self.create_result_tree(context, cursor, child, &mut tree.children);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
add_to.push(tree);
|
||||||
|
} else if let Some(container) = object.as_container() {
|
||||||
|
for child in container.iter_render_list() {
|
||||||
|
self.create_result_tree(context, cursor, child, add_to);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show_object_tree(
|
||||||
|
ui: &mut Ui,
|
||||||
|
context: &mut UpdateContext,
|
||||||
|
tree: &DisplayObjectTree,
|
||||||
|
messages: &mut Vec<Message>,
|
||||||
|
hovered_debug_rect: &mut Option<DisplayObjectHandle>,
|
||||||
|
) {
|
||||||
|
if tree.children.is_empty() {
|
||||||
|
show_item(ui, context, tree, messages, hovered_debug_rect);
|
||||||
|
} else {
|
||||||
|
CollapsingState::load_with_default_open(ui.ctx(), ui.id().with(tree.handle.as_ptr()), true)
|
||||||
|
.show_header(ui, |ui| {
|
||||||
|
show_item(ui, context, tree, messages, hovered_debug_rect);
|
||||||
|
})
|
||||||
|
.body(|ui| {
|
||||||
|
for child in &tree.children {
|
||||||
|
show_object_tree(ui, context, child, messages, hovered_debug_rect);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show_item(
|
||||||
|
ui: &mut Ui,
|
||||||
|
context: &mut UpdateContext,
|
||||||
|
tree: &DisplayObjectTree,
|
||||||
|
messages: &mut Vec<Message>,
|
||||||
|
hovered_debug_rect: &mut Option<DisplayObjectHandle>,
|
||||||
|
) {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
show_color(
|
||||||
|
ui,
|
||||||
|
Rgba::from_rgb(tree.color[0], tree.color[1], tree.color[2]),
|
||||||
|
Vec2::new(ui.spacing().interact_size.y, ui.spacing().interact_size.y),
|
||||||
|
);
|
||||||
|
let object = tree.handle.fetch(context.dynamic_root);
|
||||||
|
open_display_object_button(
|
||||||
|
ui,
|
||||||
|
context,
|
||||||
|
messages,
|
||||||
|
tree.handle.fetch(context.dynamic_root),
|
||||||
|
hovered_debug_rect,
|
||||||
|
);
|
||||||
|
if !object.visible() {
|
||||||
|
ui.weak("(Hidden)");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
|
@ -28,6 +28,10 @@ impl DisplayObjectHandle {
|
||||||
pub fn fetch<'gc>(&self, dynamic_root_set: DynamicRootSet<'gc>) -> DisplayObject<'gc> {
|
pub fn fetch<'gc>(&self, dynamic_root_set: DynamicRootSet<'gc>) -> DisplayObject<'gc> {
|
||||||
*dynamic_root_set.fetch(&self.root)
|
*dynamic_root_set.fetch(&self.root)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn as_ptr(&self) -> *const DisplayObjectPtr {
|
||||||
|
self.ptr
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for DisplayObjectHandle {
|
impl Debug for DisplayObjectHandle {
|
||||||
|
|
|
@ -1863,14 +1863,14 @@ impl Player {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "egui")]
|
#[cfg(feature = "egui")]
|
||||||
pub fn show_debug_ui(&mut self, egui_ctx: &egui::Context) {
|
pub fn show_debug_ui(&mut self, egui_ctx: &egui::Context, movie_offset: f64) {
|
||||||
// To allow using `mutate_with_update_context` and passing the context inside the debug ui,
|
// To allow using `mutate_with_update_context` and passing the context inside the debug ui,
|
||||||
// we avoid borrowing directly from self here.
|
// we avoid borrowing directly from self here.
|
||||||
// This method should only be called once and it will panic if it tries to recursively render.
|
// This method should only be called once and it will panic if it tries to recursively render.
|
||||||
let debug_ui = self.debug_ui.clone();
|
let debug_ui = self.debug_ui.clone();
|
||||||
let mut debug_ui = debug_ui.borrow_mut();
|
let mut debug_ui = debug_ui.borrow_mut();
|
||||||
self.mutate_with_update_context(|context| {
|
self.mutate_with_update_context(|context| {
|
||||||
debug_ui.show(egui_ctx, context);
|
debug_ui.show(egui_ctx, context, movie_offset);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,3 +29,5 @@ debug-menu = Debug Tools
|
||||||
debug-menu-open-stage = View Stage Info
|
debug-menu-open-stage = View Stage Info
|
||||||
debug-menu-open-movie = View Movie
|
debug-menu-open-movie = View Movie
|
||||||
debug-menu-open-movie-list = Show Known Movies
|
debug-menu-open-movie-list = Show Known Movies
|
||||||
|
debug-menu-search-display-objects = Search Display Objects...
|
||||||
|
|
||||||
|
|
|
@ -71,6 +71,7 @@ pub struct RuffleGui {
|
||||||
locale: LanguageIdentifier,
|
locale: LanguageIdentifier,
|
||||||
default_player_options: PlayerOptions,
|
default_player_options: PlayerOptions,
|
||||||
currently_opened: Option<(Url, PlayerOptions)>,
|
currently_opened: Option<(Url, PlayerOptions)>,
|
||||||
|
was_suspended_before_debug: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RuffleGui {
|
impl RuffleGui {
|
||||||
|
@ -91,6 +92,7 @@ impl RuffleGui {
|
||||||
is_about_visible: false,
|
is_about_visible: false,
|
||||||
is_as3_warning_visible: false,
|
is_as3_warning_visible: false,
|
||||||
is_open_dialog_visible: false,
|
is_open_dialog_visible: false,
|
||||||
|
was_suspended_before_debug: false,
|
||||||
|
|
||||||
context_menu: vec![],
|
context_menu: vec![],
|
||||||
open_dialog: OpenDialog::new(
|
open_dialog: OpenDialog::new(
|
||||||
|
@ -113,6 +115,7 @@ impl RuffleGui {
|
||||||
egui_ctx: &egui::Context,
|
egui_ctx: &egui::Context,
|
||||||
show_menu: bool,
|
show_menu: bool,
|
||||||
mut player: Option<&mut Player>,
|
mut player: Option<&mut Player>,
|
||||||
|
menu_height_offset: f64,
|
||||||
) {
|
) {
|
||||||
if show_menu {
|
if show_menu {
|
||||||
self.main_menu_bar(egui_ctx, player.as_deref_mut());
|
self.main_menu_bar(egui_ctx, player.as_deref_mut());
|
||||||
|
@ -124,7 +127,16 @@ impl RuffleGui {
|
||||||
self.as3_warning(egui_ctx);
|
self.as3_warning(egui_ctx);
|
||||||
|
|
||||||
if let Some(player) = player {
|
if let Some(player) = player {
|
||||||
player.show_debug_ui(egui_ctx);
|
let was_suspended = player.debug_ui().should_suspend_player();
|
||||||
|
player.show_debug_ui(egui_ctx, menu_height_offset);
|
||||||
|
if was_suspended != player.debug_ui().should_suspend_player() {
|
||||||
|
if player.debug_ui().should_suspend_player() {
|
||||||
|
self.was_suspended_before_debug = !player.is_playing();
|
||||||
|
player.set_is_playing(false);
|
||||||
|
} else {
|
||||||
|
player.set_is_playing(!self.was_suspended_before_debug);
|
||||||
|
}
|
||||||
|
}
|
||||||
for item in player.debug_ui().items_to_save() {
|
for item in player.debug_ui().items_to_save() {
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
if let Some(path) = FileDialog::new()
|
if let Some(path) = FileDialog::new()
|
||||||
|
@ -272,6 +284,12 @@ impl RuffleGui {
|
||||||
player.debug_ui().queue_message(DebugMessage::ShowKnownMovies);
|
player.debug_ui().queue_message(DebugMessage::ShowKnownMovies);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if Button::new(text(&self.locale, "debug-menu-search-display-objects")).ui(ui).clicked() {
|
||||||
|
ui.close_menu();
|
||||||
|
if let Some(player) = &mut player {
|
||||||
|
player.debug_ui().queue_message(DebugMessage::SearchForDisplayObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
menu::menu_button(ui, text(&self.locale, "help-menu"), |ui| {
|
menu::menu_button(ui, text(&self.locale, "help-menu"), |ui| {
|
||||||
|
|
|
@ -2,7 +2,7 @@ use crate::backends::DesktopUiBackend;
|
||||||
use crate::cli::Opt;
|
use crate::cli::Opt;
|
||||||
use crate::custom_event::RuffleEvent;
|
use crate::custom_event::RuffleEvent;
|
||||||
use crate::gui::movie::{MovieView, MovieViewRenderer};
|
use crate::gui::movie::{MovieView, MovieViewRenderer};
|
||||||
use crate::gui::RuffleGui;
|
use crate::gui::{RuffleGui, MENU_HEIGHT};
|
||||||
use crate::player::{PlayerController, PlayerOptions};
|
use crate::player::{PlayerController, PlayerOptions};
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use egui::Context;
|
use egui::Context;
|
||||||
|
@ -189,11 +189,17 @@ impl GuiController {
|
||||||
.expect("Surface became unavailable");
|
.expect("Surface became unavailable");
|
||||||
|
|
||||||
let raw_input = self.egui_winit.take_egui_input(&self.window);
|
let raw_input = self.egui_winit.take_egui_input(&self.window);
|
||||||
|
let show_menu = self.window.fullscreen().is_none();
|
||||||
let mut full_output = self.egui_ctx.run(raw_input, |context| {
|
let mut full_output = self.egui_ctx.run(raw_input, |context| {
|
||||||
self.gui.update(
|
self.gui.update(
|
||||||
context,
|
context,
|
||||||
self.window.fullscreen().is_none(),
|
show_menu,
|
||||||
player.as_deref_mut(),
|
player.as_deref_mut(),
|
||||||
|
if show_menu {
|
||||||
|
MENU_HEIGHT as f64 * self.window.scale_factor()
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
self.repaint_after = full_output.repaint_after;
|
self.repaint_after = full_output.repaint_after;
|
||||||
|
|
Loading…
Reference in New Issue