core: Add filtering to avm2 debug window
This commit is contained in:
parent
89962cf970
commit
5b429e3bf5
|
@ -1312,6 +1312,16 @@ dependencies = [
|
||||||
"winit",
|
"winit",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "egui_extras"
|
||||||
|
version = "0.22.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9278f4337b526f0d57e5375e5a7340a311fa6ee8f9fcc75721ac50af13face02"
|
||||||
|
dependencies = [
|
||||||
|
"egui",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "either"
|
name = "either"
|
||||||
version = "1.8.1"
|
version = "1.8.1"
|
||||||
|
@ -3699,6 +3709,7 @@ dependencies = [
|
||||||
"dasp",
|
"dasp",
|
||||||
"downcast-rs",
|
"downcast-rs",
|
||||||
"egui",
|
"egui",
|
||||||
|
"egui_extras",
|
||||||
"encoding_rs",
|
"encoding_rs",
|
||||||
"enumset",
|
"enumset",
|
||||||
"flash-lso",
|
"flash-lso",
|
||||||
|
|
|
@ -52,6 +52,7 @@ hashbrown = { version = "0.14.0", features = ["raw"] }
|
||||||
scopeguard = "1.1.0"
|
scopeguard = "1.1.0"
|
||||||
fluent-templates = "0.8.0"
|
fluent-templates = "0.8.0"
|
||||||
egui = { version = "0.22.0", optional = true }
|
egui = { version = "0.22.0", optional = true }
|
||||||
|
egui_extras = { version = "0.22.0", optional = true }
|
||||||
|
|
||||||
[target.'cfg(not(target_family = "wasm"))'.dependencies.futures]
|
[target.'cfg(not(target_family = "wasm"))'.dependencies.futures]
|
||||||
version = "0.3.28"
|
version = "0.3.28"
|
||||||
|
@ -71,6 +72,7 @@ nellymoser = ["nellymoser-rs"]
|
||||||
audio = ["dasp"]
|
audio = ["dasp"]
|
||||||
known_stubs = ["linkme"]
|
known_stubs = ["linkme"]
|
||||||
default_compatibility_rules = []
|
default_compatibility_rules = []
|
||||||
|
egui = ["dep:egui", "dep:egui_extras"]
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
build_playerglobal = { path = "build_playerglobal" }
|
build_playerglobal = { path = "build_playerglobal" }
|
||||||
|
|
|
@ -3,13 +3,19 @@ use crate::avm2::{Activation, Error, Namespace, Object, TObject, Value};
|
||||||
use crate::context::UpdateContext;
|
use crate::context::UpdateContext;
|
||||||
use crate::debug_ui::handle::{AVM2ObjectHandle, DisplayObjectHandle};
|
use crate::debug_ui::handle::{AVM2ObjectHandle, DisplayObjectHandle};
|
||||||
use crate::debug_ui::Message;
|
use crate::debug_ui::Message;
|
||||||
use egui::{Grid, Id, TextEdit, Ui, Window};
|
use egui::{Align, Id, Layout, TextEdit, Ui, Widget, Window};
|
||||||
|
use egui_extras::{Column, TableBuilder};
|
||||||
|
use fnv::FnvHashMap;
|
||||||
use gc_arena::MutationContext;
|
use gc_arena::MutationContext;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct Avm2ObjectWindow {
|
pub struct Avm2ObjectWindow {
|
||||||
hovered_debug_rect: Option<DisplayObjectHandle>,
|
hovered_debug_rect: Option<DisplayObjectHandle>,
|
||||||
|
show_private_items: bool,
|
||||||
|
call_getters: bool,
|
||||||
|
getter_values: FnvHashMap<(String, String), Option<ValueWidget>>,
|
||||||
|
search: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Avm2ObjectWindow {
|
impl Avm2ObjectWindow {
|
||||||
|
@ -30,50 +36,167 @@ impl Avm2ObjectWindow {
|
||||||
Window::new(object_name(activation.context.gc_context, object))
|
Window::new(object_name(activation.context.gc_context, object))
|
||||||
.id(Id::new(object.as_ptr()))
|
.id(Id::new(object.as_ptr()))
|
||||||
.open(&mut keep_open)
|
.open(&mut keep_open)
|
||||||
.scroll2([true, true])
|
.scroll2([false, false]) // Table will provide its own scrolling
|
||||||
.show(egui_ctx, |ui| {
|
.show(egui_ctx, |ui| {
|
||||||
if let Some(vtable) = object.vtable() {
|
self.show_properties(object, messages, &mut activation, ui);
|
||||||
let mut entries = Vec::<(String, Namespace<'gc>, Property)>::new();
|
|
||||||
for (name, ns, prop) in vtable.resolved_traits().iter() {
|
|
||||||
entries.push((name.to_string(), ns, *prop));
|
|
||||||
}
|
|
||||||
entries.sort_by(|a, b| a.0.cmp(&b.0));
|
|
||||||
|
|
||||||
Grid::new(ui.id().with("properties"))
|
|
||||||
.num_columns(3)
|
|
||||||
.show(ui, |ui| {
|
|
||||||
for (name, ns, prop) in entries {
|
|
||||||
// TODO: filtering
|
|
||||||
if ns.is_public() {
|
|
||||||
match prop {
|
|
||||||
Property::Slot { slot_id }
|
|
||||||
| Property::ConstSlot { slot_id } => {
|
|
||||||
let value = object.get_slot(slot_id);
|
|
||||||
ui.label(name).on_hover_ui(|ui| {
|
|
||||||
ui.label(format!("{ns:?}"));
|
|
||||||
});
|
|
||||||
show_avm2_value(ui, &mut activation, value, messages);
|
|
||||||
ui.end_row();
|
|
||||||
}
|
|
||||||
Property::Virtual { get: Some(get), .. } => {
|
|
||||||
let value =
|
|
||||||
object.call_method(get, &[], &mut activation);
|
|
||||||
ui.label(name).on_hover_ui(|ui| {
|
|
||||||
ui.label(format!("{ns:?}"));
|
|
||||||
});
|
|
||||||
show_avm2_value(ui, &mut activation, value, messages);
|
|
||||||
ui.end_row();
|
|
||||||
}
|
|
||||||
Property::Method { .. } => {}
|
|
||||||
Property::Virtual { get: None, set: _ } => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
keep_open
|
keep_open
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn show_properties<'gc>(
|
||||||
|
&mut self,
|
||||||
|
object: Object<'gc>,
|
||||||
|
messages: &mut Vec<Message>,
|
||||||
|
activation: &mut Activation<'_, 'gc>,
|
||||||
|
ui: &mut Ui,
|
||||||
|
) {
|
||||||
|
let mut entries = Vec::<(String, Namespace<'gc>, Property)>::new();
|
||||||
|
// We can't access things whilst we iterate the vtable, so clone and sort it all here
|
||||||
|
if let Some(vtable) = object.vtable() {
|
||||||
|
for (name, ns, prop) in vtable.resolved_traits().iter() {
|
||||||
|
entries.push((name.to_string(), ns, *prop));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
entries.sort_by(|a, b| a.0.cmp(&b.0));
|
||||||
|
|
||||||
|
// [NA] Adding these on the same line seems to break the width of the table :(
|
||||||
|
TextEdit::singleline(&mut self.search)
|
||||||
|
.hint_text("Search...")
|
||||||
|
.ui(ui);
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.checkbox(&mut self.show_private_items, "Show Private Items");
|
||||||
|
ui.checkbox(&mut self.call_getters, "Call Getters");
|
||||||
|
});
|
||||||
|
|
||||||
|
let search = self.search.to_ascii_lowercase();
|
||||||
|
|
||||||
|
TableBuilder::new(ui)
|
||||||
|
.striped(true)
|
||||||
|
.resizable(true)
|
||||||
|
.column(Column::auto())
|
||||||
|
.column(Column::remainder())
|
||||||
|
.column(Column::exact(75.0))
|
||||||
|
.auto_shrink([true, true])
|
||||||
|
.cell_layout(Layout::left_to_right(Align::Center))
|
||||||
|
.header(20.0, |mut header| {
|
||||||
|
header.col(|ui| {
|
||||||
|
ui.strong("Name");
|
||||||
|
});
|
||||||
|
header.col(|ui| {
|
||||||
|
ui.strong("Value");
|
||||||
|
});
|
||||||
|
header.col(|ui| {
|
||||||
|
ui.strong("Controls");
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.body(|mut body| {
|
||||||
|
for (name, ns, prop) in entries {
|
||||||
|
if (ns.is_public() || self.show_private_items)
|
||||||
|
&& name.to_ascii_lowercase().contains(&search)
|
||||||
|
{
|
||||||
|
match prop {
|
||||||
|
Property::Slot { slot_id } | Property::ConstSlot { slot_id } => {
|
||||||
|
body.row(18.0, |mut row| {
|
||||||
|
row.col(|ui| {
|
||||||
|
ui.label(&name).on_hover_ui(|ui| {
|
||||||
|
ui.label(format!("{ns:?}"));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
row.col(|ui| {
|
||||||
|
let value = object.get_slot(slot_id);
|
||||||
|
ValueWidget::new(activation, value).show(ui, messages);
|
||||||
|
});
|
||||||
|
row.col(|_| {});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Property::Virtual { get: Some(get), .. } => {
|
||||||
|
let key = (ns.as_uri().to_string(), name.clone());
|
||||||
|
body.row(18.0, |mut row| {
|
||||||
|
row.col(|ui| {
|
||||||
|
ui.label(&name).on_hover_ui(|ui| {
|
||||||
|
ui.label(format!("{ns:?}"));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
row.col(|ui| {
|
||||||
|
if self.call_getters {
|
||||||
|
let value = object.call_method(get, &[], activation);
|
||||||
|
ValueWidget::new(activation, value).show(ui, messages);
|
||||||
|
} else {
|
||||||
|
let value = self.getter_values.get_mut(&key);
|
||||||
|
if let Some(value) = value {
|
||||||
|
// Empty entry means we want to refresh it,
|
||||||
|
// so let's do that now
|
||||||
|
let widget = value.get_or_insert_with(|| {
|
||||||
|
let value =
|
||||||
|
object.call_method(get, &[], activation);
|
||||||
|
ValueWidget::new(activation, value)
|
||||||
|
});
|
||||||
|
widget.show(ui, messages);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
row.col(|ui| {
|
||||||
|
if ui.button("Call Getter").clicked() {
|
||||||
|
self.getter_values.insert(key, None);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
enum ValueWidget {
|
||||||
|
String(String),
|
||||||
|
Object(AVM2ObjectHandle, String),
|
||||||
|
Other(Cow<'static, str>),
|
||||||
|
Error(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ValueWidget {
|
||||||
|
fn new<'gc>(
|
||||||
|
activation: &mut Activation<'_, 'gc>,
|
||||||
|
value: Result<Value<'gc>, Error<'gc>>,
|
||||||
|
) -> Self {
|
||||||
|
match value {
|
||||||
|
Ok(Value::Undefined) => ValueWidget::Other(Cow::Borrowed("Undefined")),
|
||||||
|
Ok(Value::Null) => ValueWidget::Other(Cow::Borrowed("Null")),
|
||||||
|
Ok(Value::Bool(value)) => ValueWidget::Other(Cow::Owned(value.to_string())),
|
||||||
|
Ok(Value::Number(value)) => ValueWidget::Other(Cow::Owned(value.to_string())),
|
||||||
|
Ok(Value::Integer(value)) => ValueWidget::Other(Cow::Owned(value.to_string())),
|
||||||
|
Ok(Value::String(value)) => ValueWidget::String(value.to_string()),
|
||||||
|
Ok(Value::Object(value)) => ValueWidget::Object(
|
||||||
|
AVM2ObjectHandle::new(&mut activation.context, value),
|
||||||
|
object_name(activation.context.gc_context, value),
|
||||||
|
),
|
||||||
|
Err(e) => ValueWidget::Error(e.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show(&self, ui: &mut Ui, messages: &mut Vec<Message>) {
|
||||||
|
match self {
|
||||||
|
ValueWidget::String(value) => {
|
||||||
|
// Readonly
|
||||||
|
TextEdit::singleline(&mut value.as_str()).show(ui);
|
||||||
|
}
|
||||||
|
ValueWidget::Object(value, name) => {
|
||||||
|
if ui.button(name).clicked() {
|
||||||
|
messages.push(Message::TrackAVM2Object(value.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ValueWidget::Other(value) => {
|
||||||
|
ui.label(value.as_ref());
|
||||||
|
}
|
||||||
|
ValueWidget::Error(value) => {
|
||||||
|
ui.colored_label(ui.style().visuals.error_fg_color, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn object_name<'gc>(mc: MutationContext<'gc, '_>, object: Object<'gc>) -> String {
|
fn object_name<'gc>(mc: MutationContext<'gc, '_>, object: Object<'gc>) -> String {
|
||||||
|
@ -83,47 +206,3 @@ fn object_name<'gc>(mc: MutationContext<'gc, '_>, object: Object<'gc>) -> String
|
||||||
.unwrap_or(Cow::Borrowed("Object"));
|
.unwrap_or(Cow::Borrowed("Object"));
|
||||||
format!("{} {:p}", name, object.as_ptr())
|
format!("{} {:p}", name, object.as_ptr())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn show_avm2_value<'gc>(
|
|
||||||
ui: &mut Ui,
|
|
||||||
activation: &mut Activation<'_, 'gc>,
|
|
||||||
value: Result<Value<'gc>, Error<'gc>>,
|
|
||||||
messages: &mut Vec<Message>,
|
|
||||||
) {
|
|
||||||
match value {
|
|
||||||
Ok(Value::Undefined) => {
|
|
||||||
ui.label("Undefined");
|
|
||||||
}
|
|
||||||
Ok(Value::Null) => {
|
|
||||||
ui.label("Null");
|
|
||||||
}
|
|
||||||
Ok(Value::Bool(value)) => {
|
|
||||||
ui.label(value.to_string());
|
|
||||||
}
|
|
||||||
Ok(Value::Number(value)) => {
|
|
||||||
ui.label(value.to_string());
|
|
||||||
}
|
|
||||||
Ok(Value::Integer(value)) => {
|
|
||||||
ui.label(value.to_string());
|
|
||||||
}
|
|
||||||
Ok(Value::String(value)) => {
|
|
||||||
TextEdit::singleline(&mut value.to_string()).show(ui);
|
|
||||||
}
|
|
||||||
Ok(Value::Object(value)) => {
|
|
||||||
if value.as_executable().is_some() {
|
|
||||||
ui.label("Function");
|
|
||||||
} else if ui
|
|
||||||
.button(object_name(activation.context.gc_context, value))
|
|
||||||
.clicked()
|
|
||||||
{
|
|
||||||
messages.push(Message::TrackAVM2Object(AVM2ObjectHandle::new(
|
|
||||||
&mut activation.context,
|
|
||||||
value,
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
ui.colored_label(ui.style().visuals.error_fg_color, e.to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue