@@ -3,11 +3,19 @@ use crate::context::UpdateContext;
3
3
use crate :: debug_ui:: display_object:: open_display_object_button;
4
4
use crate :: debug_ui:: handle:: { AVM1ObjectHandle , DisplayObjectHandle } ;
5
5
use crate :: debug_ui:: Message ;
6
- use egui:: { Grid , Id , TextEdit , Ui , Window } ;
6
+ use crate :: string:: AvmString ;
7
+ use egui:: { Grid , Id , TextBuffer , TextEdit , Ui , Window } ;
8
+ use gc_arena:: Mutation ;
9
+ use ruffle_wstr:: { WStr , WString } ;
7
10
8
11
#[ derive( Debug , Default ) ]
9
12
pub struct Avm1ObjectWindow {
10
13
hovered_debug_rect : Option < DisplayObjectHandle > ,
14
+ key_filter_string : String ,
15
+ edited_key : Option < WString > ,
16
+ value_edit_buf : String ,
17
+ /// True if the active text edit should be focused (after clicking 'edit', etc.)
18
+ focus_text_edit : bool ,
11
19
}
12
20
13
21
impl Avm1ObjectWindow {
@@ -36,23 +44,273 @@ impl Avm1ObjectWindow {
36
44
. show ( ui, |ui| {
37
45
let mut keys = object. get_keys ( & mut activation, true ) ;
38
46
keys. sort ( ) ;
47
+ ui. add (
48
+ egui:: TextEdit :: singleline ( & mut self . key_filter_string )
49
+ . hint_text ( "🔍 Filter" ) ,
50
+ ) ;
51
+ ui. end_row ( ) ;
52
+ keys. retain ( |key| {
53
+ self . key_filter_string . is_empty ( )
54
+ || key
55
+ . to_string ( )
56
+ . to_ascii_lowercase ( )
57
+ . contains ( & self . key_filter_string . to_ascii_lowercase ( ) )
58
+ } ) ;
39
59
40
60
for key in keys {
41
61
let value = object. get ( key, & mut activation) ;
42
62
43
63
ui. label ( key. to_string ( ) ) ;
44
- show_avm1_value (
45
- ui,
46
- & mut activation,
64
+ if let Some ( new) =
65
+ self . show_avm1_value ( ui, & mut activation, & key, value, messages)
66
+ {
67
+ if let Err ( e) = object. set ( key, new, & mut activation) {
68
+ tracing:: error!( "Failed to set key {key}: {e}" ) ;
69
+ }
70
+ }
71
+ ui. end_row ( ) ;
72
+ }
73
+ } ) ;
74
+ } ) ;
75
+ keep_open
76
+ }
77
+ /// Shows an egui widget to inspect and (for certain value types) edit an AVM1 value.
78
+ ///
79
+ /// Optionally returns the updated value, if the user edited it.
80
+ pub fn show_avm1_value < ' gc > (
81
+ & mut self ,
82
+ ui : & mut Ui ,
83
+ activation : & mut Activation < ' _ , ' gc > ,
84
+ key : & AvmString ,
85
+ value : Result < Value < ' gc > , Error < ' gc > > ,
86
+ messages : & mut Vec < Message > ,
87
+ ) -> Option < Value < ' gc > > {
88
+ match value {
89
+ Ok ( value) => {
90
+ match value {
91
+ Value :: Undefined | Value :: Null => { }
92
+ Value :: Bool ( mut value) => {
93
+ if ui. checkbox ( & mut value, "" ) . clicked ( ) {
94
+ return Some ( Value :: Bool ( value) ) ;
95
+ }
96
+ }
97
+ Value :: Number ( value) => {
98
+ if let Some ( new) = self . num_edit_ui ( ui, key, value) . map ( Value :: Number ) {
99
+ return Some ( new) ;
100
+ }
101
+ }
102
+ Value :: String ( value) => {
103
+ if let Some ( new) = self . string_edit_ui ( ui, key, value) . map ( |string| {
104
+ Value :: String ( AvmString :: new_utf8 ( activation. gc ( ) , string) )
105
+ } ) {
106
+ return Some ( new) ;
107
+ } ;
108
+ }
109
+ Value :: Object ( value) => {
110
+ if value. as_executable ( ) . is_some ( ) {
111
+ ui. label ( "Function" ) ;
112
+ } else if ui. button ( object_name ( value) ) . clicked ( ) {
113
+ messages. push ( Message :: TrackAVM1Object ( AVM1ObjectHandle :: new (
114
+ activation. context ,
47
115
value,
116
+ ) ) ) ;
117
+ }
118
+ }
119
+ Value :: MovieClip ( value) => {
120
+ if let Some ( ( _, _, object) ) = value. resolve_reference ( activation) {
121
+ open_display_object_button (
122
+ ui,
123
+ activation. context ,
48
124
messages,
125
+ object,
49
126
& mut self . hovered_debug_rect ,
50
127
) ;
51
- ui. end_row ( ) ;
128
+ } else {
129
+ ui. colored_label (
130
+ ui. style ( ) . visuals . error_fg_color ,
131
+ format ! ( "Unknown movieclip {}" , value. path( ) ) ,
132
+ ) ;
52
133
}
53
- } ) ;
134
+ }
135
+ } ;
136
+ return show_value_type_combo_box ( ui, key. as_wstr ( ) , & value, activation. gc ( ) ) ;
137
+ }
138
+ Err ( e) => {
139
+ ui. colored_label ( ui. style ( ) . visuals . error_fg_color , e. to_string ( ) ) ;
140
+ }
141
+ }
142
+ None
143
+ }
144
+ fn num_edit_ui ( & mut self , ui : & mut Ui , key : & AvmString , num : f64 ) -> Option < f64 > {
145
+ let mut new_val = None ;
146
+ if self
147
+ . edited_key
148
+ . as_ref ( )
149
+ . is_some_and ( |edit_key| * edit_key == key. as_wstr ( ) )
150
+ {
151
+ ui. horizontal ( |ui| {
152
+ let re = ui
153
+ . add ( egui:: TextEdit :: singleline ( & mut self . value_edit_buf ) . desired_width ( 96.0 ) ) ;
154
+ if self . focus_text_edit {
155
+ re. request_focus ( ) ;
156
+ self . focus_text_edit = false ;
157
+ }
158
+ match self . value_edit_buf . parse :: < f64 > ( ) {
159
+ Ok ( num) => {
160
+ if ui. input ( |inp| inp. key_pressed ( egui:: Key :: Enter ) )
161
+ || ui. set_button ( ) . clicked ( )
162
+ {
163
+ new_val = Some ( num) ;
164
+ self . edited_key = None ;
165
+ }
166
+ }
167
+ Err ( e) => {
168
+ ui. add_enabled ( false , egui:: Button :: new ( CHECKMARK_ICON ) )
169
+ . on_disabled_hover_text ( e. to_string ( ) ) ;
170
+ }
171
+ }
172
+ if ui. cancel_button ( ) . clicked ( ) {
173
+ self . edited_key = None ;
174
+ }
54
175
} ) ;
55
- keep_open
176
+ } else {
177
+ ui. horizontal ( |ui| {
178
+ let num_str = num. to_string ( ) ;
179
+ ui. label ( & num_str) ;
180
+ if ui. edit_button ( ) . clicked ( ) {
181
+ self . edited_key = Some ( key. as_wstr ( ) . to_owned ( ) ) ;
182
+ self . value_edit_buf = num_str;
183
+ self . focus_text_edit = true ;
184
+ }
185
+ } ) ;
186
+ }
187
+ new_val
188
+ }
189
+ fn string_edit_ui (
190
+ & mut self ,
191
+ ui : & mut Ui ,
192
+ key : & AvmString ,
193
+ string : AvmString ,
194
+ ) -> Option < String > {
195
+ let mut new_val = None ;
196
+ ui. horizontal ( |ui| {
197
+ if self
198
+ . edited_key
199
+ . as_ref ( )
200
+ . is_some_and ( |edit_key| * edit_key == key. as_wstr ( ) )
201
+ {
202
+ let re = ui. add ( TextEdit :: singleline ( & mut self . value_edit_buf ) . desired_width ( 96.0 ) ) ;
203
+ if self . focus_text_edit {
204
+ re. request_focus ( ) ;
205
+ self . focus_text_edit = false ;
206
+ }
207
+ if ui. set_button ( ) . clicked ( ) {
208
+ new_val = Some ( self . value_edit_buf . take ( ) ) ;
209
+ self . edited_key = None ;
210
+ }
211
+ if ui. cancel_button ( ) . clicked ( ) {
212
+ self . edited_key = None ;
213
+ }
214
+ } else {
215
+ ui. label ( string. to_utf8_lossy ( ) ) ;
216
+ if ui. edit_button ( ) . clicked ( ) {
217
+ self . value_edit_buf = string. to_string ( ) ;
218
+ self . edited_key = Some ( key. as_wstr ( ) . to_owned ( ) ) ;
219
+ self . focus_text_edit = true ;
220
+ }
221
+ }
222
+ } ) ;
223
+ new_val
224
+ }
225
+ }
226
+
227
+ /// Dropdown menu indicating the type of the value, as well as letting the
228
+ /// user set a new type.
229
+ fn show_value_type_combo_box < ' gc > (
230
+ ui : & mut Ui ,
231
+ key : & WStr ,
232
+ value : & Value < ' gc > ,
233
+ mutation : & Mutation < ' gc > ,
234
+ ) -> Option < Value < ' gc > > {
235
+ let mut new = None ;
236
+ egui:: ComboBox :: new ( egui:: Id :: new ( "value_combo" ) . with ( key) , "Type" )
237
+ . selected_text ( value_label ( value) )
238
+ . show_ui ( ui, |ui| {
239
+ if ui
240
+ . selectable_label ( matches ! ( value, Value :: Undefined ) , "Undefined" )
241
+ . clicked ( )
242
+ {
243
+ new = Some ( Value :: Undefined ) ;
244
+ }
245
+ if ui
246
+ . selectable_label ( matches ! ( value, Value :: Null ) , "Null" )
247
+ . clicked ( )
248
+ {
249
+ new = Some ( Value :: Null ) ;
250
+ }
251
+ if ui
252
+ . selectable_label ( matches ! ( value, Value :: Bool ( _) ) , "Bool" )
253
+ . clicked ( )
254
+ {
255
+ new = Some ( Value :: Bool ( false ) ) ;
256
+ }
257
+ if ui
258
+ . selectable_label ( matches ! ( value, Value :: Number ( _) ) , "Number" )
259
+ . clicked ( )
260
+ {
261
+ new = Some ( Value :: Number ( 0.0 ) ) ;
262
+ }
263
+ if ui
264
+ . selectable_label ( matches ! ( value, Value :: String ( _) ) , "String" )
265
+ . clicked ( )
266
+ {
267
+ new = Some ( Value :: String ( AvmString :: new ( mutation, WString :: new ( ) ) ) ) ;
268
+ }
269
+ // There is no sensible way to create default values for these types,
270
+ // so just disable the selectable labels to prevent setting to these types.
271
+ ui. add_enabled (
272
+ false ,
273
+ egui:: SelectableLabel :: new ( matches ! ( value, Value :: Object ( _) ) , "Object" ) ,
274
+ ) ;
275
+ ui. add_enabled (
276
+ false ,
277
+ egui:: SelectableLabel :: new ( matches ! ( value, Value :: MovieClip ( _) ) , "MovieClip" ) ,
278
+ ) ;
279
+ } ) ;
280
+ new
281
+ }
282
+
283
+ fn value_label ( value : & Value ) -> & ' static str {
284
+ match value {
285
+ Value :: Undefined => "Undefined" ,
286
+ Value :: Null => "Null" ,
287
+ Value :: Bool ( _) => "Bool" ,
288
+ Value :: Number ( _) => "Number" ,
289
+ Value :: String ( _) => "String" ,
290
+ Value :: Object ( _) => "Object" ,
291
+ Value :: MovieClip ( _) => "MovieClip" ,
292
+ }
293
+ }
294
+
295
+ const PENCIL_ICON : & str = "✏" ;
296
+ const CHECKMARK_ICON : & str = "✔" ;
297
+ const CANCEL_ICON : & str = "🗙" ;
298
+
299
+ trait UiExt {
300
+ fn edit_button ( & mut self ) -> egui:: Response ;
301
+ fn set_button ( & mut self ) -> egui:: Response ;
302
+ fn cancel_button ( & mut self ) -> egui:: Response ;
303
+ }
304
+
305
+ impl UiExt for egui:: Ui {
306
+ fn edit_button ( & mut self ) -> egui:: Response {
307
+ self . button ( PENCIL_ICON ) . on_hover_text ( "Edit" )
308
+ }
309
+ fn set_button ( & mut self ) -> egui:: Response {
310
+ self . button ( CHECKMARK_ICON ) . on_hover_text ( "Set" )
311
+ }
312
+ fn cancel_button ( & mut self ) -> egui:: Response {
313
+ self . button ( CANCEL_ICON ) . on_hover_text ( "Cancel" )
56
314
}
57
315
}
58
316
@@ -67,52 +325,3 @@ fn object_name(object: Object) -> String {
67
325
format ! ( "Object {:p}" , object. as_ptr( ) )
68
326
}
69
327
}
70
-
71
- pub fn show_avm1_value < ' gc > (
72
- ui : & mut Ui ,
73
- activation : & mut Activation < ' _ , ' gc > ,
74
- value : Result < Value < ' gc > , Error < ' gc > > ,
75
- messages : & mut Vec < Message > ,
76
- hover : & mut Option < DisplayObjectHandle > ,
77
- ) {
78
- match value {
79
- Ok ( Value :: Undefined ) => {
80
- ui. label ( "Undefined" ) ;
81
- }
82
- Ok ( Value :: Null ) => {
83
- ui. label ( "Null" ) ;
84
- }
85
- Ok ( Value :: Bool ( value) ) => {
86
- ui. label ( value. to_string ( ) ) ;
87
- }
88
- Ok ( Value :: Number ( value) ) => {
89
- ui. label ( value. to_string ( ) ) ;
90
- }
91
- Ok ( Value :: String ( value) ) => {
92
- TextEdit :: singleline ( & mut value. to_string ( ) ) . show ( ui) ;
93
- }
94
- Ok ( Value :: Object ( value) ) => {
95
- if value. as_executable ( ) . is_some ( ) {
96
- ui. label ( "Function" ) ;
97
- } else if ui. button ( object_name ( value) ) . clicked ( ) {
98
- messages. push ( Message :: TrackAVM1Object ( AVM1ObjectHandle :: new (
99
- activation. context ,
100
- value,
101
- ) ) ) ;
102
- }
103
- }
104
- Ok ( Value :: MovieClip ( value) ) => {
105
- if let Some ( ( _, _, object) ) = value. resolve_reference ( activation) {
106
- open_display_object_button ( ui, activation. context , messages, object, hover) ;
107
- } else {
108
- ui. colored_label (
109
- ui. style ( ) . visuals . error_fg_color ,
110
- format ! ( "Unknown movieclip {}" , value. path( ) ) ,
111
- ) ;
112
- }
113
- }
114
- Err ( e) => {
115
- ui. colored_label ( ui. style ( ) . visuals . error_fg_color , e. to_string ( ) ) ;
116
- }
117
- }
118
- }
0 commit comments