core: Add filtering to avm2 debug window
This commit is contained in:
parent
89962cf970
commit
5b429e3bf5
|
@ -1312,6 +1312,16 @@ dependencies = [
|
|||
"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]]
|
||||
name = "either"
|
||||
version = "1.8.1"
|
||||
|
@ -3699,6 +3709,7 @@ dependencies = [
|
|||
"dasp",
|
||||
"downcast-rs",
|
||||
"egui",
|
||||
"egui_extras",
|
||||
"encoding_rs",
|
||||
"enumset",
|
||||
"flash-lso",
|
||||
|
|
|
@ -52,6 +52,7 @@ hashbrown = { version = "0.14.0", features = ["raw"] }
|
|||
scopeguard = "1.1.0"
|
||||
fluent-templates = "0.8.0"
|
||||
egui = { version = "0.22.0", optional = true }
|
||||
egui_extras = { version = "0.22.0", optional = true }
|
||||
|
||||
[target.'cfg(not(target_family = "wasm"))'.dependencies.futures]
|
||||
version = "0.3.28"
|
||||
|
@ -71,6 +72,7 @@ nellymoser = ["nellymoser-rs"]
|
|||
audio = ["dasp"]
|
||||
known_stubs = ["linkme"]
|
||||
default_compatibility_rules = []
|
||||
egui = ["dep:egui", "dep:egui_extras"]
|
||||
|
||||
[build-dependencies]
|
||||
build_playerglobal = { path = "build_playerglobal" }
|
||||
|
|
|
@ -3,13 +3,19 @@ use crate::avm2::{Activation, Error, Namespace, Object, TObject, Value};
|
|||
use crate::context::UpdateContext;
|
||||
use crate::debug_ui::handle::{AVM2ObjectHandle, DisplayObjectHandle};
|
||||
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 std::borrow::Cow;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Avm2ObjectWindow {
|
||||
hovered_debug_rect: Option<DisplayObjectHandle>,
|
||||
show_private_items: bool,
|
||||
call_getters: bool,
|
||||
getter_values: FnvHashMap<(String, String), Option<ValueWidget>>,
|
||||
search: String,
|
||||
}
|
||||
|
||||
impl Avm2ObjectWindow {
|
||||
|
@ -30,50 +36,167 @@ impl Avm2ObjectWindow {
|
|||
Window::new(object_name(activation.context.gc_context, object))
|
||||
.id(Id::new(object.as_ptr()))
|
||||
.open(&mut keep_open)
|
||||
.scroll2([true, true])
|
||||
.scroll2([false, false]) // Table will provide its own scrolling
|
||||
.show(egui_ctx, |ui| {
|
||||
if let Some(vtable) = object.vtable() {
|
||||
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: _ } => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
self.show_properties(object, messages, &mut activation, ui);
|
||||
});
|
||||
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 {
|
||||
|
@ -83,47 +206,3 @@ fn object_name<'gc>(mc: MutationContext<'gc, '_>, object: Object<'gc>) -> String
|
|||
.unwrap_or(Cow::Borrowed("Object"));
|
||||
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