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::debug_ui::avm1::Avm1ObjectWindow;
|
||||
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::movie::{MovieListWindow, MovieWindow};
|
||||
use crate::display_object::TDisplayObject;
|
||||
|
@ -30,6 +30,7 @@ pub struct DebugUi {
|
|||
queued_messages: Vec<Message>,
|
||||
items_to_save: Vec<ItemToSave>,
|
||||
movie_list: Option<MovieListWindow>,
|
||||
display_object_search: Option<DisplayObjectSearchWindow>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -42,10 +43,16 @@ pub enum Message {
|
|||
TrackTopLevelMovie,
|
||||
ShowKnownMovies,
|
||||
SaveFile(ItemToSave),
|
||||
SearchForDisplayObject,
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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 {
|
||||
match message {
|
||||
Message::TrackDisplayObject(object) => {
|
||||
|
@ -98,10 +111,17 @@ impl DebugUi {
|
|||
Message::ShowKnownMovies => {
|
||||
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> {
|
||||
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() {
|
||||
if let Some(object) = window.hovered_debug_rect() {
|
||||
let object = object.fetch(dynamic_root_set);
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
mod search;
|
||||
|
||||
pub use search::DisplayObjectSearchWindow;
|
||||
|
||||
use crate::avm1::TObject as _;
|
||||
use crate::avm2::object::TObject as _;
|
||||
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> {
|
||||
*dynamic_root_set.fetch(&self.root)
|
||||
}
|
||||
|
||||
pub fn as_ptr(&self) -> *const DisplayObjectPtr {
|
||||
self.ptr
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for DisplayObjectHandle {
|
||||
|
|
|
@ -1863,14 +1863,14 @@ impl Player {
|
|||
}
|
||||
|
||||
#[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,
|
||||
// we avoid borrowing directly from self here.
|
||||
// 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 mut debug_ui = debug_ui.borrow_mut();
|
||||
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-movie = View Movie
|
||||
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,
|
||||
default_player_options: PlayerOptions,
|
||||
currently_opened: Option<(Url, PlayerOptions)>,
|
||||
was_suspended_before_debug: bool,
|
||||
}
|
||||
|
||||
impl RuffleGui {
|
||||
|
@ -91,6 +92,7 @@ impl RuffleGui {
|
|||
is_about_visible: false,
|
||||
is_as3_warning_visible: false,
|
||||
is_open_dialog_visible: false,
|
||||
was_suspended_before_debug: false,
|
||||
|
||||
context_menu: vec![],
|
||||
open_dialog: OpenDialog::new(
|
||||
|
@ -113,6 +115,7 @@ impl RuffleGui {
|
|||
egui_ctx: &egui::Context,
|
||||
show_menu: bool,
|
||||
mut player: Option<&mut Player>,
|
||||
menu_height_offset: f64,
|
||||
) {
|
||||
if show_menu {
|
||||
self.main_menu_bar(egui_ctx, player.as_deref_mut());
|
||||
|
@ -124,7 +127,16 @@ impl RuffleGui {
|
|||
self.as3_warning(egui_ctx);
|
||||
|
||||
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() {
|
||||
std::thread::spawn(move || {
|
||||
if let Some(path) = FileDialog::new()
|
||||
|
@ -272,6 +284,12 @@ impl RuffleGui {
|
|||
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| {
|
||||
|
|
|
@ -2,7 +2,7 @@ use crate::backends::DesktopUiBackend;
|
|||
use crate::cli::Opt;
|
||||
use crate::custom_event::RuffleEvent;
|
||||
use crate::gui::movie::{MovieView, MovieViewRenderer};
|
||||
use crate::gui::RuffleGui;
|
||||
use crate::gui::{RuffleGui, MENU_HEIGHT};
|
||||
use crate::player::{PlayerController, PlayerOptions};
|
||||
use anyhow::anyhow;
|
||||
use egui::Context;
|
||||
|
@ -189,11 +189,17 @@ impl GuiController {
|
|||
.expect("Surface became unavailable");
|
||||
|
||||
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| {
|
||||
self.gui.update(
|
||||
context,
|
||||
self.window.fullscreen().is_none(),
|
||||
show_menu,
|
||||
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;
|
||||
|
|
Loading…
Reference in New Issue