1use super::dispatch::{
4 DialogItem, DialogPopupDraw, DialogPopupTrackingState, DialogTrackingState,
5 PendingDialogPopupMenu, PersistentDialogSnapshot, QueuedEvent, RetainedModalDialogClickState,
6};
7use super::types::{decode_mac_roman_for_render, Rect, ShapeOp};
8use crate::cpu::{CpuOps, Register};
9use crate::display::CursorImage;
10use crate::memory::{MacMemoryBus, MemoryBus};
11use crate::quickdraw::fonts::get_font_face_scaled;
12use crate::quickdraw::text::get_font_metrics;
13use crate::ui_theme::{ControlKind, UiThemeId};
14use crate::Result;
15use std::collections::VecDeque;
16use std::sync::OnceLock;
17
18static TRACE_DIALOG_PROCS: OnceLock<bool> = OnceLock::new();
19static TRACE_DIALOG_FILTER: OnceLock<bool> = OnceLock::new();
20static TRACE_TEXTEDIT: OnceLock<bool> = OnceLock::new();
21static TRACE_DIALOG_ITEMS: OnceLock<bool> = OnceLock::new();
22static TRACE_DIALOG_TEXT_INLINE: OnceLock<bool> = OnceLock::new();
23const MANAGER_DIALOG_RECORD_ALIGNMENT: u32 = 256;
24
25struct DialogCIconLayout {
26 width: i16,
27 height: i16,
28 pm_row_bytes: u32,
29 mask_row_bytes: u32,
30 bmap_row_bytes: u32,
31 pixel_size: u16,
32 mask_data_ptr: u32,
33 bmap_data_ptr: u32,
34 pixel_data_ptr: u32,
35}
36
37#[derive(Clone, Copy)]
38struct FullscreenOffscreenDialogRestoreCandidate {
39 base: u32,
40 row_bytes: u32,
41 top: i16,
42 left: i16,
43 bottom: i16,
44 right: i16,
45 ctab_handle: u32,
46 score: u32,
47}
48
49#[derive(Clone, Copy, Debug, PartialEq, Eq)]
50struct TeResolvedStyle {
51 font: i16,
52 face: i16,
53 size: i16,
54 color: (u16, u16, u16),
55 line_height: i16,
56 ascent: i16,
57}
58
59#[derive(Clone, Copy, Debug, PartialEq, Eq)]
60struct TeStyleRun {
61 start: usize,
62 style_index: usize,
63 style: TeResolvedStyle,
64}
65
66fn trace_dialog_procs_enabled() -> bool {
67 *TRACE_DIALOG_PROCS.get_or_init(|| std::env::var_os("SYSTEMLESS_TRACE_DIALOG_PROCS").is_some())
68}
69
70fn trace_dialog_filter_enabled() -> bool {
71 *TRACE_DIALOG_FILTER
72 .get_or_init(|| std::env::var_os("SYSTEMLESS_TRACE_DIALOG_FILTER").is_some())
73}
74
75fn trace_textedit_enabled() -> bool {
76 *TRACE_TEXTEDIT.get_or_init(|| std::env::var_os("SYSTEMLESS_TRACE_TEXTEDIT").is_some())
77}
78
79fn trace_dialog_items_enabled() -> bool {
80 *TRACE_DIALOG_ITEMS.get_or_init(|| std::env::var_os("SYSTEMLESS_TRACE_DIALOG_ITEMS").is_some())
81}
82
83fn trace_dialog_text_inline_enabled() -> bool {
84 *TRACE_DIALOG_TEXT_INLINE
85 .get_or_init(|| std::env::var_os("SYSTEMLESS_TRACE_DIALOG_TEXT").is_some())
86}
87
88fn input_trace_event_name(what: u16) -> &'static str {
89 match what {
90 0 => "nullEvent",
91 1 => "mouseDown",
92 2 => "mouseUp",
93 3 => "keyDown",
94 4 => "keyUp",
95 5 => "autoKey",
96 6 => "updateEvt",
97 7 => "diskEvt",
98 8 => "activateEvt",
99 _ => "unknownEvent",
100 }
101}
102
103fn input_trace_nonzero(value: u32) -> String {
104 if value == 0 {
105 "$00000000".to_string()
106 } else {
107 "$NONZERO".to_string()
108 }
109}
110
111fn input_trace_event_message(what: u16, message: u32) -> String {
112 match what {
113 6 | 8 => input_trace_nonzero(message),
114 _ => format!("${message:08X}"),
115 }
116}
117
118fn input_trace_text_bytes(value: &str) -> String {
119 if value.is_empty() {
120 return "empty".to_string();
121 }
122 let mut out = String::from("hex:");
123 for byte in value.as_bytes() {
124 out.push_str(&format!("{byte:02X}"));
125 }
126 out
127}
128
129impl super::TrapDispatcher {
130 fn record_dialog_input_trace(
131 &mut self,
132 trap: &str,
133 event_ptr: u32,
134 what: u16,
135 message: u32,
136 where_v: i16,
137 where_h: i16,
138 modifiers: u16,
139 active_dialog: Option<u32>,
140 result: bool,
141 detail: &str,
142 ) {
143 if !self.input_trace_enabled {
144 return;
145 }
146 let active_dialog = active_dialog
147 .map(input_trace_nonzero)
148 .unwrap_or_else(|| "none".to_string());
149 let mut line = format!(
150 "{trap} action=guest_event:{} event_ptr={} delivered=EventRecord{{what={}({}), message={}, where=({where_v},{where_h}), modifiers=${modifiers:04X}}} {} active_dialog={} result={}",
151 input_trace_event_name(what),
152 input_trace_nonzero(event_ptr),
153 input_trace_event_name(what),
154 what,
155 input_trace_event_message(what, message),
156 self.input_trace_state_fields(),
157 active_dialog,
158 if result { "true" } else { "false" },
159 );
160 if !detail.is_empty() {
161 line.push(' ');
162 line.push_str(detail);
163 }
164 self.record_input_trace_line(line);
165 }
166
167 fn record_modal_dialog_input_trace(
168 &mut self,
169 action: &str,
170 dialog_ptr: u32,
171 bounds: (i16, i16, i16, i16),
172 item_hit: i16,
173 item_type: Option<u8>,
174 highlighted: Option<bool>,
175 result: &str,
176 outcome: &str,
177 ) {
178 if !self.input_trace_enabled {
179 return;
180 }
181 let item_type = item_type
182 .map(|value| format!("${value:02X}"))
183 .unwrap_or_else(|| "none".to_string());
184 let highlighted = highlighted
185 .map(|value| if value { "true" } else { "false" }.to_string())
186 .unwrap_or_else(|| "none".to_string());
187 self.record_input_trace_line(format!(
188 "A991 action={} live_mouse=({},{}) {} dialog={} bounds=({},{},{},{}) item_hit={} item_type={} highlighted={} result={} outcome={}",
189 action,
190 self.mouse_pos.0,
191 self.mouse_pos.1,
192 self.input_trace_state_fields(),
193 input_trace_nonzero(dialog_ptr),
194 bounds.0,
195 bounds.1,
196 bounds.2,
197 bounds.3,
198 item_hit,
199 item_type,
200 highlighted,
201 result,
202 outcome,
203 ));
204 }
205
206 fn record_modal_dialog_text_input_trace(
207 &mut self,
208 action: &str,
209 dialog_ptr: u32,
210 bounds: (i16, i16, i16, i16),
211 edit_item: i16,
212 item_type: Option<u8>,
213 key_code: u8,
214 char_code: u8,
215 text_before: &str,
216 text_after: &str,
217 result: &str,
218 outcome: &str,
219 ) {
220 if !self.input_trace_enabled {
221 return;
222 }
223 let item_type = item_type
224 .map(|value| format!("${value:02X}"))
225 .unwrap_or_else(|| "none".to_string());
226 self.record_input_trace_line(format!(
227 "A991 action={} live_mouse=({},{}) {} dialog={} bounds=({},{},{},{}) edit_item={} item_type={} key_code=${:02X} char_code=${:02X} text_before={} text_after={} result={} outcome={}",
228 action,
229 self.mouse_pos.0,
230 self.mouse_pos.1,
231 self.input_trace_state_fields(),
232 input_trace_nonzero(dialog_ptr),
233 bounds.0,
234 bounds.1,
235 bounds.2,
236 bounds.3,
237 edit_item,
238 item_type,
239 key_code,
240 char_code,
241 input_trace_text_bytes(text_before),
242 input_trace_text_bytes(text_after),
243 result,
244 outcome,
245 ));
246 }
247
248 fn record_modal_dialog_filter_input_trace(
249 &mut self,
250 action: &str,
251 dialog_ptr: u32,
252 bounds: (i16, i16, i16, i16),
253 filter_proc: u32,
254 event: Option<&QueuedEvent>,
255 item_hit: i16,
256 item_type: Option<u8>,
257 handled_mouse_down: bool,
258 dialog_retained: bool,
259 result: &str,
260 outcome: &str,
261 ) {
262 if !self.input_trace_enabled {
263 return;
264 }
265 let item_type = item_type
266 .map(|value| format!("${value:02X}"))
267 .unwrap_or_else(|| "none".to_string());
268 let (event_name, message, where_text, modifiers) = if let Some(event) = event {
269 (
270 format!("{}({})", input_trace_event_name(event.what), event.what),
271 input_trace_event_message(event.what, event.message),
272 format!("({},{})", event.where_v, event.where_h),
273 format!("${:04X}", event.modifiers),
274 )
275 } else {
276 (
277 "none".to_string(),
278 "none".to_string(),
279 "none".to_string(),
280 "none".to_string(),
281 )
282 };
283 self.record_input_trace_line(format!(
284 "A991 action={} live_mouse=({},{}) {} dialog={} bounds=({},{},{},{}) filter_proc={} event={} message={} where={} modifiers={} item_hit={} item_type={} handled_mouse_down={} dialog_retained={} result={} outcome={}",
285 action,
286 self.mouse_pos.0,
287 self.mouse_pos.1,
288 self.input_trace_state_fields(),
289 input_trace_nonzero(dialog_ptr),
290 bounds.0,
291 bounds.1,
292 bounds.2,
293 bounds.3,
294 input_trace_nonzero(filter_proc),
295 event_name,
296 message,
297 where_text,
298 modifiers,
299 item_hit,
300 item_type,
301 handled_mouse_down,
302 dialog_retained,
303 result,
304 outcome,
305 ));
306 }
307
308 fn dialog_template_res_err(&self, dialog_id: i16) -> i16 {
309 if self.find_resource_any(*b"DLOG", dialog_id).is_some() {
310 0
311 } else {
312 0
313 }
314 }
315
316 fn loaded_resource_handle_in_file(
317 &self,
318 res_type: [u8; 4],
319 res_id: i16,
320 ptr: u32,
321 refnum: u16,
322 ) -> Option<u32> {
323 self.loaded_handles
324 .iter()
325 .find_map(|(&handle, &(loaded_ptr, loaded_type, loaded_id))| {
326 if loaded_ptr == ptr
327 && loaded_type == res_type
328 && loaded_id == res_id
329 && self.resource_handle_files.get(&handle).copied() == Some(refnum)
330 {
331 Some(handle)
332 } else {
333 None
334 }
335 })
336 }
337
338 fn set_handle_purgeable(&mut self, handle: u32, purgeable: bool) {
339 if handle == 0 {
340 return;
341 }
342
343 if purgeable {
344 *self.handle_state_bits.entry(handle).or_insert(0) |= 0x40;
345 } else if let Some(bits) = self.handle_state_bits.get_mut(&handle) {
346 *bits &= !0x40;
347 if *bits == 0 {
348 self.handle_state_bits.remove(&handle);
349 }
350 }
351 }
352
353 fn prepare_dialog_resource_handle(
354 &mut self,
355 bus: &mut MacMemoryBus,
356 res_type: [u8; 4],
357 res_id: i16,
358 purgeable: bool,
359 load_if_missing: bool,
360 ) -> Option<(u32, u32)> {
361 let (refnum, ptr) = self.find_resource_any(res_type, res_id)?;
362 let handle = if load_if_missing {
363 let handle =
364 self.get_or_create_resource_handle_in_file(bus, res_type, res_id, ptr, refnum);
365 if ptr != 0 && bus.read_long(handle) == 0 {
366 bus.write_long(handle, ptr);
367 self.ptr_to_handle.insert(ptr, handle);
368 }
369 handle
370 } else {
371 self.loaded_resource_handle_in_file(res_type, res_id, ptr, refnum)?
372 };
373
374 self.set_handle_purgeable(handle, purgeable);
375 Some((handle, ptr))
376 }
377
378 fn dialog_item_resource_type(item_type: u8) -> Option<[u8; 4]> {
379 match item_type & 0x7F {
380 7 => Some(*b"CNTL"),
381 32 => Some(*b"ICON"),
382 64 => Some(*b"PICT"),
383 _ => None,
384 }
385 }
386
387 fn cascade_dialog_resource_purgeability(
388 &mut self,
389 bus: &mut MacMemoryBus,
390 template_type: [u8; 4],
391 template_id: i16,
392 purgeable: bool,
393 load_if_missing: bool,
394 ) {
395 let Some((_, template_ptr)) = self.prepare_dialog_resource_handle(
396 bus,
397 template_type,
398 template_id,
399 purgeable,
400 load_if_missing,
401 ) else {
402 return;
403 };
404
405 let items_id = match template_type {
406 [b'D', b'L', b'O', b'G'] if bus.get_alloc_size(template_ptr).unwrap_or(0) >= 20 => {
408 bus.read_word(template_ptr + 18) as i16
409 }
410 [b'A', b'L', b'R', b'T'] if bus.get_alloc_size(template_ptr).unwrap_or(0) >= 10 => {
412 bus.read_word(template_ptr + 8) as i16
413 }
414 _ => return,
415 };
416
417 let Some((_, ditl_ptr)) = self.prepare_dialog_resource_handle(
418 bus,
419 *b"DITL",
420 items_id,
421 purgeable,
422 load_if_missing,
423 ) else {
424 return;
425 };
426
427 let ditl_len = bus.get_alloc_size(ditl_ptr).unwrap_or(0);
428 let items = Self::parse_ditl(bus, ditl_ptr, ditl_len);
429 for item in items {
430 if let Some(res_type) = Self::dialog_item_resource_type(item.item_type) {
431 self.prepare_dialog_resource_handle(
432 bus,
433 res_type,
434 item.resource_id,
435 purgeable,
436 load_if_missing,
437 );
438 }
439 }
440 }
441
442 const TE_DEST_RECT_OFFSET: u32 = 0x00;
443 const TE_VIEW_RECT_OFFSET: u32 = 0x08;
444 const TE_SEL_RECT_OFFSET: u32 = 0x10;
445 const TE_LINE_HEIGHT_OFFSET: u32 = 0x18;
446 const TE_FONT_ASCENT_OFFSET: u32 = 0x1A;
447 const TE_SEL_POINT_OFFSET: u32 = 0x1C;
448 const TE_SEL_START_OFFSET: u32 = 0x20;
449 const TE_SEL_END_OFFSET: u32 = 0x22;
450 const TE_ACTIVE_OFFSET: u32 = 0x24;
451 const TE_CARET_TIME_OFFSET: u32 = 0x34;
452 const TE_CARET_STATE_OFFSET: u32 = 0x38;
453 #[allow(dead_code)]
454 const TE_CR_ONLY_OFFSET: u32 = 0x48;
455 const TE_JUST_OFFSET: u32 = 0x3A;
456 const TE_LENGTH_OFFSET: u32 = 0x3C;
457 const TE_HTEXT_OFFSET: u32 = 0x3E;
458 const TE_TX_FONT_OFFSET: u32 = 0x4A;
459 const TE_TX_FACE_OFFSET: u32 = 0x4C;
460 const TE_TX_MODE_OFFSET: u32 = 0x4E;
461 const TE_TX_SIZE_OFFSET: u32 = 0x50;
462 const TE_IN_PORT_OFFSET: u32 = 0x52;
463 const TE_N_LINES_OFFSET: u32 = 0x5E;
464 const TE_LINE_STARTS_OFFSET: u32 = 0x60;
465 const TE_REC_MIN_SIZE: u32 = 128;
466 const TE_CARET_BLINK_TICKS: u32 = 32;
467 const TE_LINE_LEFT_INSET: i16 = 1;
471
472 const DBOX_FRAME_MARGIN: i16 = 8;
473 const EDIT_TEXT_FRAME_OUTSET: i16 = 3;
474 const STANDARD_CONTROL_MARK_SIZE: i16 = 12;
475 const STANDARD_CONTROL_MARK_LEFT_INSET: i16 = 2;
476 const STANDARD_CONTROL_TITLE_GAP: i16 = 4;
477 const INACTIVE_STANDARD_CONTROL_TITLE_RGB: [u16; 3] = [0xA1A1, 0xA1A1, 0xA1A1];
478 const CLASSIC_RADIO_OUTLINE_12: [&'static str; 12] = [
479 "....####....",
480 "..##....##..",
481 ".#........#.",
482 ".#........#.",
483 "#..........#",
484 "#..........#",
485 "#..........#",
486 "#..........#",
487 ".#........#.",
488 ".#........#.",
489 "..##....##..",
490 "....####....",
491 ];
492 const CLASSIC_RADIO_DOT_12: [&'static str; 12] = [
493 "............",
494 "............",
495 "............",
496 "....####....",
497 "...######...",
498 "...######...",
499 "...######...",
500 "...######...",
501 "....####....",
502 "............",
503 "............",
504 "............",
505 ];
506
507 const TE_STYLE_N_RUNS_OFFSET: u32 = 0x00;
508 const TE_STYLE_N_STYLES_OFFSET: u32 = 0x02;
509 const TE_STYLE_STYLE_TABLE_OFFSET: u32 = 0x04;
510 const TE_STYLE_LH_TABLE_OFFSET: u32 = 0x08;
511 const TE_STYLE_NULL_STYLE_OFFSET: u32 = 0x10;
512 const TE_STYLE_RUNS_OFFSET: u32 = 0x14;
513
514 const ST_ELEMENT_REFCOUNT_OFFSET: u32 = 0x00;
515 const ST_ELEMENT_HEIGHT_OFFSET: u32 = 0x02;
516 const ST_ELEMENT_ASCENT_OFFSET: u32 = 0x04;
517 const ST_ELEMENT_FONT_OFFSET: u32 = 0x06;
518 const ST_ELEMENT_FACE_OFFSET: u32 = 0x08;
522 const ST_ELEMENT_SIZE_OFFSET: u32 = 0x0A;
523 const ST_ELEMENT_COLOR_OFFSET: u32 = 0x0C;
524 const ST_ELEMENT_SIZE: u32 = 0x12;
525
526 const LH_ELEMENT_HEIGHT_OFFSET: u32 = 0x00;
527 const LH_ELEMENT_ASCENT_OFFSET: u32 = 0x02;
528 const LH_ELEMENT_SIZE: u32 = 0x04;
529
530 const NULL_STYLE_SCRAP_OFFSET: u32 = 0x04;
531 const NULL_STYLE_REC_SIZE: u32 = 0x08;
532
533 const SCRAP_N_STYLES_OFFSET: u32 = 0x00;
536 const SCRAP_STYLE_TAB_OFFSET: u32 = 0x02;
537 const SCRAP_STYLE_START_CHAR_OFFSET: u32 = 0x00;
538 const SCRAP_STYLE_HEIGHT_OFFSET: u32 = 0x04;
539 const SCRAP_STYLE_ASCENT_OFFSET: u32 = 0x06;
540 const SCRAP_STYLE_FONT_OFFSET: u32 = 0x08;
541 const SCRAP_STYLE_FACE_OFFSET: u32 = 0x0A;
542 const SCRAP_STYLE_SIZE_OFFSET: u32 = 0x0C;
543 const SCRAP_STYLE_COLOR_OFFSET: u32 = 0x0E;
544 const SCRAP_STYLE_ELEMENT_SIZE: u32 = 0x14;
545 const STYLE_SCRAP_REC_SIZE: u32 = Self::SCRAP_STYLE_TAB_OFFSET + Self::SCRAP_STYLE_ELEMENT_SIZE;
546
547 const TE_FEATURE_AUTO_SCROLL: u16 = 0;
548 const TE_FEATURE_TEXT_BUFFERING: u16 = 1;
549 const TE_FEATURE_OUTLINE_HILITE: u16 = 2;
550 const TE_FEATURE_INLINE_INPUT: u16 = 3;
551 const TE_FEATURE_USE_TEXT_SERVICES: u16 = 4;
552
553 const TE_BIT_CLEAR: i16 = 0;
554 const TE_BIT_SET: i16 = 1;
555 const TE_BIT_TEST: i16 = -1;
556
557 fn dialog_window_proc_id(&self, bus: &MacMemoryBus, dialog_ptr: u32) -> i16 {
558 self.window_proc_ids
559 .get(&dialog_ptr)
560 .copied()
561 .unwrap_or_else(|| bus.read_word(dialog_ptr + 108) as i16)
564 }
565
566 fn dialog_window_title(bus: &MacMemoryBus, dialog_ptr: u32) -> String {
567 if dialog_ptr == 0 {
568 return String::new();
569 }
570 let title_handle = bus.read_long(dialog_ptr + 134);
573 if title_handle == 0 {
574 return String::new();
575 }
576 let title_ptr = bus.read_long(title_handle);
577 if title_ptr == 0 {
578 return String::new();
579 }
580 decode_mac_roman_for_render(&bus.read_pstring(title_ptr))
581 }
582
583 fn ensure_text_handle_size(bus: &mut MacMemoryBus, item_handle: u32, size: usize) -> u32 {
584 if item_handle == 0 {
585 return 0;
586 }
587
588 let current_ptr = bus.read_long(item_handle);
589 if size == 0 {
590 if current_ptr != 0 {
591 bus.free(current_ptr);
592 bus.write_long(item_handle, 0);
593 }
594 return 0;
595 }
596
597 let required = size as u32;
598 let current_size = if current_ptr != 0 {
599 bus.get_alloc_size(current_ptr).unwrap_or(0)
600 } else {
601 0
602 };
603
604 if current_ptr != 0 && current_size == required {
605 return current_ptr;
606 }
607
608 let new_ptr = bus.alloc(required);
609 if new_ptr == 0 {
610 return current_ptr;
611 }
612
613 if current_ptr != 0 {
614 bus.free(current_ptr);
615 }
616 bus.write_long(item_handle, new_ptr);
617 new_ptr
618 }
619
620 fn text_item_string_from_handle(bus: &MacMemoryBus, item_handle: u32) -> String {
621 Self::text_item_string_from_handle_if_present(bus, item_handle).unwrap_or_default()
622 }
623
624 fn text_item_string_from_handle_if_present(
625 bus: &MacMemoryBus,
626 item_handle: u32,
627 ) -> Option<String> {
628 Self::text_item_bytes_from_handle_if_present(bus, item_handle)
629 .map(|bytes| decode_mac_roman_for_render(&bytes))
630 }
631
632 fn text_item_bytes_from_handle_if_present(
633 bus: &MacMemoryBus,
634 item_handle: u32,
635 ) -> Option<Vec<u8>> {
636 if item_handle == 0 {
637 return None;
638 }
639
640 let data_ptr = bus.read_long(item_handle);
641 if data_ptr == 0 {
642 return Some(Vec::new());
643 }
644
645 let len = bus.get_alloc_size(data_ptr).unwrap_or(0) as usize;
646 Some(bus.read_bytes(data_ptr, len))
647 }
648
649 fn dialog_item_handle_addr(bus: &MacMemoryBus, dialog_ptr: u32, item_no: i16) -> Option<u32> {
650 if item_no <= 0 {
651 return None;
652 }
653
654 let items_handle = bus.read_long(dialog_ptr + 156);
655 if items_handle == 0 {
656 return None;
657 }
658 let ditl_ptr = bus.read_long(items_handle);
659 if ditl_ptr == 0 {
660 return None;
661 }
662
663 let max_index = bus.read_word(ditl_ptr) as i16;
664 if item_no > max_index + 1 {
665 return None;
666 }
667
668 let ditl_len = bus.get_alloc_size(ditl_ptr).unwrap_or(u32::MAX);
669 let mut offset = 2u32;
670 for current_item in 1..=max_index + 1 {
671 let item_handle_addr = ditl_ptr + offset;
672 offset += 4; offset += 8; let item_type = bus.read_byte(ditl_ptr + offset);
675 let data_len_byte = bus.read_byte(ditl_ptr + offset + 1);
676 offset += 2; let base_type = item_type & 0x7F;
678 let remaining = ditl_len.saturating_sub(offset);
679 let payload_len = Self::ditl_item_payload_len(base_type, data_len_byte, remaining)?;
680 let padded = (payload_len + 1) & !1;
681 if current_item == item_no {
682 return Some(item_handle_addr);
683 }
684 offset += padded;
685 }
686
687 None
688 }
689
690 fn dialog_item_handle(bus: &MacMemoryBus, dialog_ptr: u32, item_no: i16) -> u32 {
691 Self::dialog_item_handle_addr(bus, dialog_ptr, item_no)
692 .map(|addr| bus.read_long(addr))
693 .unwrap_or(0)
694 }
695
696 fn set_dialog_item_handle(bus: &mut MacMemoryBus, dialog_ptr: u32, item_no: i16, handle: u32) {
697 if let Some(addr) = Self::dialog_item_handle_addr(bus, dialog_ptr, item_no) {
698 bus.write_long(addr, handle);
699 }
700 }
701
702 pub(crate) fn front_dialog_for_user_item_proc(&self, proc_ptr: u32) -> Option<u32> {
703 if proc_ptr == 0 {
704 return None;
705 }
706 let front = self.front_window;
707 if front == 0 {
708 return None;
709 }
710 let Some(items) = self.dialog_items.get(&front) else {
711 return None;
712 };
713 if items
714 .iter()
715 .any(|item| (item.item_type & 0x7F) == 0 && item.proc_ptr == proc_ptr)
716 {
717 Some(front)
718 } else {
719 None
720 }
721 }
722
723 pub(crate) fn front_dialog_has_user_item_proc(&self, proc_ptr: u32) -> bool {
724 self.front_dialog_for_user_item_proc(proc_ptr).is_some()
725 }
726
727 pub(crate) fn redraw_dialog_window_contents(
728 &mut self,
729 bus: &mut MacMemoryBus,
730 dialog_ptr: u32,
731 ) {
732 if let Some(mut items) = self.dialog_items.get(&dialog_ptr).cloned() {
733 Self::refresh_ditl_proc_ptrs(bus, dialog_ptr, &mut items);
734 let bounds = Self::dialog_screen_bounds(bus, dialog_ptr);
735 let proc_id = self.dialog_window_proc_id(bus, dialog_ptr);
736 let (edit_text, edit_item, default_item) =
737 Self::dialog_edit_state(bus, dialog_ptr, &items);
738 self.draw_dialog(
739 bus,
740 bounds,
741 proc_id,
742 "",
743 &items,
744 default_item,
745 &edit_text,
746 edit_item,
747 false,
748 dialog_ptr,
749 );
750 if Self::dialog_is_game_managed(bounds, &items) {
751 self.dialog_visible_snapshots.remove(&dialog_ptr);
752 } else {
753 let pixels = self.save_dialog_pixels(bus, bounds);
754 self.dialog_visible_snapshots
755 .insert(dialog_ptr, PersistentDialogSnapshot { bounds, pixels });
756 }
757 self.dialog_items.insert(dialog_ptr, items);
758 }
759 }
760
761 pub(crate) fn queue_modeless_dialog_draw_procs(
762 &mut self,
763 bus: &mut MacMemoryBus,
764 dialog_ptr: u32,
765 ) {
766 self.queue_modeless_dialog_draw_procs_intersecting(bus, dialog_ptr, None);
767 }
768
769 fn update_dialog_window_contents<C: CpuOps>(
770 &mut self,
771 bus: &mut MacMemoryBus,
772 cpu: &mut C,
773 dialog_ptr: u32,
774 update_rect: Option<(i16, i16, i16, i16)>,
775 ) -> bool {
776 let Some(mut items) = self.dialog_items.get(&dialog_ptr).cloned() else {
777 return false;
778 };
779 Self::refresh_ditl_proc_ptrs(bus, dialog_ptr, &mut items);
780 let bounds = Self::dialog_screen_bounds(bus, dialog_ptr);
781 let update_screen_rect =
782 update_rect.and_then(|rect| Self::dialog_update_screen_rect(bounds, rect));
783 let proc_id = self.dialog_window_proc_id(bus, dialog_ptr);
784 let (edit_text, edit_item, default_item) = Self::dialog_edit_state(bus, dialog_ptr, &items);
785 self.set_current_port_state(bus, cpu, dialog_ptr, None);
790 let Some(update_screen_rect) = update_screen_rect else {
791 return false;
792 };
793 let before_pixels = self.save_dialog_pixels(bus, bounds);
794 self.dialog_initial_draw_deferred.remove(&dialog_ptr);
795 self.draw_dialog(
796 bus,
797 bounds,
798 proc_id,
799 "",
800 &items,
801 default_item,
802 &edit_text,
803 edit_item,
804 false,
805 dialog_ptr,
806 );
807 self.restore_dialog_pixels_outside_rect(bus, bounds, &before_pixels, update_screen_rect);
812 self.dialog_items.insert(dialog_ptr, items);
813 self.queue_modeless_dialog_draw_procs_intersecting(bus, dialog_ptr, update_rect);
817 true
818 }
819
820 fn dialog_update_screen_rect(
821 bounds: (i16, i16, i16, i16),
822 rect: (i16, i16, i16, i16),
823 ) -> Option<(i16, i16, i16, i16)> {
824 let screen_rect = if Self::rects_intersect(rect, bounds) {
825 rect
826 } else {
827 (
828 bounds.0.saturating_add(rect.0),
829 bounds.1.saturating_add(rect.1),
830 bounds.0.saturating_add(rect.2),
831 bounds.1.saturating_add(rect.3),
832 )
833 };
834 Self::rect_intersection(
835 screen_rect,
836 (
837 bounds.0 - Self::DBOX_FRAME_MARGIN,
838 bounds.1 - Self::DBOX_FRAME_MARGIN,
839 bounds.2 + Self::DBOX_FRAME_MARGIN,
840 bounds.3 + Self::DBOX_FRAME_MARGIN,
841 ),
842 )
843 }
844
845 fn queue_modeless_dialog_draw_procs_intersecting(
846 &mut self,
847 bus: &mut MacMemoryBus,
848 dialog_ptr: u32,
849 update_rect: Option<(i16, i16, i16, i16)>,
850 ) {
851 let Some(mut items) = self.dialog_items.get(&dialog_ptr).cloned() else {
852 return;
853 };
854 Self::refresh_ditl_proc_ptrs(bus, dialog_ptr, &mut items);
855 let bounds = Self::dialog_screen_bounds(bus, dialog_ptr);
856 let effective_update_rect = update_rect.map(|rect| {
857 if Self::rects_intersect(rect, bounds) {
858 rect
859 } else {
860 (
861 bounds.0.saturating_add(rect.0),
862 bounds.1.saturating_add(rect.1),
863 bounds.0.saturating_add(rect.2),
864 bounds.1.saturating_add(rect.3),
865 )
866 }
867 });
868 for (i, item) in items.iter().enumerate() {
869 let item_rect = Self::dialog_item_screen_rect(bounds, item.rect);
870 if (item.item_type & 0x7F) == 0
871 && item.proc_ptr != 0
872 && Self::rects_intersect(item_rect, bounds)
873 && effective_update_rect
874 .map(|rect| Self::rects_intersect(item_rect, rect))
875 .unwrap_or(true)
876 {
877 self.modeless_dialog_draw_proc_queue.push_back((
878 dialog_ptr,
879 item.proc_ptr,
880 (i + 1) as i16,
881 ));
882 }
883 }
884 self.dialog_items.insert(dialog_ptr, items);
885 }
886
887 fn allocate_te_handle(bus: &mut MacMemoryBus) -> u32 {
888 let te_ptr = bus.alloc(Self::TE_REC_MIN_SIZE);
891 if te_ptr == 0 {
892 return 0;
893 }
894 let handle = bus.alloc(4);
895 if handle == 0 {
896 return 0;
897 }
898 bus.write_long(handle, te_ptr);
899 handle
900 }
901
902 fn allocate_handle_with_data(bus: &mut MacMemoryBus, size: u32) -> u32 {
903 let handle = bus.alloc(4);
904 if handle == 0 {
905 return 0;
906 }
907
908 let data_ptr = if size == 0 { 0 } else { bus.alloc(size) };
909 bus.write_long(handle, data_ptr);
910 handle
911 }
912
913 fn ensure_handle_capacity(bus: &mut MacMemoryBus, handle: u32, min_size: u32) -> u32 {
914 if handle == 0 {
915 return 0;
916 }
917
918 let current_ptr = bus.read_long(handle);
919 let current_size = if current_ptr != 0 {
920 bus.get_alloc_size(current_ptr).unwrap_or(0)
921 } else {
922 0
923 };
924 if current_size >= min_size {
925 return current_ptr;
926 }
927
928 let new_ptr = bus.alloc(min_size);
929 if new_ptr == 0 {
930 return current_ptr;
931 }
932 if current_ptr != 0 && current_size != 0 {
933 let existing = bus.read_bytes(current_ptr, current_size as usize);
934 bus.write_bytes(new_ptr, &existing);
935 bus.free(current_ptr);
936 }
937 bus.write_long(handle, new_ptr);
938 new_ptr
939 }
940
941 fn te_record_size_for_line_count(line_count: usize) -> u32 {
942 let line_entries = line_count.saturating_add(4) as u32;
943 Self::TE_REC_MIN_SIZE.max(Self::TE_LINE_STARTS_OFFSET + line_entries * 2)
944 }
945
946 fn ensure_te_record_line_capacity(
947 bus: &mut MacMemoryBus,
948 te_handle: u32,
949 line_count: usize,
950 ) -> u32 {
951 Self::ensure_handle_capacity(
952 bus,
953 te_handle,
954 Self::te_record_size_for_line_count(line_count),
955 )
956 }
957
958 fn te_read_rect(bus: &MacMemoryBus, addr: u32) -> (i16, i16, i16, i16) {
959 (
960 bus.read_word(addr) as i16,
961 bus.read_word(addr + 2) as i16,
962 bus.read_word(addr + 4) as i16,
963 bus.read_word(addr + 6) as i16,
964 )
965 }
966
967 fn te_write_rect_words(bus: &mut MacMemoryBus, addr: u32, rect: (i16, i16, i16, i16)) {
968 bus.write_word(addr, rect.0 as u16);
969 bus.write_word(addr + 2, rect.1 as u16);
970 bus.write_word(addr + 4, rect.2 as u16);
971 bus.write_word(addr + 6, rect.3 as u16);
972 }
973
974 fn te_rect_is_empty(rect: (i16, i16, i16, i16)) -> bool {
975 rect.0 == 0 && rect.1 == 0 && rect.2 == 0 && rect.3 == 0
976 }
977
978 fn te_rect_ptr_candidate(bus: &MacMemoryBus, ptr: u32) -> Option<(i16, i16, i16, i16)> {
979 if ptr == 0 || (ptr & 1) != 0 || ptr < 0x1000 {
980 return None;
981 }
982 let end = ptr.checked_add(8)?;
983 if end > bus.ram_size() {
984 return None;
985 }
986
987 Some(Self::te_read_rect(bus, ptr))
988 }
989
990 #[allow(clippy::type_complexity)] fn te_new_rect_args(
1008 bus: &MacMemoryBus,
1009 sp: u32,
1010 ) -> ((i16, i16, i16, i16), (i16, i16, i16, i16), u32) {
1011 let view_ptr_word = bus.read_long(sp);
1014 let dest_ptr_word = bus.read_long(sp + 4);
1015 let view_via_ptr = Self::te_rect_ptr_candidate(bus, view_ptr_word);
1016 let dest_via_ptr = Self::te_rect_ptr_candidate(bus, dest_ptr_word);
1017 if let (Some(dest), Some(view)) = (dest_via_ptr, view_via_ptr) {
1018 if Self::te_rect_is_empty(view) && !Self::te_rect_is_empty(dest) {
1020 (dest, dest, 8)
1021 } else if Self::te_rect_is_empty(dest) && !Self::te_rect_is_empty(view) {
1022 (view, view, 8)
1023 } else if Self::te_rect_is_empty(dest) && Self::te_rect_is_empty(view) {
1024 (
1025 Self::te_read_rect(bus, sp + 8),
1026 Self::te_read_rect(bus, sp),
1027 16,
1028 )
1029 } else {
1030 (dest, view, 8)
1031 }
1032 } else {
1033 (
1035 Self::te_read_rect(bus, sp + 8),
1036 Self::te_read_rect(bus, sp),
1037 16,
1038 )
1039 }
1040 }
1041
1042 fn te_record_ptr(bus: &MacMemoryBus, te_handle: u32) -> u32 {
1043 if te_handle == 0 {
1044 0
1045 } else {
1046 bus.read_long(te_handle)
1047 }
1048 }
1049
1050 fn te_is_styled_record(bus: &MacMemoryBus, te_ptr: u32) -> bool {
1051 te_ptr != 0 && bus.read_word(te_ptr + Self::TE_TX_SIZE_OFFSET) == 0xFFFF
1052 }
1053
1054 fn te_text_handle(bus: &MacMemoryBus, te_handle: u32) -> u32 {
1055 let te_ptr = Self::te_record_ptr(bus, te_handle);
1056 if te_ptr == 0 {
1057 0
1058 } else {
1059 bus.read_long(te_ptr + Self::TE_HTEXT_OFFSET)
1060 }
1061 }
1062
1063 fn te_style_handle(bus: &MacMemoryBus, te_handle: u32) -> u32 {
1064 let te_ptr = Self::te_record_ptr(bus, te_handle);
1065 if Self::te_is_styled_record(bus, te_ptr) {
1066 bus.read_long(te_ptr + Self::TE_TX_FONT_OFFSET)
1067 } else {
1068 0
1069 }
1070 }
1071
1072 fn te_write_style_handle(bus: &mut MacMemoryBus, te_handle: u32, style_handle: u32) {
1073 let te_ptr = Self::te_record_ptr(bus, te_handle);
1074 if Self::te_is_styled_record(bus, te_ptr) {
1075 bus.write_long(te_ptr + Self::TE_TX_FONT_OFFSET, style_handle);
1076 }
1077 }
1078
1079 fn te_line_starts(bus: &MacMemoryBus, te_handle: u32) -> Vec<usize> {
1080 let te_ptr = Self::te_record_ptr(bus, te_handle);
1081 if te_ptr == 0 {
1082 return vec![0];
1083 }
1084
1085 let n_lines = bus.read_word(te_ptr + Self::TE_N_LINES_OFFSET) as usize;
1086 let mut starts = Vec::with_capacity(n_lines.saturating_add(1));
1087 for index in 0..=n_lines {
1088 starts.push(
1089 bus.read_word(te_ptr + Self::TE_LINE_STARTS_OFFSET + (index as u32 * 2)) as usize,
1090 );
1091 }
1092 if starts.is_empty() {
1093 starts.push(0);
1094 }
1095 starts
1096 }
1097
1098 fn te_char_to_line_index(bus: &MacMemoryBus, te_handle: u32, offset: usize) -> usize {
1099 let starts = Self::te_line_starts(bus, te_handle);
1100 let n_lines = starts.len().saturating_sub(1);
1101 if n_lines <= 1 {
1102 return 0;
1103 }
1104
1105 let mut low = 0usize;
1106 let mut high = n_lines;
1107 let mut current = (high + low) / 2;
1108 while low < high && starts[current] != offset {
1109 if starts[current] < offset {
1110 low = current + 1;
1111 } else {
1112 high = current.saturating_sub(1);
1113 }
1114 current = (high + low) / 2;
1115 }
1116
1117 if starts[current] > offset || current == n_lines {
1118 current.saturating_sub(1)
1119 } else {
1120 current
1121 }
1122 }
1123
1124 fn font_lookup_size(size: i16) -> i16 {
1125 if size == 0 {
1130 0
1131 } else {
1132 size.max(1)
1133 }
1134 }
1135
1136 fn te_char_to_point(&self, bus: &MacMemoryBus, te_handle: u32, offset: usize) -> (i16, i16) {
1137 let te_ptr = Self::te_record_ptr(bus, te_handle);
1138 if te_ptr == 0 {
1139 return (0, 0);
1140 }
1141
1142 let dest_rect = Self::te_read_rect(bus, te_ptr + Self::TE_DEST_RECT_OFFSET);
1143 let just = bus.read_word(te_ptr + Self::TE_JUST_OFFSET) as i16;
1144 let text_bytes = Self::te_text_bytes(bus, te_handle);
1145 let text_len = text_bytes.len();
1146 let clamped = offset.min(text_len);
1147 let starts = Self::te_line_starts(bus, te_handle);
1148 let line_index = Self::te_char_to_line_index(bus, te_handle, clamped);
1149 let line_start = starts.get(line_index).copied().unwrap_or(0);
1150 let line_end = starts.get(line_index + 1).copied().unwrap_or(text_len);
1151 let on_break = clamped != 0
1152 && clamped == text_len
1153 && text_bytes.get(clamped - 1).copied() == Some(b'\r');
1154 let styled_runs = if Self::te_is_styled_record(bus, te_ptr) {
1155 self.te_style_runs(bus, te_handle, text_len)
1156 } else {
1157 Vec::new()
1158 };
1159 let uses_styled_runs = !styled_runs.is_empty() && Self::te_is_styled_record(bus, te_ptr);
1160 let (font, _, size, _, _, _) = self.te_primary_style(bus, te_handle);
1161 let rect_width = dest_rect.3.saturating_sub(dest_rect.1);
1162 let left_offset = match just {
1168 1 | -1 => {
1169 let line_width = if on_break {
1170 rect_width.saturating_sub(1)
1171 } else {
1172 let line_width = if uses_styled_runs {
1173 self.te_measure_text_width_styled(
1174 &styled_runs,
1175 &text_bytes,
1176 line_start,
1177 line_end,
1178 )
1179 } else {
1180 self.te_measure_text_width(
1181 font,
1182 Self::font_lookup_size(size),
1183 &text_bytes,
1184 line_start,
1185 line_end,
1186 )
1187 };
1188 rect_width.saturating_sub(line_width).saturating_sub(1)
1189 };
1190 if just == 1 {
1191 line_width / 2
1192 } else {
1193 line_width
1194 }
1195 }
1196 _ => Self::TE_LINE_LEFT_INSET,
1197 };
1198 let x = if on_break {
1199 dest_rect.1.saturating_add(left_offset)
1200 } else {
1201 dest_rect
1202 .1
1203 .saturating_add(left_offset)
1204 .saturating_add(if uses_styled_runs {
1205 self.te_measure_text_width_styled(
1206 &styled_runs,
1207 &text_bytes,
1208 line_start,
1209 clamped,
1210 )
1211 } else {
1212 self.te_measure_text_width(
1213 font,
1214 Self::font_lookup_size(size),
1215 &text_bytes,
1216 line_start,
1217 clamped,
1218 )
1219 })
1220 };
1221
1222 let mut top = dest_rect.0;
1223 for current_line in 0..line_index {
1224 top = top.saturating_add(Self::te_height_for_line(bus, te_handle, current_line));
1225 }
1226 if on_break {
1227 top = top.saturating_add(Self::te_height_for_line(bus, te_handle, line_index));
1228 }
1229
1230 (top, x)
1231 }
1232
1233 fn te_point_to_char(&self, bus: &MacMemoryBus, te_handle: u32, point: (i16, i16)) -> i16 {
1237 let te_ptr = Self::te_record_ptr(bus, te_handle);
1238 if te_ptr == 0 {
1239 return 0;
1240 }
1241
1242 let text_bytes = Self::te_text_bytes(bus, te_handle);
1243 let text_len = text_bytes.len();
1244 let starts = Self::te_line_starts(bus, te_handle);
1245 let n_lines = starts.len().saturating_sub(1);
1246 if text_len == 0 || n_lines == 0 {
1247 return 0;
1248 }
1249
1250 let dest_rect = Self::te_read_rect(bus, te_ptr + Self::TE_DEST_RECT_OFFSET);
1251 let (point_v, point_h) = point;
1252
1253 if point_v < dest_rect.0 {
1254 return 0;
1255 }
1256
1257 let mut top = dest_rect.0;
1258 let mut line_index = n_lines.saturating_sub(1);
1259 let mut found_line = false;
1260 for current_line in 0..n_lines {
1261 let height = Self::te_height_for_line(bus, te_handle, current_line);
1262 let bottom = top.saturating_add(height);
1263 if point_v < bottom {
1264 line_index = current_line;
1265 found_line = true;
1266 break;
1267 }
1268 top = bottom;
1269 }
1270 if !found_line {
1271 return text_len.min(i16::MAX as usize) as i16;
1272 }
1273
1274 let line_start = starts[line_index];
1275 let line_end = starts.get(line_index + 1).copied().unwrap_or(text_len);
1276
1277 let just = bus.read_word(te_ptr + Self::TE_JUST_OFFSET) as i16;
1278 let styled_runs = if Self::te_is_styled_record(bus, te_ptr) {
1279 self.te_style_runs(bus, te_handle, text_len)
1280 } else {
1281 Vec::new()
1282 };
1283 let uses_styled_runs = !styled_runs.is_empty() && Self::te_is_styled_record(bus, te_ptr);
1284 let (font, _, size, _, _, _) = self.te_primary_style(bus, te_handle);
1285 let rect_width = dest_rect.3.saturating_sub(dest_rect.1);
1286 let left_offset = match just {
1287 1 | -1 => {
1288 let measured_width = if uses_styled_runs {
1289 self.te_measure_text_width_styled(
1290 &styled_runs,
1291 &text_bytes,
1292 line_start,
1293 line_end,
1294 )
1295 } else {
1296 self.te_measure_text_width(
1297 font,
1298 Self::font_lookup_size(size),
1299 &text_bytes,
1300 line_start,
1301 line_end,
1302 )
1303 };
1304 let line_width = rect_width.saturating_sub(measured_width).saturating_sub(1);
1305 if just == 1 {
1306 line_width / 2
1307 } else {
1308 line_width
1309 }
1310 }
1311 _ => Self::TE_LINE_LEFT_INSET,
1312 };
1313
1314 let mut x_cursor = dest_rect.1.saturating_add(left_offset);
1315 for (offset, byte) in text_bytes[line_start..line_end].iter().enumerate() {
1316 let index = line_start + offset;
1317 if matches!(*byte, b'\r' | b'\n') {
1318 return index.min(i16::MAX as usize) as i16;
1319 }
1320 let advance = if uses_styled_runs {
1321 let style = Self::te_style_at_offset(&styled_runs, index);
1322 self.te_char_width(style.font, style.size, *byte)
1323 } else {
1324 self.te_char_width(font, Self::font_lookup_size(size), *byte)
1325 };
1326 let mid = x_cursor.saturating_add(advance / 2);
1330 if point_h < mid {
1331 return index.min(i16::MAX as usize) as i16;
1332 }
1333 x_cursor = x_cursor.saturating_add(advance);
1334 }
1335 line_end.min(i16::MAX as usize) as i16
1336 }
1337
1338 fn te_scroll_contents(
1339 &mut self,
1340 cpu: &mut impl CpuOps,
1341 bus: &mut MacMemoryBus,
1342 te_handle: u32,
1343 dh: i16,
1344 dv: i16,
1345 ) {
1346 if dh == 0 && dv == 0 {
1347 return;
1348 }
1349
1350 let te_ptr = Self::te_record_ptr(bus, te_handle);
1351 if te_ptr == 0 {
1352 return;
1353 }
1354
1355 let mut dest_rect = Self::te_read_rect(bus, te_ptr + Self::TE_DEST_RECT_OFFSET);
1356 dest_rect.0 = dest_rect.0.saturating_add(dv);
1357 dest_rect.1 = dest_rect.1.saturating_add(dh);
1358 dest_rect.2 = dest_rect.2.saturating_add(dv);
1359 dest_rect.3 = dest_rect.3.saturating_add(dh);
1360 Self::te_write_rect_words(bus, te_ptr + Self::TE_DEST_RECT_OFFSET, dest_rect);
1361 self.draw_te_contents(cpu, bus, te_handle);
1362 if let Some(ref tracking) = self.dialog_tracking {
1366 if !tracking.game_managed && tracking.rendered_pixels_final {
1367 let bounds = tracking.bounds;
1368 let new_pixels = self.save_dialog_pixels(bus, bounds);
1369 self.dialog_tracking.as_mut().unwrap().rendered_pixels = new_pixels;
1370 }
1371 }
1372 }
1373
1374 fn te_getdelta(sel_start: i16, sel_stop: i16, view_start: i16, view_stop: i16) -> i16 {
1375 if sel_start < view_start {
1376 view_start.saturating_sub(sel_start)
1377 } else if sel_stop > view_stop {
1378 if sel_stop.saturating_sub(sel_start) > view_stop.saturating_sub(view_start) {
1379 view_start.saturating_sub(sel_start)
1380 } else {
1381 view_stop.saturating_sub(sel_stop)
1382 }
1383 } else {
1384 0
1385 }
1386 }
1387
1388 fn te_height_for_line(bus: &MacMemoryBus, te_handle: u32, line_index: usize) -> i16 {
1389 let te_ptr = Self::te_record_ptr(bus, te_handle);
1390 if te_ptr == 0 {
1391 return 0;
1392 }
1393
1394 if Self::te_is_styled_record(bus, te_ptr) {
1395 let style_handle = Self::te_style_handle(bus, te_handle);
1396 if style_handle != 0 {
1397 let style_ptr = bus.read_long(style_handle);
1398 if style_ptr != 0 {
1399 let lh_handle = bus.read_long(style_ptr + Self::TE_STYLE_LH_TABLE_OFFSET);
1400 let lh_ptr = if lh_handle != 0 {
1401 bus.read_long(lh_handle)
1402 } else {
1403 0
1404 };
1405 if lh_ptr != 0 {
1406 return bus.read_word(
1407 lh_ptr
1408 + (line_index as u32 * Self::LH_ELEMENT_SIZE)
1409 + Self::LH_ELEMENT_HEIGHT_OFFSET,
1410 ) as i16;
1411 }
1412 }
1413 }
1414 }
1415
1416 bus.read_word(te_ptr + Self::TE_LINE_HEIGHT_OFFSET) as i16
1417 }
1418
1419 fn te_ascent_for_line(bus: &MacMemoryBus, te_handle: u32, line_index: usize) -> i16 {
1420 let te_ptr = Self::te_record_ptr(bus, te_handle);
1421 if te_ptr == 0 {
1422 return 0;
1423 }
1424
1425 if Self::te_is_styled_record(bus, te_ptr) {
1426 let style_handle = Self::te_style_handle(bus, te_handle);
1427 if style_handle != 0 {
1428 let style_ptr = bus.read_long(style_handle);
1429 if style_ptr != 0 {
1430 let lh_handle = bus.read_long(style_ptr + Self::TE_STYLE_LH_TABLE_OFFSET);
1431 let lh_ptr = if lh_handle != 0 {
1432 bus.read_long(lh_handle)
1433 } else {
1434 0
1435 };
1436 if lh_ptr != 0 {
1437 return bus.read_word(
1438 lh_ptr
1439 + (line_index as u32 * Self::LH_ELEMENT_SIZE)
1440 + Self::LH_ELEMENT_ASCENT_OFFSET,
1441 ) as i16;
1442 }
1443 }
1444 }
1445 }
1446
1447 bus.read_word(te_ptr + Self::TE_FONT_ASCENT_OFFSET) as i16
1448 }
1449
1450 fn te_primary_style(
1451 &self,
1452 bus: &MacMemoryBus,
1453 te_handle: u32,
1454 ) -> (i16, i16, i16, (u16, u16, u16), i16, i16) {
1455 let te_ptr = Self::te_record_ptr(bus, te_handle);
1456 if te_ptr == 0 {
1457 let size = Self::font_lookup_size(self.tx_size);
1458 let metrics = get_font_metrics(self.tx_font, size);
1459 return (
1460 self.tx_font,
1461 self.tx_face,
1462 size,
1463 self.fg_color,
1464 metrics.ascent + metrics.descent + metrics.leading,
1465 metrics.ascent,
1466 );
1467 }
1468
1469 if Self::te_is_styled_record(bus, te_ptr) {
1470 let style_handle = bus.read_long(te_ptr + Self::TE_TX_FONT_OFFSET);
1471 let style_ptr = if style_handle != 0 {
1472 bus.read_long(style_handle)
1473 } else {
1474 0
1475 };
1476 if style_ptr != 0 {
1477 let style_table_handle =
1478 bus.read_long(style_ptr + Self::TE_STYLE_STYLE_TABLE_OFFSET);
1479 let style_table_ptr = if style_table_handle != 0 {
1480 bus.read_long(style_table_handle)
1481 } else {
1482 0
1483 };
1484 if style_table_ptr != 0 {
1485 return (
1486 bus.read_word(style_table_ptr + Self::ST_ELEMENT_FONT_OFFSET) as i16,
1487 i16::from(bus.read_byte(style_table_ptr + Self::ST_ELEMENT_FACE_OFFSET)),
1488 bus.read_word(style_table_ptr + Self::ST_ELEMENT_SIZE_OFFSET) as i16,
1489 (
1490 bus.read_word(style_table_ptr + Self::ST_ELEMENT_COLOR_OFFSET),
1491 bus.read_word(style_table_ptr + Self::ST_ELEMENT_COLOR_OFFSET + 2),
1492 bus.read_word(style_table_ptr + Self::ST_ELEMENT_COLOR_OFFSET + 4),
1493 ),
1494 bus.read_word(style_table_ptr + Self::ST_ELEMENT_HEIGHT_OFFSET) as i16,
1495 bus.read_word(style_table_ptr + Self::ST_ELEMENT_ASCENT_OFFSET) as i16,
1496 );
1497 }
1498 }
1499 }
1500
1501 let tx_font = bus.read_word(te_ptr + Self::TE_TX_FONT_OFFSET) as i16;
1502 let tx_face = i16::from(bus.read_byte(te_ptr + Self::TE_TX_FACE_OFFSET));
1503 let tx_size =
1504 Self::font_lookup_size(bus.read_word(te_ptr + Self::TE_TX_SIZE_OFFSET) as i16);
1505 let metrics = get_font_metrics(tx_font, tx_size);
1506 (
1507 tx_font,
1508 tx_face,
1509 tx_size,
1510 self.fg_color,
1511 metrics.ascent + metrics.descent + metrics.leading,
1512 metrics.ascent,
1513 )
1514 }
1515
1516 fn te_resolved_style_from_parts(
1517 font: i16,
1518 face: i16,
1519 size: i16,
1520 color: (u16, u16, u16),
1521 line_height: i16,
1522 ascent: i16,
1523 ) -> TeResolvedStyle {
1524 let resolved_size = Self::font_lookup_size(size);
1525 let metrics = get_font_metrics(font, resolved_size);
1526 let fallback_height = metrics.ascent + metrics.descent + metrics.leading;
1527 TeResolvedStyle {
1528 font,
1529 face,
1530 size: resolved_size,
1531 color,
1532 line_height: if line_height > 0 {
1533 line_height
1534 } else {
1535 fallback_height
1536 },
1537 ascent: if ascent > 0 { ascent } else { metrics.ascent },
1538 }
1539 }
1540
1541 fn te_primary_resolved_style(&self, bus: &MacMemoryBus, te_handle: u32) -> TeResolvedStyle {
1542 let (font, face, size, color, line_height, ascent) = self.te_primary_style(bus, te_handle);
1543 Self::te_resolved_style_from_parts(font, face, size, color, line_height, ascent)
1544 }
1545
1546 fn te_style_from_table_element(bus: &MacMemoryBus, style_ptr: u32) -> TeResolvedStyle {
1547 Self::te_resolved_style_from_parts(
1548 bus.read_word(style_ptr + Self::ST_ELEMENT_FONT_OFFSET) as i16,
1549 i16::from(bus.read_byte(style_ptr + Self::ST_ELEMENT_FACE_OFFSET)),
1550 bus.read_word(style_ptr + Self::ST_ELEMENT_SIZE_OFFSET) as i16,
1551 (
1552 bus.read_word(style_ptr + Self::ST_ELEMENT_COLOR_OFFSET),
1553 bus.read_word(style_ptr + Self::ST_ELEMENT_COLOR_OFFSET + 2),
1554 bus.read_word(style_ptr + Self::ST_ELEMENT_COLOR_OFFSET + 4),
1555 ),
1556 bus.read_word(style_ptr + Self::ST_ELEMENT_HEIGHT_OFFSET) as i16,
1557 bus.read_word(style_ptr + Self::ST_ELEMENT_ASCENT_OFFSET) as i16,
1558 )
1559 }
1560
1561 fn te_style_from_scrap_element(bus: &MacMemoryBus, scrap_style_ptr: u32) -> TeResolvedStyle {
1562 Self::te_resolved_style_from_parts(
1563 bus.read_word(scrap_style_ptr + Self::SCRAP_STYLE_FONT_OFFSET) as i16,
1564 i16::from(bus.read_byte(scrap_style_ptr + Self::SCRAP_STYLE_FACE_OFFSET)),
1565 bus.read_word(scrap_style_ptr + Self::SCRAP_STYLE_SIZE_OFFSET) as i16,
1566 (
1567 bus.read_word(scrap_style_ptr + Self::SCRAP_STYLE_COLOR_OFFSET),
1568 bus.read_word(scrap_style_ptr + Self::SCRAP_STYLE_COLOR_OFFSET + 2),
1569 bus.read_word(scrap_style_ptr + Self::SCRAP_STYLE_COLOR_OFFSET + 4),
1570 ),
1571 bus.read_word(scrap_style_ptr + Self::SCRAP_STYLE_HEIGHT_OFFSET) as i16,
1572 bus.read_word(scrap_style_ptr + Self::SCRAP_STYLE_ASCENT_OFFSET) as i16,
1573 )
1574 }
1575
1576 fn te_write_style_table_element(
1577 bus: &mut MacMemoryBus,
1578 style_ptr: u32,
1579 style: TeResolvedStyle,
1580 ) {
1581 bus.write_word(style_ptr + Self::ST_ELEMENT_REFCOUNT_OFFSET, 1);
1582 bus.write_word(
1583 style_ptr + Self::ST_ELEMENT_HEIGHT_OFFSET,
1584 style.line_height as u16,
1585 );
1586 bus.write_word(
1587 style_ptr + Self::ST_ELEMENT_ASCENT_OFFSET,
1588 style.ascent as u16,
1589 );
1590 bus.write_word(style_ptr + Self::ST_ELEMENT_FONT_OFFSET, style.font as u16);
1591 bus.write_byte(style_ptr + Self::ST_ELEMENT_FACE_OFFSET, style.face as u8);
1592 bus.write_word(style_ptr + Self::ST_ELEMENT_SIZE_OFFSET, style.size as u16);
1593 bus.write_word(style_ptr + Self::ST_ELEMENT_COLOR_OFFSET, style.color.0);
1594 bus.write_word(style_ptr + Self::ST_ELEMENT_COLOR_OFFSET + 2, style.color.1);
1595 bus.write_word(style_ptr + Self::ST_ELEMENT_COLOR_OFFSET + 4, style.color.2);
1596 }
1597
1598 fn te_set_null_style(
1599 bus: &mut MacMemoryBus,
1600 te_handle: u32,
1601 mode: u16,
1602 text_style_ptr: u32,
1603 ) -> bool {
1604 let te_ptr = Self::te_record_ptr(bus, te_handle);
1605 if !Self::te_is_styled_record(bus, te_ptr) || text_style_ptr == 0 {
1606 return false;
1607 }
1608 let style_handle = Self::te_style_handle(bus, te_handle);
1609 let style_ptr = if style_handle != 0 {
1610 bus.read_long(style_handle)
1611 } else {
1612 0
1613 };
1614 let null_style_handle = if style_ptr != 0 {
1615 bus.read_long(style_ptr + Self::TE_STYLE_NULL_STYLE_OFFSET)
1616 } else {
1617 0
1618 };
1619 let null_style_ptr = if null_style_handle != 0 {
1620 bus.read_long(null_style_handle)
1621 } else {
1622 0
1623 };
1624 let null_scrap_handle = if null_style_ptr != 0 {
1625 bus.read_long(null_style_ptr + Self::NULL_STYLE_SCRAP_OFFSET)
1626 } else {
1627 0
1628 };
1629 let scrap_ptr = if null_scrap_handle != 0 {
1630 bus.read_long(null_scrap_handle)
1631 } else {
1632 0
1633 };
1634 if scrap_ptr == 0 {
1635 return false;
1636 }
1637
1638 let element = scrap_ptr + Self::SCRAP_STYLE_TAB_OFFSET;
1639 bus.write_word(scrap_ptr + Self::SCRAP_N_STYLES_OFFSET, 1);
1640 if (mode & 0x0001) != 0 {
1641 bus.write_word(
1642 element + Self::SCRAP_STYLE_FONT_OFFSET,
1643 bus.read_word(text_style_ptr),
1644 );
1645 }
1646 if (mode & 0x0002) != 0 {
1647 bus.write_byte(
1648 element + Self::SCRAP_STYLE_FACE_OFFSET,
1649 bus.read_byte(text_style_ptr + 2),
1650 );
1651 }
1652 if (mode & 0x0004) != 0 {
1653 bus.write_word(
1654 element + Self::SCRAP_STYLE_SIZE_OFFSET,
1655 bus.read_word(text_style_ptr + 4),
1656 );
1657 }
1658 if (mode & 0x0008) != 0 {
1659 for offset in [0u32, 2, 4] {
1660 bus.write_word(
1661 element + Self::SCRAP_STYLE_COLOR_OFFSET + offset,
1662 bus.read_word(text_style_ptr + 6 + offset),
1663 );
1664 }
1665 }
1666 let font = bus.read_word(element + Self::SCRAP_STYLE_FONT_OFFSET) as i16;
1667 let size =
1668 Self::font_lookup_size(bus.read_word(element + Self::SCRAP_STYLE_SIZE_OFFSET) as i16);
1669 let metrics = get_font_metrics(font, size);
1670 bus.write_word(
1671 element + Self::SCRAP_STYLE_HEIGHT_OFFSET,
1672 (metrics.ascent + metrics.descent + metrics.leading) as u16,
1673 );
1674 bus.write_word(
1675 element + Self::SCRAP_STYLE_ASCENT_OFFSET,
1676 metrics.ascent as u16,
1677 );
1678 true
1679 }
1680
1681 fn te_null_style_scrap_handle(bus: &MacMemoryBus, te_handle: u32) -> u32 {
1682 let style_handle = Self::te_style_handle(bus, te_handle);
1683 let style_ptr = if style_handle != 0 {
1684 bus.read_long(style_handle)
1685 } else {
1686 0
1687 };
1688 let null_style_handle = if style_ptr != 0 {
1689 bus.read_long(style_ptr + Self::TE_STYLE_NULL_STYLE_OFFSET)
1690 } else {
1691 0
1692 };
1693 let null_style_ptr = if null_style_handle != 0 {
1694 bus.read_long(null_style_handle)
1695 } else {
1696 0
1697 };
1698 if null_style_ptr != 0 {
1699 bus.read_long(null_style_ptr + Self::NULL_STYLE_SCRAP_OFFSET)
1700 } else {
1701 0
1702 }
1703 }
1704
1705 fn te_style_runs(
1706 &self,
1707 bus: &MacMemoryBus,
1708 te_handle: u32,
1709 text_len: usize,
1710 ) -> Vec<TeStyleRun> {
1711 let fallback = self.te_primary_resolved_style(bus, te_handle);
1712 let te_ptr = Self::te_record_ptr(bus, te_handle);
1713 if !Self::te_is_styled_record(bus, te_ptr) {
1714 return vec![TeStyleRun {
1715 start: 0,
1716 style_index: 0,
1717 style: fallback,
1718 }];
1719 }
1720
1721 let style_handle = Self::te_style_handle(bus, te_handle);
1722 let style_ptr = if style_handle != 0 {
1723 bus.read_long(style_handle)
1724 } else {
1725 0
1726 };
1727 if style_ptr == 0 {
1728 return vec![TeStyleRun {
1729 start: 0,
1730 style_index: 0,
1731 style: fallback,
1732 }];
1733 }
1734
1735 let style_table_handle = bus.read_long(style_ptr + Self::TE_STYLE_STYLE_TABLE_OFFSET);
1736 let style_table_ptr = if style_table_handle != 0 {
1737 bus.read_long(style_table_handle)
1738 } else {
1739 0
1740 };
1741 if style_table_ptr == 0 {
1742 return vec![TeStyleRun {
1743 start: 0,
1744 style_index: 0,
1745 style: fallback,
1746 }];
1747 }
1748
1749 let n_runs = bus.read_word(style_ptr + Self::TE_STYLE_N_RUNS_OFFSET) as usize;
1750 let n_styles = bus.read_word(style_ptr + Self::TE_STYLE_N_STYLES_OFFSET) as usize;
1751 let style_record_size = bus.get_alloc_size(style_ptr).unwrap_or(0);
1752 let style_table_size = bus.get_alloc_size(style_table_ptr).unwrap_or(0);
1753 let max_runs = style_record_size
1754 .saturating_sub(Self::TE_STYLE_RUNS_OFFSET)
1755 .checked_div(4)
1756 .unwrap_or(0) as usize;
1757 let max_styles = style_table_size
1758 .checked_div(Self::ST_ELEMENT_SIZE)
1759 .unwrap_or(0) as usize;
1760 let run_count = n_runs.min(max_runs);
1761 let style_count = n_styles.min(max_styles);
1762 if run_count == 0 || style_count == 0 {
1763 return vec![TeStyleRun {
1764 start: 0,
1765 style_index: 0,
1766 style: fallback,
1767 }];
1768 }
1769
1770 let mut runs = Vec::with_capacity(run_count);
1771 for run_index in 0..run_count {
1772 let run_ptr = style_ptr + Self::TE_STYLE_RUNS_OFFSET + (run_index as u32 * 4);
1773 let style_index_word = bus.read_word(run_ptr + 2);
1774 if style_index_word == 0xFFFF {
1775 break;
1776 }
1777 let style_index = style_index_word as usize;
1778 if style_index >= style_count {
1779 continue;
1780 }
1781 let style_element_ptr = style_table_ptr + (style_index as u32 * Self::ST_ELEMENT_SIZE);
1782 runs.push(TeStyleRun {
1783 start: (bus.read_word(run_ptr) as usize).min(text_len),
1784 style_index,
1785 style: Self::te_style_from_table_element(bus, style_element_ptr),
1786 });
1787 }
1788
1789 if runs.is_empty() {
1790 return vec![TeStyleRun {
1791 start: 0,
1792 style_index: 0,
1793 style: fallback,
1794 }];
1795 }
1796
1797 runs.sort_by_key(|run| run.start);
1798 let mut folded: Vec<TeStyleRun> = Vec::with_capacity(runs.len() + 1);
1799 for run in runs {
1800 if let Some(last) = folded.last_mut() {
1801 if last.start == run.start {
1802 *last = run;
1803 continue;
1804 }
1805 if last.style == run.style {
1806 continue;
1807 }
1808 }
1809 folded.push(run);
1810 }
1811
1812 if folded.first().is_none_or(|run| run.start != 0) {
1813 folded.insert(
1814 0,
1815 TeStyleRun {
1816 start: 0,
1817 style_index: 0,
1818 style: fallback,
1819 },
1820 );
1821 }
1822 folded
1823 }
1824
1825 fn te_style_at_offset(runs: &[TeStyleRun], offset: usize) -> TeResolvedStyle {
1826 let mut style = runs
1827 .first()
1828 .map(|run| run.style)
1829 .unwrap_or_else(|| Self::te_resolved_style_from_parts(0, 0, 0, (0, 0, 0), 0, 0));
1830 for run in runs {
1831 if run.start > offset {
1832 break;
1833 }
1834 style = run.style;
1835 }
1836 style
1837 }
1838
1839 fn te_measure_text_width_styled(
1840 &self,
1841 runs: &[TeStyleRun],
1842 text_bytes: &[u8],
1843 start: usize,
1844 end: usize,
1845 ) -> i16 {
1846 let start = start.min(text_bytes.len());
1847 let end = end.min(text_bytes.len());
1848 let mut width = 0i16;
1849 for (offset, &byte) in text_bytes[start..end].iter().enumerate() {
1850 let style = Self::te_style_at_offset(runs, start + offset);
1851 width = width.saturating_add(self.te_char_width(style.font, style.size, byte));
1852 }
1853 width
1854 }
1855
1856 fn te_next_break_styled(
1857 &self,
1858 runs: &[TeStyleRun],
1859 text_bytes: &[u8],
1860 offset: usize,
1861 max_width: i16,
1862 ) -> usize {
1863 let len = text_bytes.len();
1864 if offset >= len {
1865 return len;
1866 }
1867
1868 let mut width = 0i16;
1869 let mut index = offset;
1870 while width <= max_width && index < len && !matches!(text_bytes[index], b'\r' | b'\n') {
1871 let style = Self::te_style_at_offset(runs, index);
1872 width =
1873 width.saturating_add(self.te_char_width(style.font, style.size, text_bytes[index]));
1874 index += 1;
1875 }
1876
1877 let mut next = if width > max_width {
1878 let max_index = index.saturating_sub(1);
1879 if text_bytes[max_index] == b' ' {
1880 let mut scan = index;
1881 while scan < len && text_bytes[scan] == b' ' {
1882 scan += 1;
1883 }
1884 scan
1885 } else {
1886 let mut scan = max_index;
1887 while scan > offset && !Self::te_word_break_byte(text_bytes[scan - 1]) {
1888 scan -= 1;
1889 }
1890 if scan == offset {
1891 max_index
1892 } else {
1893 scan
1894 }
1895 }
1896 } else if index == len {
1897 len
1898 } else {
1899 index + 1
1900 };
1901
1902 if next == offset && offset < len {
1903 next += 1;
1904 }
1905 next
1906 }
1907
1908 fn te_wrap_lines_styled(
1909 &self,
1910 runs: &[TeStyleRun],
1911 text_bytes: &[u8],
1912 box_width: i16,
1913 ) -> Vec<(usize, usize)> {
1914 let mut lines = Vec::new();
1915 let mut line_start = 0usize;
1916 while line_start < text_bytes.len() {
1917 let next = self.te_next_break_styled(runs, text_bytes, line_start, box_width);
1918 lines.push((line_start, next.min(text_bytes.len())));
1919 line_start = next;
1920 }
1921 lines
1922 }
1923
1924 fn te_line_metrics_for_range(runs: &[TeStyleRun], start: usize, end: usize) -> (i16, i16) {
1925 if runs.is_empty() {
1926 return (0, 0);
1927 }
1928
1929 let mut line_height = 0i16;
1930 let mut ascent = 0i16;
1931 if start >= end {
1932 let style = Self::te_style_at_offset(runs, start);
1933 return (style.line_height, style.ascent);
1934 }
1935
1936 for offset in start..end {
1937 let style = Self::te_style_at_offset(runs, offset);
1938 line_height = line_height.max(style.line_height);
1939 ascent = ascent.max(style.ascent);
1940 }
1941 (line_height.max(1), ascent.max(0))
1942 }
1943
1944 fn te_update_styled_run_sentinel(bus: &mut MacMemoryBus, te_handle: u32, text_len: usize) {
1945 let style_handle = Self::te_style_handle(bus, te_handle);
1946 if style_handle == 0 {
1947 return;
1948 }
1949 let style_ptr = bus.read_long(style_handle);
1950 if style_ptr == 0 {
1951 return;
1952 }
1953 let n_runs = bus.read_word(style_ptr + Self::TE_STYLE_N_RUNS_OFFSET) as usize;
1954 let needed = Self::TE_STYLE_RUNS_OFFSET + ((n_runs as u32 + 1) * 4);
1955 let style_ptr = Self::ensure_handle_capacity(bus, style_handle, needed);
1956 if style_ptr == 0 {
1957 return;
1958 }
1959 let sentinel = style_ptr + Self::TE_STYLE_RUNS_OFFSET + (n_runs as u32 * 4);
1960 bus.write_word(
1961 sentinel,
1962 text_len.saturating_add(1).min(u16::MAX as usize) as u16,
1963 );
1964 bus.write_word(sentinel + 2, 0xFFFF);
1965 }
1966
1967 fn te_apply_style_scrap_to_range(
1968 &mut self,
1969 bus: &mut MacMemoryBus,
1970 te_handle: u32,
1971 style_scrap: u32,
1972 range_start: usize,
1973 range_len: usize,
1974 ) -> bool {
1975 let te_ptr = Self::te_record_ptr(bus, te_handle);
1976 if !Self::te_is_styled_record(bus, te_ptr) || style_scrap == 0 || range_len == 0 {
1977 return false;
1978 }
1979
1980 let text_len = Self::te_text_length(bus, te_handle);
1981 let range_start = range_start.min(text_len);
1982 let range_end = range_start.saturating_add(range_len).min(text_len);
1983 if range_start >= range_end {
1984 return false;
1985 }
1986
1987 let scrap_ptr = bus.read_long(style_scrap);
1988 if scrap_ptr == 0 {
1989 return false;
1990 }
1991 let scrap_size = bus.get_alloc_size(scrap_ptr).unwrap_or(0);
1992 if scrap_size < Self::SCRAP_STYLE_TAB_OFFSET {
1993 return false;
1994 }
1995 let style_count = (bus.read_word(scrap_ptr + Self::SCRAP_N_STYLES_OFFSET) as usize).min(
1996 scrap_size
1997 .saturating_sub(Self::SCRAP_STYLE_TAB_OFFSET)
1998 .checked_div(Self::SCRAP_STYLE_ELEMENT_SIZE)
1999 .unwrap_or(0) as usize,
2000 );
2001 if style_count == 0 {
2002 return false;
2003 }
2004 let existing_runs = self.te_style_runs(bus, te_handle, text_len);
2005 let mut candidates: Vec<(usize, u8, TeResolvedStyle)> =
2006 Vec::with_capacity(existing_runs.len() + style_count + 2);
2007 for run in &existing_runs {
2008 if run.start < range_start || run.start > range_end {
2009 candidates.push((run.start.min(text_len), 0, run.style));
2010 }
2011 }
2012
2013 for style_index in 0..style_count {
2014 let scrap_style_ptr = scrap_ptr
2015 + Self::SCRAP_STYLE_TAB_OFFSET
2016 + (style_index as u32 * Self::SCRAP_STYLE_ELEMENT_SIZE);
2017 let local_start =
2018 bus.read_long(scrap_style_ptr + Self::SCRAP_STYLE_START_CHAR_OFFSET) as usize;
2019 let run_start = range_start.saturating_add(local_start.min(range_end - range_start));
2020 candidates.push((
2021 run_start,
2022 1,
2023 Self::te_style_from_scrap_element(bus, scrap_style_ptr),
2024 ));
2025 }
2026
2027 if range_end < text_len {
2028 candidates.push((
2029 range_end,
2030 2,
2031 Self::te_style_at_offset(&existing_runs, range_end),
2032 ));
2033 }
2034 if !candidates.iter().any(|(start, _, _)| *start == 0) {
2035 candidates.push((0, 0, Self::te_style_at_offset(&existing_runs, 0)));
2036 }
2037
2038 candidates.sort_by_key(|(start, order, _)| (*start, *order));
2039 let mut merged: Vec<(usize, TeResolvedStyle)> = Vec::with_capacity(candidates.len());
2040 for (start, _, style) in candidates {
2041 let start = start.min(text_len);
2042 if let Some((last_start, last_style)) = merged.last_mut() {
2043 if *last_start == start {
2044 *last_style = style;
2045 continue;
2046 }
2047 if *last_style == style {
2048 continue;
2049 }
2050 }
2051 merged.push((start, style));
2052 }
2053
2054 if merged.is_empty() {
2055 merged.push((0, self.te_primary_resolved_style(bus, te_handle)));
2056 }
2057 if merged[0].0 != 0 {
2058 merged.insert(0, (0, Self::te_style_at_offset(&existing_runs, 0)));
2059 }
2060
2061 let run_count = merged.len().min(u16::MAX as usize);
2062 let style_handle = Self::te_style_handle(bus, te_handle);
2063 if style_handle == 0 {
2064 return false;
2065 }
2066 let style_ptr = Self::ensure_handle_capacity(
2067 bus,
2068 style_handle,
2069 Self::TE_STYLE_RUNS_OFFSET + ((run_count as u32 + 1) * 4),
2070 );
2071 if style_ptr == 0 {
2072 return false;
2073 }
2074
2075 let mut style_table_handle = bus.read_long(style_ptr + Self::TE_STYLE_STYLE_TABLE_OFFSET);
2076 if style_table_handle == 0 {
2077 style_table_handle = Self::allocate_handle_with_data(bus, 0);
2078 bus.write_long(
2079 style_ptr + Self::TE_STYLE_STYLE_TABLE_OFFSET,
2080 style_table_handle,
2081 );
2082 }
2083 let style_table_ptr = Self::ensure_handle_capacity(
2084 bus,
2085 style_table_handle,
2086 (run_count as u32) * Self::ST_ELEMENT_SIZE,
2087 );
2088 if style_table_ptr == 0 {
2089 return false;
2090 }
2091
2092 bus.write_word(style_ptr + Self::TE_STYLE_N_RUNS_OFFSET, run_count as u16);
2093 bus.write_word(style_ptr + Self::TE_STYLE_N_STYLES_OFFSET, run_count as u16);
2094 for (index, (start, style)) in merged.iter().take(run_count).enumerate() {
2095 let style_element_ptr = style_table_ptr + (index as u32 * Self::ST_ELEMENT_SIZE);
2096 Self::te_write_style_table_element(bus, style_element_ptr, *style);
2097 let run_ptr = style_ptr + Self::TE_STYLE_RUNS_OFFSET + (index as u32 * 4);
2098 bus.write_word(run_ptr, (*start).min(u16::MAX as usize) as u16);
2099 bus.write_word(run_ptr + 2, index.min(u16::MAX as usize) as u16);
2100 }
2101 Self::te_update_styled_run_sentinel(bus, te_handle, text_len);
2102 true
2103 }
2104
2105 #[allow(dead_code)]
2106 fn te_line_metrics(&self, bus: &MacMemoryBus, te_handle: u32) -> (i16, i16) {
2107 let te_ptr = Self::te_record_ptr(bus, te_handle);
2108 if te_ptr == 0 {
2109 let metrics = get_font_metrics(self.tx_font, Self::font_lookup_size(self.tx_size));
2110 return (
2111 metrics.ascent + metrics.descent + metrics.leading,
2112 metrics.ascent,
2113 );
2114 }
2115
2116 let line_height = bus.read_word(te_ptr + Self::TE_LINE_HEIGHT_OFFSET) as i16;
2117 let font_ascent = bus.read_word(te_ptr + Self::TE_FONT_ASCENT_OFFSET) as i16;
2118 if line_height > 0 && font_ascent >= 0 {
2119 return (line_height, font_ascent);
2120 }
2121
2122 let (_, _, _, _, resolved_line_height, resolved_ascent) =
2123 self.te_primary_style(bus, te_handle);
2124 (resolved_line_height, resolved_ascent)
2125 }
2126
2127 fn initialize_te_record(
2128 &mut self,
2129 bus: &mut MacMemoryBus,
2130 te_handle: u32,
2131 dest_rect: (i16, i16, i16, i16),
2132 view_rect: (i16, i16, i16, i16),
2133 ) {
2134 let te_ptr = Self::te_record_ptr(bus, te_handle);
2135 if te_ptr == 0 {
2136 return;
2137 }
2138
2139 let metrics = get_font_metrics(self.tx_font, Self::font_lookup_size(self.tx_size));
2140 let line_height = metrics.ascent + metrics.descent + metrics.leading;
2141 let h_text = bus.alloc(4);
2142 if h_text != 0 {
2143 bus.write_long(h_text, 0);
2144 }
2145
2146 Self::te_write_rect_words(bus, te_ptr + Self::TE_DEST_RECT_OFFSET, dest_rect);
2147 Self::te_write_rect_words(bus, te_ptr + Self::TE_VIEW_RECT_OFFSET, view_rect);
2148 Self::te_write_rect_words(bus, te_ptr + Self::TE_SEL_RECT_OFFSET, dest_rect);
2149 bus.write_word(te_ptr + Self::TE_LINE_HEIGHT_OFFSET, line_height as u16);
2150 bus.write_word(te_ptr + Self::TE_FONT_ASCENT_OFFSET, metrics.ascent as u16);
2151 bus.write_word(te_ptr + Self::TE_SEL_POINT_OFFSET, dest_rect.0 as u16);
2152 bus.write_word(te_ptr + Self::TE_SEL_POINT_OFFSET + 2, dest_rect.1 as u16);
2153 bus.write_word(te_ptr + Self::TE_SEL_START_OFFSET, 0);
2154 bus.write_word(te_ptr + Self::TE_SEL_END_OFFSET, 0);
2155 bus.write_long(te_ptr + Self::TE_CARET_TIME_OFFSET, self.tick_count);
2156 bus.write_word(te_ptr + Self::TE_CARET_STATE_OFFSET, 0);
2157 bus.write_word(te_ptr + Self::TE_LENGTH_OFFSET, 0);
2158 bus.write_long(te_ptr + Self::TE_HTEXT_OFFSET, h_text);
2159 bus.write_word(te_ptr + Self::TE_TX_FONT_OFFSET, self.tx_font as u16);
2160 bus.write_byte(te_ptr + Self::TE_TX_FACE_OFFSET, self.tx_face as u8);
2161 bus.write_word(te_ptr + Self::TE_TX_MODE_OFFSET, self.tx_mode as u16);
2162 bus.write_word(te_ptr + Self::TE_TX_SIZE_OFFSET, self.tx_size as u16);
2163 bus.write_long(te_ptr + Self::TE_IN_PORT_OFFSET, self.current_port);
2164 self.textedit_states.remove(&te_handle);
2165 }
2166
2167 fn initialize_styled_te_record(
2168 &mut self,
2169 bus: &mut MacMemoryBus,
2170 te_handle: u32,
2171 dest_rect: (i16, i16, i16, i16),
2172 view_rect: (i16, i16, i16, i16),
2173 ) {
2174 let te_ptr = Self::te_record_ptr(bus, te_handle);
2175 if te_ptr == 0 {
2176 return;
2177 }
2178
2179 let style_size = Self::font_lookup_size(self.tx_size);
2180 let metrics = get_font_metrics(self.tx_font, style_size);
2181 let line_height = metrics.ascent + metrics.descent + metrics.leading;
2182
2183 let h_text = Self::allocate_handle_with_data(bus, 0);
2184 let style_table = Self::allocate_handle_with_data(bus, Self::ST_ELEMENT_SIZE);
2185 let lh_table = Self::allocate_handle_with_data(bus, Self::LH_ELEMENT_SIZE);
2186 let null_scrap = Self::allocate_handle_with_data(bus, Self::STYLE_SCRAP_REC_SIZE);
2187 let null_style = Self::allocate_handle_with_data(bus, Self::NULL_STYLE_REC_SIZE);
2188 let style_handle = Self::allocate_handle_with_data(bus, 0x1C);
2189
2190 let style_table_ptr = if style_table != 0 {
2191 bus.read_long(style_table)
2192 } else {
2193 0
2194 };
2195 if style_table_ptr != 0 {
2196 bus.write_word(style_table_ptr + Self::ST_ELEMENT_REFCOUNT_OFFSET, 1);
2197 bus.write_word(
2198 style_table_ptr + Self::ST_ELEMENT_HEIGHT_OFFSET,
2199 line_height as u16,
2200 );
2201 bus.write_word(
2202 style_table_ptr + Self::ST_ELEMENT_ASCENT_OFFSET,
2203 metrics.ascent as u16,
2204 );
2205 bus.write_word(
2206 style_table_ptr + Self::ST_ELEMENT_FONT_OFFSET,
2207 self.tx_font as u16,
2208 );
2209 bus.write_byte(
2210 style_table_ptr + Self::ST_ELEMENT_FACE_OFFSET,
2211 self.tx_face as u8,
2212 );
2213 bus.write_word(
2214 style_table_ptr + Self::ST_ELEMENT_SIZE_OFFSET,
2215 style_size as u16,
2216 );
2217 bus.write_word(
2218 style_table_ptr + Self::ST_ELEMENT_COLOR_OFFSET,
2219 self.fg_color.0,
2220 );
2221 bus.write_word(
2222 style_table_ptr + Self::ST_ELEMENT_COLOR_OFFSET + 2,
2223 self.fg_color.1,
2224 );
2225 bus.write_word(
2226 style_table_ptr + Self::ST_ELEMENT_COLOR_OFFSET + 4,
2227 self.fg_color.2,
2228 );
2229 }
2230
2231 let lh_table_ptr = if lh_table != 0 {
2232 bus.read_long(lh_table)
2233 } else {
2234 0
2235 };
2236 if lh_table_ptr != 0 {
2237 bus.write_word(
2238 lh_table_ptr + Self::LH_ELEMENT_HEIGHT_OFFSET,
2239 line_height as u16,
2240 );
2241 bus.write_word(
2242 lh_table_ptr + Self::LH_ELEMENT_ASCENT_OFFSET,
2243 metrics.ascent as u16,
2244 );
2245 }
2246
2247 let null_scrap_ptr = if null_scrap != 0 {
2248 bus.read_long(null_scrap)
2249 } else {
2250 0
2251 };
2252 if null_scrap_ptr != 0 {
2253 bus.write_word(null_scrap_ptr + Self::SCRAP_N_STYLES_OFFSET, 0);
2254 bus.write_long(
2255 null_scrap_ptr + Self::SCRAP_STYLE_TAB_OFFSET + Self::SCRAP_STYLE_START_CHAR_OFFSET,
2256 0,
2257 );
2258 bus.write_word(
2259 null_scrap_ptr + Self::SCRAP_STYLE_TAB_OFFSET + Self::SCRAP_STYLE_HEIGHT_OFFSET,
2260 line_height as u16,
2261 );
2262 bus.write_word(
2263 null_scrap_ptr + Self::SCRAP_STYLE_TAB_OFFSET + Self::SCRAP_STYLE_ASCENT_OFFSET,
2264 metrics.ascent as u16,
2265 );
2266 bus.write_word(
2267 null_scrap_ptr + Self::SCRAP_STYLE_TAB_OFFSET + Self::SCRAP_STYLE_FONT_OFFSET,
2268 self.tx_font as u16,
2269 );
2270 bus.write_byte(
2271 null_scrap_ptr + Self::SCRAP_STYLE_TAB_OFFSET + Self::SCRAP_STYLE_FACE_OFFSET,
2272 self.tx_face as u8,
2273 );
2274 bus.write_word(
2275 null_scrap_ptr + Self::SCRAP_STYLE_TAB_OFFSET + Self::SCRAP_STYLE_SIZE_OFFSET,
2276 style_size as u16,
2277 );
2278 bus.write_word(
2279 null_scrap_ptr + Self::SCRAP_STYLE_TAB_OFFSET + Self::SCRAP_STYLE_COLOR_OFFSET,
2280 self.fg_color.0,
2281 );
2282 bus.write_word(
2283 null_scrap_ptr + Self::SCRAP_STYLE_TAB_OFFSET + Self::SCRAP_STYLE_COLOR_OFFSET + 2,
2284 self.fg_color.1,
2285 );
2286 bus.write_word(
2287 null_scrap_ptr + Self::SCRAP_STYLE_TAB_OFFSET + Self::SCRAP_STYLE_COLOR_OFFSET + 4,
2288 self.fg_color.2,
2289 );
2290 }
2291
2292 let null_style_ptr = if null_style != 0 {
2293 bus.read_long(null_style)
2294 } else {
2295 0
2296 };
2297 if null_style_ptr != 0 {
2298 bus.write_long(null_style_ptr + Self::NULL_STYLE_SCRAP_OFFSET, null_scrap);
2299 }
2300
2301 let style_ptr = if style_handle != 0 {
2302 bus.read_long(style_handle)
2303 } else {
2304 0
2305 };
2306 if style_ptr != 0 {
2307 bus.write_word(style_ptr + Self::TE_STYLE_N_RUNS_OFFSET, 1);
2308 bus.write_word(style_ptr + Self::TE_STYLE_N_STYLES_OFFSET, 1);
2309 bus.write_long(style_ptr + Self::TE_STYLE_STYLE_TABLE_OFFSET, style_table);
2310 bus.write_long(style_ptr + Self::TE_STYLE_LH_TABLE_OFFSET, lh_table);
2311 bus.write_long(style_ptr + Self::TE_STYLE_NULL_STYLE_OFFSET, null_style);
2312 bus.write_word(style_ptr + Self::TE_STYLE_RUNS_OFFSET, 0);
2313 bus.write_word(style_ptr + Self::TE_STYLE_RUNS_OFFSET + 2, 0);
2314 bus.write_word(style_ptr + Self::TE_STYLE_RUNS_OFFSET + 4, 1);
2315 bus.write_word(style_ptr + Self::TE_STYLE_RUNS_OFFSET + 6, 0xFFFF);
2316 }
2317
2318 Self::te_write_rect_words(bus, te_ptr + Self::TE_DEST_RECT_OFFSET, dest_rect);
2319 Self::te_write_rect_words(bus, te_ptr + Self::TE_VIEW_RECT_OFFSET, view_rect);
2320 Self::te_write_rect_words(bus, te_ptr + Self::TE_SEL_RECT_OFFSET, dest_rect);
2321 bus.write_word(te_ptr + Self::TE_LINE_HEIGHT_OFFSET, 0xFFFF);
2322 bus.write_word(te_ptr + Self::TE_FONT_ASCENT_OFFSET, 0xFFFF);
2323 bus.write_word(te_ptr + Self::TE_SEL_POINT_OFFSET, dest_rect.0 as u16);
2324 bus.write_word(te_ptr + Self::TE_SEL_POINT_OFFSET + 2, dest_rect.1 as u16);
2325 bus.write_word(te_ptr + Self::TE_SEL_START_OFFSET, 0);
2326 bus.write_word(te_ptr + Self::TE_SEL_END_OFFSET, 0);
2327 bus.write_long(te_ptr + Self::TE_CARET_TIME_OFFSET, self.tick_count);
2328 bus.write_word(te_ptr + Self::TE_CARET_STATE_OFFSET, 0);
2329 bus.write_word(te_ptr + Self::TE_JUST_OFFSET, 0);
2330 bus.write_word(te_ptr + Self::TE_LENGTH_OFFSET, 0);
2331 bus.write_long(te_ptr + Self::TE_HTEXT_OFFSET, h_text);
2332 bus.write_long(te_ptr + Self::TE_TX_FONT_OFFSET, style_handle);
2333 bus.write_word(te_ptr + Self::TE_TX_MODE_OFFSET, self.tx_mode as u16);
2334 bus.write_word(te_ptr + Self::TE_TX_SIZE_OFFSET, 0xFFFF);
2335 bus.write_long(te_ptr + Self::TE_IN_PORT_OFFSET, self.current_port);
2336 bus.write_word(te_ptr + Self::TE_N_LINES_OFFSET, 0);
2337 bus.write_word(te_ptr + Self::TE_LINE_STARTS_OFFSET, 0);
2338 self.textedit_states.remove(&te_handle);
2339 }
2340
2341 fn te_text_length(bus: &MacMemoryBus, te_handle: u32) -> usize {
2342 let te_ptr = Self::te_record_ptr(bus, te_handle);
2343 if te_ptr == 0 {
2344 return 0;
2345 }
2346
2347 let stored = bus.read_word(te_ptr + Self::TE_LENGTH_OFFSET) as usize;
2348 if stored != 0 {
2349 return stored;
2350 }
2351
2352 let h_text = bus.read_long(te_ptr + Self::TE_HTEXT_OFFSET);
2353 if h_text == 0 {
2354 return 0;
2355 }
2356 let text_ptr = bus.read_long(h_text);
2357 if text_ptr == 0 {
2358 0
2359 } else {
2360 bus.get_alloc_size(text_ptr).unwrap_or(0) as usize
2361 }
2362 }
2363
2364 fn te_text_bytes(bus: &MacMemoryBus, te_handle: u32) -> Vec<u8> {
2365 let len = Self::te_text_length(bus, te_handle);
2366 if len == 0 {
2367 return Vec::new();
2368 }
2369
2370 let h_text = Self::te_text_handle(bus, te_handle);
2371 if h_text == 0 {
2372 return Vec::new();
2373 }
2374 let text_ptr = bus.read_long(h_text);
2375 if text_ptr == 0 {
2376 return Vec::new();
2377 }
2378 bus.read_bytes(text_ptr, len)
2379 }
2380
2381 pub(crate) fn te_find_word_bounds(
2382 &self,
2383 bus: &MacMemoryBus,
2384 te_handle: u32,
2385 current_pos: usize,
2386 ) -> (u16, u16) {
2387 let text_bytes = Self::te_text_bytes(bus, te_handle);
2388 let len = text_bytes.len();
2389 if len == 0 {
2390 return (0, 0);
2391 }
2392
2393 let pos = current_pos.min(len);
2394 if pos < len && Self::te_word_break_byte(text_bytes[pos]) {
2395 return (pos as u16, pos as u16);
2396 }
2397
2398 let mut start = pos;
2399 while start > 0 && !Self::te_word_break_byte(text_bytes[start - 1]) {
2400 start -= 1;
2401 }
2402
2403 let mut end = pos;
2404 while end < len && !Self::te_word_break_byte(text_bytes[end]) {
2405 end += 1;
2406 }
2407
2408 (start as u16, end as u16)
2409 }
2410
2411 #[allow(dead_code)]
2412 fn te_reset_styled_metadata(
2413 &mut self,
2414 bus: &mut MacMemoryBus,
2415 te_handle: u32,
2416 text_len: usize,
2417 ) {
2418 let style_handle = Self::te_style_handle(bus, te_handle);
2419 if style_handle == 0 {
2420 return;
2421 }
2422
2423 let (font, face, size, color, line_height, ascent) = self.te_primary_style(bus, te_handle);
2424 let style_ptr = bus.read_long(style_handle);
2425 if style_ptr == 0 {
2426 return;
2427 }
2428
2429 let style_table_handle = bus.read_long(style_ptr + Self::TE_STYLE_STYLE_TABLE_OFFSET);
2430 let style_table_ptr = if style_table_handle != 0 {
2431 bus.read_long(style_table_handle)
2432 } else {
2433 0
2434 };
2435 if style_table_ptr != 0 {
2436 bus.write_word(style_table_ptr + Self::ST_ELEMENT_REFCOUNT_OFFSET, 1);
2437 bus.write_word(
2438 style_table_ptr + Self::ST_ELEMENT_HEIGHT_OFFSET,
2439 line_height as u16,
2440 );
2441 bus.write_word(
2442 style_table_ptr + Self::ST_ELEMENT_ASCENT_OFFSET,
2443 ascent as u16,
2444 );
2445 bus.write_word(style_table_ptr + Self::ST_ELEMENT_FONT_OFFSET, font as u16);
2446 bus.write_byte(style_table_ptr + Self::ST_ELEMENT_FACE_OFFSET, face as u8);
2447 bus.write_word(style_table_ptr + Self::ST_ELEMENT_SIZE_OFFSET, size as u16);
2448 bus.write_word(style_table_ptr + Self::ST_ELEMENT_COLOR_OFFSET, color.0);
2449 bus.write_word(style_table_ptr + Self::ST_ELEMENT_COLOR_OFFSET + 2, color.1);
2450 bus.write_word(style_table_ptr + Self::ST_ELEMENT_COLOR_OFFSET + 4, color.2);
2451 }
2452
2453 let lh_handle = bus.read_long(style_ptr + Self::TE_STYLE_LH_TABLE_OFFSET);
2454 let lh_ptr = if lh_handle != 0 {
2455 bus.read_long(lh_handle)
2456 } else {
2457 0
2458 };
2459 if lh_ptr != 0 {
2460 bus.write_word(lh_ptr + Self::LH_ELEMENT_HEIGHT_OFFSET, line_height as u16);
2461 bus.write_word(lh_ptr + Self::LH_ELEMENT_ASCENT_OFFSET, ascent as u16);
2462 }
2463
2464 bus.write_word(style_ptr + Self::TE_STYLE_N_RUNS_OFFSET, 1);
2465 bus.write_word(style_ptr + Self::TE_STYLE_N_STYLES_OFFSET, 1);
2466 bus.write_word(style_ptr + Self::TE_STYLE_RUNS_OFFSET, 0);
2467 bus.write_word(style_ptr + Self::TE_STYLE_RUNS_OFFSET + 2, 0);
2468 bus.write_word(
2469 style_ptr + Self::TE_STYLE_RUNS_OFFSET + 4,
2470 text_len.saturating_add(1).min(u16::MAX as usize) as u16,
2471 );
2472 bus.write_word(style_ptr + Self::TE_STYLE_RUNS_OFFSET + 6, 0xFFFF);
2473 }
2474
2475 fn te_set_text_contents(&mut self, bus: &mut MacMemoryBus, te_handle: u32, text: &[u8]) {
2476 let te_ptr = Self::te_record_ptr(bus, te_handle);
2477 if te_ptr == 0 {
2478 return;
2479 }
2480
2481 let mut h_text = bus.read_long(te_ptr + Self::TE_HTEXT_OFFSET);
2482 if h_text == 0 {
2483 h_text = Self::allocate_handle_with_data(bus, 0);
2484 bus.write_long(te_ptr + Self::TE_HTEXT_OFFSET, h_text);
2485 }
2486
2487 let text_ptr = Self::ensure_text_handle_size(bus, h_text, text.len());
2488 if !text.is_empty() && text_ptr != 0 {
2489 bus.write_bytes(text_ptr, text);
2490 }
2491
2492 let clamped_len = text.len().min(u16::MAX as usize) as u16;
2493 bus.write_word(te_ptr + Self::TE_LENGTH_OFFSET, clamped_len);
2494 bus.write_word(te_ptr + Self::TE_SEL_START_OFFSET, clamped_len);
2495 bus.write_word(te_ptr + Self::TE_SEL_END_OFFSET, clamped_len);
2496 self.te_recalculate_layout(bus, te_handle);
2497 }
2498
2499 fn textedit_idle(&mut self, cpu: &mut impl CpuOps, bus: &mut MacMemoryBus, te_handle: u32) {
2500 if trace_textedit_enabled() {
2501 eprintln!("[TE] TEIdle hTE=${te_handle:08X}");
2502 }
2503 let te_ptr = Self::te_record_ptr(bus, te_handle);
2504 if te_ptr == 0 {
2505 return;
2506 }
2507 if bus.read_word(te_ptr + Self::TE_ACTIVE_OFFSET) == 0 {
2508 return;
2509 }
2510
2511 let sel_start = bus.read_word(te_ptr + Self::TE_SEL_START_OFFSET);
2512 let sel_end = bus.read_word(te_ptr + Self::TE_SEL_END_OFFSET);
2513 if sel_start != sel_end {
2514 return;
2515 }
2516
2517 let last_toggle = bus.read_long(te_ptr + Self::TE_CARET_TIME_OFFSET);
2518 if self.tick_count.wrapping_sub(last_toggle) < Self::TE_CARET_BLINK_TICKS {
2519 return;
2520 }
2521
2522 let caret_state = bus.read_word(te_ptr + Self::TE_CARET_STATE_OFFSET);
2523 bus.write_word(
2524 te_ptr + Self::TE_CARET_STATE_OFFSET,
2525 if caret_state == 0 { 1 } else { 0 },
2526 );
2527 bus.write_long(te_ptr + Self::TE_CARET_TIME_OFFSET, self.tick_count);
2528 self.draw_te_contents(cpu, bus, te_handle);
2529 }
2530
2531 fn te_insert_text(&mut self, bus: &mut MacMemoryBus, te_handle: u32, text: &[u8]) {
2532 let te_ptr = Self::te_record_ptr(bus, te_handle);
2533 if te_ptr == 0 {
2534 return;
2535 }
2536
2537 let existing = Self::te_text_bytes(bus, te_handle);
2538 let mut sel_start = bus.read_word(te_ptr + Self::TE_SEL_START_OFFSET) as usize;
2539 let mut sel_end = bus.read_word(te_ptr + Self::TE_SEL_END_OFFSET) as usize;
2540 let text_len = existing.len();
2541 sel_start = sel_start.min(text_len);
2542 sel_end = sel_end.min(text_len);
2543 if sel_end < sel_start {
2544 std::mem::swap(&mut sel_start, &mut sel_end);
2545 }
2546
2547 let mut merged =
2548 Vec::with_capacity(sel_start + text.len() + text_len.saturating_sub(sel_end));
2549 merged.extend_from_slice(&existing[..sel_start]);
2550 merged.extend_from_slice(text);
2551 merged.extend_from_slice(&existing[sel_end..]);
2552 self.te_set_text_contents(bus, te_handle, &merged);
2553
2554 let insertion_end = (sel_start + text.len()).min(u16::MAX as usize) as u16;
2555 bus.write_word(te_ptr + Self::TE_SEL_START_OFFSET, insertion_end);
2556 bus.write_word(te_ptr + Self::TE_SEL_END_OFFSET, insertion_end);
2557 }
2558
2559 fn te_char_width(&self, font: i16, size: i16, byte: u8) -> i16 {
2560 let size = Self::font_lookup_size(size);
2561 let (_face, scale) = get_font_face_scaled(font, size);
2562 let ch = byte as char;
2563 if let Some((glyph, _)) = crate::quickdraw::text::get_glyph(font, size, ch) {
2564 self.glyph_advance(glyph) * scale
2565 } else {
2566 self.missing_glyph_advance() * scale
2567 }
2568 }
2569
2570 fn te_measure_text_width(
2571 &self,
2572 font: i16,
2573 size: i16,
2574 text_bytes: &[u8],
2575 start: usize,
2576 end: usize,
2577 ) -> i16 {
2578 let mut width = 0i16;
2579 for &b in &text_bytes[start..end] {
2580 width += self.te_char_width(font, size, b);
2581 }
2582 width
2583 }
2584
2585 fn te_word_break_byte(byte: u8) -> bool {
2586 byte <= 0x20
2587 }
2588
2589 fn te_next_break(
2590 &self,
2591 font: i16,
2592 size: i16,
2593 text_bytes: &[u8],
2594 offset: usize,
2595 max_width: i16,
2596 ) -> usize {
2597 let len = text_bytes.len();
2598 if offset >= len {
2599 return len;
2600 }
2601
2602 let mut width = 0i16;
2603 let mut index = offset;
2604 while width <= max_width && index < len && !matches!(text_bytes[index], b'\r' | b'\n') {
2605 width = width.saturating_add(self.te_char_width(font, size, text_bytes[index]));
2606 index += 1;
2607 }
2608
2609 let mut next = if width > max_width {
2610 let max_index = index.saturating_sub(1);
2611 if text_bytes[max_index] == b' ' {
2612 let mut scan = index;
2613 while scan < len && text_bytes[scan] == b' ' {
2614 scan += 1;
2615 }
2616 scan
2617 } else {
2618 let mut scan = max_index;
2619 while scan > offset && !Self::te_word_break_byte(text_bytes[scan - 1]) {
2620 scan -= 1;
2621 }
2622 if scan == offset {
2623 max_index
2624 } else {
2625 scan
2626 }
2627 }
2628 } else if index == len {
2629 len
2630 } else {
2631 index + 1
2632 };
2633
2634 if next == offset && offset < len {
2635 next += 1;
2636 }
2637 next
2638 }
2639
2640 fn te_wrap_lines(
2641 &self,
2642 font: i16,
2643 size: i16,
2644 text_bytes: &[u8],
2645 box_width: i16,
2646 ) -> Vec<(usize, usize)> {
2647 let mut lines: Vec<(usize, usize)> = Vec::new();
2648 let mut line_start = 0usize;
2649 while line_start < text_bytes.len() {
2650 let next = self.te_next_break(font, size, text_bytes, line_start, box_width);
2651 lines.push((line_start, next.min(text_bytes.len())));
2652 line_start = next;
2653 }
2654
2655 lines
2656 }
2657
2658 fn te_recalculate_layout(&mut self, bus: &mut MacMemoryBus, te_handle: u32) {
2659 let te_ptr = Self::te_record_ptr(bus, te_handle);
2660 if te_ptr == 0 {
2661 return;
2662 }
2663
2664 let text_bytes = Self::te_text_bytes(bus, te_handle);
2665 let text_len = text_bytes.len();
2666 let dest_rect = Self::te_read_rect(bus, te_ptr + Self::TE_DEST_RECT_OFFSET);
2667 let (font, _, size, _, line_height, font_ascent) = self.te_primary_style(bus, te_handle);
2668 let styled = Self::te_is_styled_record(bus, te_ptr);
2669 let style_runs = if styled {
2670 self.te_style_runs(bus, te_handle, text_len)
2671 } else {
2672 Vec::new()
2673 };
2674 let uses_styled_runs = styled && !style_runs.is_empty();
2675 let lines = if text_bytes.is_empty() {
2676 Vec::new()
2677 } else if uses_styled_runs {
2678 self.te_wrap_lines_styled(&style_runs, &text_bytes, (dest_rect.3 - dest_rect.1).max(0))
2679 } else {
2680 self.te_wrap_lines(font, size, &text_bytes, (dest_rect.3 - dest_rect.1).max(0))
2681 };
2682 let line_count = lines.len();
2683 let te_ptr = Self::ensure_te_record_line_capacity(bus, te_handle, line_count);
2684 if te_ptr == 0 {
2685 return;
2686 }
2687
2688 bus.write_word(
2689 te_ptr + Self::TE_N_LINES_OFFSET,
2690 line_count.min(u16::MAX as usize) as u16,
2691 );
2692 if styled {
2693 bus.write_word(te_ptr + Self::TE_LINE_HEIGHT_OFFSET, 0xFFFF);
2694 bus.write_word(te_ptr + Self::TE_FONT_ASCENT_OFFSET, 0xFFFF);
2695 } else {
2696 bus.write_word(te_ptr + Self::TE_LINE_HEIGHT_OFFSET, line_height as u16);
2697 bus.write_word(te_ptr + Self::TE_FONT_ASCENT_OFFSET, font_ascent as u16);
2698 }
2699
2700 for index in 0..=line_count.saturating_add(1) {
2701 bus.write_word(te_ptr + Self::TE_LINE_STARTS_OFFSET + (index as u32 * 2), 0);
2702 }
2703 for (index, (start, _)) in lines.iter().enumerate() {
2704 bus.write_word(
2705 te_ptr + Self::TE_LINE_STARTS_OFFSET + (index as u32 * 2),
2706 (*start).min(u16::MAX as usize) as u16,
2707 );
2708 }
2709 bus.write_word(
2710 te_ptr + Self::TE_LINE_STARTS_OFFSET + (line_count as u32 * 2),
2711 text_len.min(u16::MAX as usize) as u16,
2712 );
2713 bus.write_word(
2714 te_ptr + Self::TE_LINE_STARTS_OFFSET + ((line_count as u32 + 1) * 2),
2715 0,
2716 );
2717
2718 if styled {
2719 let style_handle = Self::te_style_handle(bus, te_handle);
2720 if style_handle != 0 {
2721 let style_ptr = bus.read_long(style_handle);
2722 if style_ptr != 0 {
2723 let lh_handle = bus.read_long(style_ptr + Self::TE_STYLE_LH_TABLE_OFFSET);
2724 if lh_handle != 0 {
2725 let lh_ptr = Self::ensure_handle_capacity(
2726 bus,
2727 lh_handle,
2728 ((line_count + 1) as u32) * Self::LH_ELEMENT_SIZE,
2729 );
2730 if lh_ptr != 0 {
2731 for index in 0..=line_count {
2732 let (line_height, font_ascent) = if uses_styled_runs
2733 && index < lines.len()
2734 {
2735 let (start, end) = lines[index];
2736 Self::te_line_metrics_for_range(&style_runs, start, end)
2737 } else if uses_styled_runs {
2738 Self::te_line_metrics_for_range(&style_runs, text_len, text_len)
2739 } else {
2740 (line_height, font_ascent)
2741 };
2742 let element = lh_ptr + (index as u32 * Self::LH_ELEMENT_SIZE);
2743 bus.write_word(
2744 element + Self::LH_ELEMENT_HEIGHT_OFFSET,
2745 line_height as u16,
2746 );
2747 bus.write_word(
2748 element + Self::LH_ELEMENT_ASCENT_OFFSET,
2749 font_ascent as u16,
2750 );
2751 }
2752 }
2753 }
2754 }
2755 }
2756 }
2757
2758 if styled {
2759 Self::te_update_styled_run_sentinel(bus, te_handle, text_len);
2760 }
2761 }
2762
2763 fn te_line_origin_x(just: i16, box_left: i16, box_right: i16, line_width: i16) -> i16 {
2764 match just {
2771 1 => box_left + ((box_right - box_left) - line_width) / 2,
2772 -1 => box_right - line_width,
2773 _ => box_left.saturating_add(Self::TE_LINE_LEFT_INSET), }
2775 }
2776
2777 fn port_has_drawable_bitmap(bus: &MacMemoryBus, port: u32) -> bool {
2778 if port == 0 {
2779 return false;
2780 }
2781
2782 let port_version = bus.read_word(port + 6);
2783 let is_color = (port_version & 0xC000) != 0;
2784 let (base, row_bytes, top, left, bottom, right) = if is_color {
2785 let pix_map_handle = bus.read_long(port + 2);
2786 if pix_map_handle == 0 {
2787 return false;
2788 }
2789 let pix_map_ptr = bus.read_long(pix_map_handle);
2790 if pix_map_ptr == 0 {
2791 return false;
2792 }
2793 (
2794 bus.read_long(pix_map_ptr),
2795 bus.read_word(pix_map_ptr + 4) & 0x3FFF,
2796 bus.read_word(pix_map_ptr + 6) as i16,
2797 bus.read_word(pix_map_ptr + 8) as i16,
2798 bus.read_word(pix_map_ptr + 10) as i16,
2799 bus.read_word(pix_map_ptr + 12) as i16,
2800 )
2801 } else {
2802 (
2803 bus.read_long(port + 2),
2804 bus.read_word(port + 6) & 0x3FFF,
2805 bus.read_word(port + 8) as i16,
2806 bus.read_word(port + 10) as i16,
2807 bus.read_word(port + 12) as i16,
2808 bus.read_word(port + 14) as i16,
2809 )
2810 };
2811
2812 base != 0 && row_bytes != 0 && top < bottom && left < right
2813 }
2814
2815 fn draw_te_contents(&mut self, cpu: &mut impl CpuOps, bus: &mut MacMemoryBus, te_handle: u32) {
2816 let te_ptr = Self::te_record_ptr(bus, te_handle);
2817 if te_ptr == 0 {
2818 return;
2819 }
2820
2821 let text_bytes = Self::te_text_bytes(bus, te_handle);
2822 let dest_rect = Self::te_read_rect(bus, te_ptr + Self::TE_DEST_RECT_OFFSET);
2823 let view_rect = Self::te_read_rect(bus, te_ptr + Self::TE_VIEW_RECT_OFFSET);
2824 let te_port = bus.read_long(te_ptr + Self::TE_IN_PORT_OFFSET);
2825 if !Self::port_has_drawable_bitmap(bus, te_port) {
2826 return;
2827 }
2828 let just = bus.read_word(te_ptr + Self::TE_JUST_OFFSET) as i16;
2829 let (font, face, size, color, _, _) = self.te_primary_style(bus, te_handle);
2830 let styled_runs = if Self::te_is_styled_record(bus, te_ptr) {
2831 self.te_style_runs(bus, te_handle, text_bytes.len())
2832 } else {
2833 Vec::new()
2834 };
2835 let uses_styled_runs = !styled_runs.is_empty() && Self::te_is_styled_record(bus, te_ptr);
2836 let old_font = self.tx_font;
2837 let old_face = self.tx_face;
2838 let old_size = self.tx_size;
2839 let old_fg = self.fg_color;
2840 let old_loc = self.pn_loc;
2841 let previous_port = self.current_port;
2842 let previous_gdevice = self.current_gdevice;
2843 let switched_port = te_port != 0 && te_port != previous_port;
2844
2845 if te_port != 0 {
2849 self.set_current_port_state(bus, cpu, te_port, None);
2850 }
2851 if trace_textedit_enabled() {
2852 let (vis_top, vis_left, vis_bottom, vis_right) = {
2853 let vis_handle = bus.read_long(self.current_port + 24);
2854 let vis_ptr = if vis_handle != 0 {
2855 bus.read_long(vis_handle)
2856 } else {
2857 0
2858 };
2859 if vis_ptr != 0 {
2860 (
2861 bus.read_word(vis_ptr + 2) as i16,
2862 bus.read_word(vis_ptr + 4) as i16,
2863 bus.read_word(vis_ptr + 6) as i16,
2864 bus.read_word(vis_ptr + 8) as i16,
2865 )
2866 } else {
2867 (0, 0, 0, 0)
2868 }
2869 };
2870 let (clip_top, clip_left, clip_bottom, clip_right) = {
2871 let clip_handle = bus.read_long(self.current_port + 28);
2872 let clip_ptr = if clip_handle != 0 {
2873 bus.read_long(clip_handle)
2874 } else {
2875 0
2876 };
2877 if clip_ptr != 0 {
2878 (
2879 bus.read_word(clip_ptr + 2) as i16,
2880 bus.read_word(clip_ptr + 4) as i16,
2881 bus.read_word(clip_ptr + 6) as i16,
2882 bus.read_word(clip_ptr + 8) as i16,
2883 )
2884 } else {
2885 (0, 0, 0, 0)
2886 }
2887 };
2888 eprintln!(
2889 "[TE] draw_te_contents hTE=${:08X} te_port=${:08X} prev_port=${:08X} current_port=${:08X} switched={} gworld={} dest=({},{},{},{}) view=({},{},{},{}) vis=({},{},{},{}) clip=({},{},{},{})",
2890 te_handle,
2891 te_port,
2892 previous_port,
2893 self.current_port,
2894 switched_port,
2895 self.gworld_devices.contains_key(&te_port),
2896 dest_rect.0,
2897 dest_rect.1,
2898 dest_rect.2,
2899 dest_rect.3,
2900 view_rect.0,
2901 view_rect.1,
2902 view_rect.2,
2903 view_rect.3,
2904 vis_top,
2905 vis_left,
2906 vis_bottom,
2907 vis_right,
2908 clip_top,
2909 clip_left,
2910 clip_bottom,
2911 clip_right,
2912 );
2913 }
2914
2915 let checksum_view_rect =
2916 |bus: &MacMemoryBus, port: u32, rect: (i16, i16, i16, i16)| -> u64 {
2917 if port == 0 {
2918 return 0;
2919 }
2920 let port_version = bus.read_word(port + 6);
2921 let is_color = (port_version & 0xC000) != 0;
2922 let (pix_base, row_bytes, bounds_top, bounds_left, bounds_bottom, bounds_right) =
2923 if is_color {
2924 let pix_map_handle = bus.read_long(port + 2);
2925 if pix_map_handle == 0 {
2926 return 0;
2927 }
2928 let pix_map_ptr = bus.read_long(pix_map_handle);
2929 if pix_map_ptr == 0 {
2930 return 0;
2931 }
2932 (
2933 bus.read_long(pix_map_ptr),
2934 (bus.read_word(pix_map_ptr + 4) & 0x3FFF) as u32,
2935 bus.read_word(pix_map_ptr + 6) as i16,
2936 bus.read_word(pix_map_ptr + 8) as i16,
2937 bus.read_word(pix_map_ptr + 10) as i16,
2938 bus.read_word(pix_map_ptr + 12) as i16,
2939 )
2940 } else {
2941 (
2942 bus.read_long(port + 2),
2943 (bus.read_word(port + 6) & 0x3FFF) as u32,
2944 bus.read_word(port + 8) as i16,
2945 bus.read_word(port + 10) as i16,
2946 bus.read_word(port + 12) as i16,
2947 bus.read_word(port + 14) as i16,
2948 )
2949 };
2950 let top = rect.0.max(bounds_top);
2951 let left = rect.1.max(bounds_left);
2952 let bottom = rect.2.min(bounds_bottom);
2953 let right = rect.3.min(bounds_right);
2954 if top >= bottom || left >= right || pix_base == 0 {
2955 return 0;
2956 }
2957 let mut sum = 0u64;
2958 for y in top..bottom {
2959 let dy = (y - bounds_top) as u32;
2960 for x in left..right {
2961 let dx = (x - bounds_left) as u32;
2962 sum =
2963 sum.wrapping_add(bus.read_byte(pix_base + dy * row_bytes + dx) as u64);
2964 }
2965 }
2966 sum
2967 };
2968 let checksum_before = if trace_textedit_enabled() {
2969 checksum_view_rect(bus, self.current_port, view_rect)
2970 } else {
2971 0
2972 };
2973
2974 self.tx_font = font;
2975 self.tx_face = face;
2976 self.tx_size = size;
2977 self.fg_color = color;
2978
2979 let clip_top = view_rect.0;
2980 let clip_left = view_rect.1;
2981 let clip_bottom = view_rect.2;
2982 let clip_right = view_rect.3;
2983 let box_width = (dest_rect.3 - dest_rect.1).max(0);
2984
2985 self.draw_rect(
2986 cpu,
2987 bus,
2988 &Rect {
2989 top: clip_top,
2990 left: clip_left,
2991 bottom: clip_bottom,
2992 right: clip_right,
2993 },
2994 ShapeOp::Erase,
2995 );
2996 let lines = if text_bytes.is_empty() {
2997 vec![(0usize, 0usize)]
2998 } else if uses_styled_runs {
2999 self.te_wrap_lines_styled(&styled_runs, &text_bytes, box_width)
3000 } else {
3001 self.te_wrap_lines(font, size, &text_bytes, box_width)
3002 };
3003
3004 let mut top = dest_rect.0;
3005 let mut visible_lines = Vec::new();
3006 let mut visual_lines = Vec::new();
3007 for (index, (start, end)) in lines.iter().enumerate() {
3008 let line_height = Self::te_height_for_line(bus, te_handle, index);
3009 let line_ascent = Self::te_ascent_for_line(bus, te_handle, index);
3010 let line_bottom = top.saturating_add(line_height);
3011 if line_bottom <= clip_top {
3012 top = line_bottom;
3013 continue;
3014 }
3015 if top >= clip_bottom {
3016 break;
3017 }
3018 visible_lines.push(index);
3019 let mut trimmed_end = *end;
3020 while trimmed_end > *start
3021 && matches!(text_bytes[trimmed_end - 1], b' ' | b'\r' | b'\n')
3022 {
3023 trimmed_end -= 1;
3024 }
3025 let line_width = if uses_styled_runs {
3026 self.te_measure_text_width_styled(&styled_runs, &text_bytes, *start, trimmed_end)
3027 } else {
3028 self.te_measure_text_width(font, size, &text_bytes, *start, trimmed_end)
3029 };
3030 let x = Self::te_line_origin_x(just, dest_rect.1, dest_rect.3, line_width);
3031 visual_lines.push((*start, *end, top, line_bottom, x));
3032 self.pn_loc = (top.saturating_add(line_ascent), x);
3033 for (offset, &byte) in text_bytes[*start..trimmed_end].iter().enumerate() {
3034 if uses_styled_runs {
3035 let style = Self::te_style_at_offset(&styled_runs, *start + offset);
3036 self.tx_font = style.font;
3037 self.tx_face = style.face;
3038 self.tx_size = style.size;
3039 self.fg_color = style.color;
3040 }
3041 self.draw_char(cpu, bus, byte as char);
3042 }
3043 top = line_bottom;
3044 }
3045
3046 let te_active = bus.read_word(te_ptr + Self::TE_ACTIVE_OFFSET) != 0;
3047 let caret_visible = bus.read_word(te_ptr + Self::TE_CARET_STATE_OFFSET) == 0;
3048 let outline_hilite = self.te_feature_bit(te_handle, Self::TE_FEATURE_OUTLINE_HILITE);
3049 let sel_start = bus.read_word(te_ptr + Self::TE_SEL_START_OFFSET) as usize;
3050 let sel_end = bus.read_word(te_ptr + Self::TE_SEL_END_OFFSET) as usize;
3051 let (selection_start, selection_end) = if sel_start <= sel_end {
3052 (sel_start, sel_end)
3053 } else {
3054 (sel_end, sel_start)
3055 };
3056 if selection_start == selection_end {
3057 if te_active && caret_visible {
3058 if let Some(&(line_start, line_end, line_top, line_bottom, line_x)) = visual_lines
3059 .iter()
3060 .find(|&&(start, end, _, _, _)| {
3061 selection_start >= start && selection_start <= end
3062 })
3063 .or_else(|| visual_lines.last())
3064 {
3065 let caret_offset = selection_start.min(line_end).max(line_start);
3066 let measured_width = if uses_styled_runs {
3067 self.te_measure_text_width_styled(
3068 &styled_runs,
3069 &text_bytes,
3070 line_start,
3071 caret_offset,
3072 )
3073 } else {
3074 self.te_measure_text_width(
3075 font,
3076 size,
3077 &text_bytes,
3078 line_start,
3079 caret_offset,
3080 )
3081 };
3082 let mut caret_x = line_x + measured_width;
3083 if caret_offset > line_start {
3084 caret_x = caret_x.saturating_sub(1);
3085 }
3086 let caret_width = self.ui_theme().text_theme().caret_width.max(1);
3087 if !self.draw_theme_caret(
3088 bus,
3089 line_top,
3090 caret_x,
3091 line_bottom,
3092 caret_x.saturating_add(caret_width),
3093 ) {
3094 self.draw_rect(
3095 cpu,
3096 bus,
3097 &Rect {
3098 top: line_top,
3099 left: caret_x,
3100 bottom: line_bottom,
3101 right: caret_x.saturating_add(1),
3102 },
3103 ShapeOp::Paint,
3104 );
3105 }
3106 }
3107 }
3108 } else if te_active || outline_hilite {
3109 for &(line_start, line_end, line_top, line_bottom, line_x) in &visual_lines {
3114 let start = selection_start.max(line_start);
3115 let end = selection_end.min(line_end);
3116 if start >= end {
3117 continue;
3118 }
3119 let mut selection_left = line_x
3120 + if uses_styled_runs {
3121 self.te_measure_text_width_styled(
3122 &styled_runs,
3123 &text_bytes,
3124 line_start,
3125 start,
3126 )
3127 } else {
3128 self.te_measure_text_width(font, size, &text_bytes, line_start, start)
3129 };
3130 let selection_right = line_x
3131 + if uses_styled_runs {
3132 self.te_measure_text_width_styled(
3133 &styled_runs,
3134 &text_bytes,
3135 line_start,
3136 end,
3137 )
3138 } else {
3139 self.te_measure_text_width(font, size, &text_bytes, line_start, end)
3140 };
3141 if start == line_start && matches!(just, 0 | -2) {
3142 selection_left = selection_left.saturating_sub(Self::TE_LINE_LEFT_INSET);
3147 }
3148 if self.ui_theme_id() == UiThemeId::ClassicSystem7 {
3149 if te_active {
3150 self.draw_rect(
3151 cpu,
3152 bus,
3153 &Rect {
3154 top: line_top,
3155 left: selection_left,
3156 bottom: line_bottom,
3157 right: selection_right,
3158 },
3159 ShapeOp::Invert,
3160 );
3161 }
3162 } else {
3163 self.draw_theme_text_selection(
3167 bus,
3168 line_top,
3169 selection_left,
3170 line_bottom,
3171 selection_right,
3172 te_active,
3173 );
3174 }
3175 }
3176 }
3177
3178 self.tx_font = old_font;
3179 self.tx_face = old_face;
3180 self.tx_size = old_size;
3181 self.fg_color = old_fg;
3182 self.pn_loc = old_loc;
3183 if trace_textedit_enabled() {
3184 eprintln!(
3185 "[TE] draw_te_contents visible_lines hTE=${:08X} {:?}",
3186 te_handle, visible_lines
3187 );
3188 eprintln!(
3189 "[TE] draw_te_contents checksum hTE=${:08X} before={} after={}",
3190 te_handle,
3191 checksum_before,
3192 checksum_view_rect(bus, self.current_port, view_rect)
3193 );
3194 }
3195
3196 if switched_port {
3197 self.set_current_port_state(bus, cpu, previous_port, Some(previous_gdevice));
3198 }
3199 }
3200
3201 fn te_feature_bit(&self, te_handle: u32, feature: u16) -> bool {
3202 let mask = 1u16.checked_shl(feature as u32).unwrap_or(0);
3203 self.textedit_states
3204 .get(&te_handle)
3205 .map(|state| (state.feature_bits & mask) != 0)
3206 .unwrap_or(false)
3207 }
3208
3209 pub fn te_auto_scroll_enabled(&self, te_handle: u32) -> bool {
3215 self.te_feature_bit(te_handle, Self::TE_FEATURE_AUTO_SCROLL)
3216 }
3217
3218 fn set_te_feature_bit(&mut self, te_handle: u32, feature: u16, enabled: bool) {
3219 let mask = 1u16.checked_shl(feature as u32).unwrap_or(0);
3220 let state = self.textedit_states.entry(te_handle).or_default();
3221 if enabled {
3222 state.feature_bits |= mask;
3223 } else {
3224 state.feature_bits &= !mask;
3225 }
3226 }
3227
3228 fn initialize_dialog_item_handles(
3229 &mut self,
3230 bus: &mut MacMemoryBus,
3231 dialog_ptr: u32,
3232 items: &[DialogItem],
3233 ) {
3234 self.initialize_dialog_item_handles_from(bus, dialog_ptr, items, 0);
3235 }
3236
3237 fn initialize_dialog_item_handles_from(
3238 &mut self,
3239 bus: &mut MacMemoryBus,
3240 dialog_ptr: u32,
3241 items: &[DialogItem],
3242 start_index: usize,
3243 ) {
3244 for (index, item) in items.iter().enumerate().skip(start_index) {
3249 let item_no = index as i16 + 1;
3250 let Some(item_handle_addr) = Self::dialog_item_handle_addr(bus, dialog_ptr, item_no)
3251 else {
3252 continue;
3253 };
3254
3255 let base_type = item.item_type & 0x7F;
3256 let item_handle = match base_type {
3257 8 | 16 => {
3258 let text_bytes = item.text.as_bytes();
3259 let handle = bus.alloc(4);
3260 let text_ptr = if text_bytes.is_empty() {
3261 0
3262 } else {
3263 let ptr = bus.alloc(text_bytes.len() as u32);
3264 bus.write_bytes(ptr, text_bytes);
3265 ptr
3266 };
3267 bus.write_long(handle, text_ptr);
3268 self.dialog_item_handles.insert(handle, (dialog_ptr, index));
3269 handle
3270 }
3271 32 => self
3272 .find_resource_any(*b"ICON", item.resource_id)
3273 .map(|(_, ptr)| {
3274 self.get_or_create_resource_handle(bus, *b"ICON", item.resource_id, ptr)
3275 })
3276 .unwrap_or(0),
3277 4..=6 => {
3278 let existing = bus.read_long(item_handle_addr);
3279 if existing != 0 {
3280 existing
3281 } else {
3282 self.create_standard_dialog_control_handle(bus, dialog_ptr, item_no, item)
3283 }
3284 }
3285 7 => self
3286 .find_resource_any(*b"CNTL", item.resource_id)
3287 .map(|(_, cntl_ptr)| {
3288 let value = bus.read_word(cntl_ptr + 8) as i16;
3289 let vis_word = bus.read_word(cntl_ptr + 10);
3290 let max = bus.read_word(cntl_ptr + 12) as i16;
3291 let min = bus.read_word(cntl_ptr + 14) as i16;
3292 let proc_id = bus.read_word(cntl_ptr + 16) as i16;
3293 let ref_con = bus.read_long(cntl_ptr + 18);
3294 let title_len = bus.read_byte(cntl_ptr + 22) as usize;
3295 let title = bus.read_bytes(cntl_ptr + 23, title_len.min(255));
3296 let ctrl_ptr = bus.alloc(296);
3297 let handle = bus.alloc(4);
3298 bus.write_long(handle, ctrl_ptr);
3299 self.initialize_control_record(
3300 bus,
3301 ctrl_ptr,
3302 dialog_ptr,
3303 item.rect,
3304 &title,
3305 vis_word != 0,
3306 value,
3307 min,
3308 max,
3309 proc_id,
3310 ref_con,
3311 );
3312 self.ensure_control_aux_record(bus, handle);
3313 let old_head = bus.read_long(dialog_ptr + 140);
3314 bus.write_long(ctrl_ptr, old_head);
3315 bus.write_long(dialog_ptr + 140, handle);
3316 self.dialog_control_handles
3317 .insert(handle, (dialog_ptr, item_no));
3318 self.dialog_control_values
3319 .insert((dialog_ptr, item_no), value);
3320 handle
3321 })
3322 .unwrap_or(0),
3323 64 => self
3324 .find_resource_any(*b"PICT", item.resource_id)
3325 .map(|(_, ptr)| {
3326 self.get_or_create_resource_handle(bus, *b"PICT", item.resource_id, ptr)
3327 })
3328 .unwrap_or(0),
3329 0 => item.proc_ptr,
3330 _ => 0,
3331 };
3332
3333 bus.write_long(item_handle_addr, item_handle);
3334 }
3335 }
3336
3337 fn create_standard_dialog_control_handle(
3338 &mut self,
3339 bus: &mut MacMemoryBus,
3340 dialog_ptr: u32,
3341 item_no: i16,
3342 item: &DialogItem,
3343 ) -> u32 {
3344 let proc_id = match item.item_type & 0x7F {
3345 4 => 0, 5 => 1, 6 => 2, _ => return 0,
3349 };
3350 let value = self
3351 .dialog_control_values
3352 .get(&(dialog_ptr, item_no))
3353 .copied()
3354 .unwrap_or(0);
3355 let ctrl_ptr = bus.alloc(296);
3356 let handle = bus.alloc(4);
3357 if ctrl_ptr == 0 || handle == 0 {
3358 return 0;
3359 }
3360
3361 bus.write_long(handle, ctrl_ptr);
3362 self.initialize_control_record(
3363 bus,
3364 ctrl_ptr,
3365 dialog_ptr,
3366 item.rect,
3367 item.text.as_bytes(),
3368 true,
3369 value,
3370 0,
3371 1,
3372 proc_id,
3373 0,
3374 );
3375 self.ensure_control_aux_record(bus, handle);
3376
3377 let old_head = bus.read_long(dialog_ptr + 140);
3378 bus.write_long(ctrl_ptr, old_head);
3379 bus.write_long(dialog_ptr + 140, handle);
3380 self.dialog_control_handles
3381 .insert(handle, (dialog_ptr, item_no));
3382 self.dialog_control_values
3383 .insert((dialog_ptr, item_no), value);
3384 handle
3385 }
3386
3387 fn ditl_item_record_len_at(
3388 bus: &MacMemoryBus,
3389 ptr: u32,
3390 data_len: u32,
3391 record_offset: u32,
3392 ) -> Option<u32> {
3393 if record_offset + 14 > data_len {
3394 return None;
3395 }
3396
3397 let item_type = bus.read_byte(ptr + record_offset + 12);
3398 let data_len_byte = bus.read_byte(ptr + record_offset + 13);
3399 let base_type = item_type & 0x7F;
3400 let payload_offset = record_offset + 14;
3401 let remaining = data_len.saturating_sub(payload_offset);
3402 let payload_len = Self::ditl_item_payload_len(base_type, data_len_byte, remaining)?;
3403 let padded = (payload_len + 1) & !1;
3404 if padded > remaining {
3405 return None;
3406 }
3407
3408 Some(14 + padded)
3409 }
3410
3411 fn ditl_used_len(bus: &MacMemoryBus, ptr: u32, data_len: u32) -> u32 {
3412 if ptr == 0 || data_len < 2 {
3413 return data_len;
3414 }
3415
3416 let max_index = bus.read_word(ptr) as i16;
3417 let count = if max_index < 0 {
3418 0
3419 } else {
3420 max_index as usize + 1
3421 };
3422 let mut offset = 2u32;
3423 for _ in 0..count {
3424 let Some(record_len) = Self::ditl_item_record_len_at(bus, ptr, data_len, offset) else {
3425 break;
3426 };
3427 offset = offset.saturating_add(record_len);
3428 }
3429
3430 offset.min(data_len)
3431 }
3432
3433 fn dialog_local_size(bus: &MacMemoryBus, dialog_ptr: u32) -> (i16, i16) {
3434 let height = (bus.read_word(dialog_ptr + 20) as i16)
3435 .saturating_sub(bus.read_word(dialog_ptr + 16) as i16);
3436 let width = (bus.read_word(dialog_ptr + 22) as i16)
3437 .saturating_sub(bus.read_word(dialog_ptr + 18) as i16);
3438 (height, width)
3439 }
3440
3441 fn append_ditl_offset(&self, bus: &MacMemoryBus, dialog_ptr: u32, method: i16) -> (i16, i16) {
3442 match method {
3443 0 => (0, 0),
3446 1 => {
3451 let (_, width) = Self::dialog_local_size(bus, dialog_ptr);
3452 (0, width)
3453 }
3454 2 => {
3455 let (height, _) = Self::dialog_local_size(bus, dialog_ptr);
3456 (height, 0)
3457 }
3458 item_method if item_method < 0 => {
3459 let item_no = item_method.checked_neg().unwrap_or(i16::MAX) as usize;
3460 self.dialog_items
3461 .get(&dialog_ptr)
3462 .and_then(|items| item_no.checked_sub(1).and_then(|index| items.get(index)))
3463 .map(|item| (item.rect.0, item.rect.1))
3464 .unwrap_or((0, 0))
3465 }
3466 _ => (0, 0),
3467 }
3468 }
3469
3470 fn offset_dialog_item_rect(item: &mut DialogItem, v_delta: i16, h_delta: i16) {
3471 item.rect = (
3472 item.rect.0.saturating_add(v_delta),
3473 item.rect.1.saturating_add(h_delta),
3474 item.rect.2.saturating_add(v_delta),
3475 item.rect.3.saturating_add(h_delta),
3476 );
3477 }
3478
3479 fn erase_retained_dialog_items_after_ditl_shorten(
3480 &mut self,
3481 bus: &mut MacMemoryBus,
3482 dialog_ptr: u32,
3483 removed_items: &[DialogItem],
3484 ) {
3485 if removed_items.is_empty() || dialog_ptr == 0 {
3486 return;
3487 }
3488 let bounds = Self::dialog_screen_bounds(bus, dialog_ptr);
3489 for item in removed_items {
3490 let (top, left, bottom, right) = Self::dialog_item_screen_rect(bounds, item.rect);
3491 self.fill_rect_clipped_to_dialog(
3492 bus,
3493 bounds,
3494 (top - 2, left - 2, bottom + 2, right + 2),
3495 false,
3496 );
3497 }
3498 if self.dialog_visible_snapshots.contains_key(&dialog_ptr) {
3499 let pixels = self.save_dialog_pixels(bus, bounds);
3500 self.dialog_visible_snapshots
3501 .insert(dialog_ptr, PersistentDialogSnapshot { bounds, pixels });
3502 }
3503 let tracking_bounds = self
3504 .dialog_tracking
3505 .as_ref()
3506 .filter(|tracking| tracking.dialog_ptr == dialog_ptr)
3507 .map(|tracking| tracking.bounds);
3508 if let Some(bounds) = tracking_bounds {
3509 let rendered = self.save_dialog_pixels(bus, bounds);
3510 if let Some(tracking) = self.dialog_tracking.as_mut() {
3511 if tracking.dialog_ptr == dialog_ptr {
3512 tracking.rendered_pixels = rendered;
3513 tracking.rendered_pixels_final = true;
3514 }
3515 }
3516 }
3517 }
3518
3519 pub(crate) fn append_ditl_to_dialog(
3520 &mut self,
3521 bus: &mut MacMemoryBus,
3522 dialog_ptr: u32,
3523 ditl_handle: u32,
3524 method: i16,
3525 ) -> u16 {
3526 if dialog_ptr == 0 || ditl_handle == 0 {
3527 return self
3528 .dialog_items
3529 .get(&dialog_ptr)
3530 .map(|items| items.len() as u16)
3531 .unwrap_or(0);
3532 }
3533
3534 let source_ptr = bus.read_long(ditl_handle);
3535 let Some(source_alloc_len) = bus.get_alloc_size(source_ptr) else {
3536 return self
3537 .dialog_items
3538 .get(&dialog_ptr)
3539 .map(|items| items.len() as u16)
3540 .unwrap_or(0);
3541 };
3542 let source_len = Self::ditl_used_len(bus, source_ptr, source_alloc_len);
3543 if source_len <= 2 {
3544 return self
3545 .dialog_items
3546 .get(&dialog_ptr)
3547 .map(|items| items.len() as u16)
3548 .unwrap_or(0);
3549 }
3550
3551 let mut appended_items = Self::parse_ditl(bus, source_ptr, source_len);
3552 if appended_items.is_empty() {
3553 return self
3554 .dialog_items
3555 .get(&dialog_ptr)
3556 .map(|items| items.len() as u16)
3557 .unwrap_or(0);
3558 }
3559
3560 let mut items_handle = bus.read_long(dialog_ptr + 156);
3561 if items_handle == 0 {
3562 items_handle = bus.alloc(4);
3563 let empty_ditl_ptr = bus.alloc(2);
3564 bus.write_word(empty_ditl_ptr, 0xFFFF);
3565 bus.write_long(items_handle, empty_ditl_ptr);
3566 bus.write_long(dialog_ptr + 156, items_handle);
3567 }
3568
3569 let old_ptr = bus.read_long(items_handle);
3570 let old_alloc_len = bus.get_alloc_size(old_ptr).unwrap_or(0);
3571 let old_len = if old_ptr != 0 && old_alloc_len >= 2 {
3572 Self::ditl_used_len(bus, old_ptr, old_alloc_len).max(2)
3573 } else {
3574 2
3575 };
3576 let raw_existing_items = if old_ptr != 0 && old_alloc_len >= 2 {
3577 Self::parse_ditl(bus, old_ptr, old_len)
3578 } else {
3579 Vec::new()
3580 };
3581 let mut all_items = self
3582 .dialog_items
3583 .get(&dialog_ptr)
3584 .cloned()
3585 .unwrap_or_else(|| raw_existing_items.clone());
3586 if all_items.len() != raw_existing_items.len() && !raw_existing_items.is_empty() {
3587 all_items = raw_existing_items;
3588 }
3589
3590 let start_index = all_items.len();
3591 let source_body_len = source_len.saturating_sub(2);
3592 let new_len = old_len.saturating_add(source_body_len);
3593 let new_ptr = bus.alloc(new_len);
3594 if new_ptr == 0 {
3595 return all_items.len() as u16;
3596 }
3597
3598 if old_ptr != 0 && old_alloc_len >= 2 {
3599 let old_bytes = bus.read_bytes(old_ptr, old_len as usize);
3600 bus.write_bytes(new_ptr, &old_bytes);
3601 } else {
3602 bus.write_word(new_ptr, 0xFFFF);
3603 }
3604 let source_body = bus.read_bytes(source_ptr + 2, source_body_len as usize);
3605 bus.write_bytes(new_ptr + old_len, &source_body);
3606
3607 let (v_delta, h_delta) = self.append_ditl_offset(bus, dialog_ptr, method);
3608 let mut source_offset = 2u32;
3609 let mut dest_offset = old_len;
3610 for item in &mut appended_items {
3611 if v_delta != 0 || h_delta != 0 {
3612 Self::offset_dialog_item_rect(item, v_delta, h_delta);
3613 bus.write_word(new_ptr + dest_offset + 4, item.rect.0 as u16);
3614 bus.write_word(new_ptr + dest_offset + 6, item.rect.1 as u16);
3615 bus.write_word(new_ptr + dest_offset + 8, item.rect.2 as u16);
3616 bus.write_word(new_ptr + dest_offset + 10, item.rect.3 as u16);
3617 }
3618
3619 let Some(record_len) =
3620 Self::ditl_item_record_len_at(bus, source_ptr, source_len, source_offset)
3621 else {
3622 break;
3623 };
3624 source_offset = source_offset.saturating_add(record_len);
3625 dest_offset = dest_offset.saturating_add(record_len);
3626 }
3627
3628 all_items.extend(appended_items);
3629 let new_count = all_items.len();
3630 let max_index = if new_count == 0 {
3631 0xFFFF
3632 } else {
3633 (new_count as u16).saturating_sub(1)
3634 };
3635 bus.write_word(new_ptr, max_index);
3636 bus.write_long(items_handle, new_ptr);
3637
3638 self.dialog_items.insert(dialog_ptr, all_items.clone());
3639 self.initialize_dialog_item_handles_from(bus, dialog_ptr, &all_items, start_index);
3640
3641 new_count as u16
3642 }
3643
3644 pub(crate) fn shorten_ditl_in_dialog(
3645 &mut self,
3646 bus: &mut MacMemoryBus,
3647 dialog_ptr: u32,
3648 number_items: u16,
3649 ) -> u16 {
3650 if dialog_ptr == 0 {
3651 return 0;
3652 }
3653
3654 let items_handle = bus.read_long(dialog_ptr + 156);
3655 let ditl_ptr = if items_handle != 0 {
3656 bus.read_long(items_handle)
3657 } else {
3658 0
3659 };
3660 let ditl_len = bus.get_alloc_size(ditl_ptr).unwrap_or(0);
3661 let raw_items = if ditl_ptr != 0 && ditl_len >= 2 {
3662 Self::parse_ditl(bus, ditl_ptr, Self::ditl_used_len(bus, ditl_ptr, ditl_len))
3663 } else {
3664 Vec::new()
3665 };
3666 let mut items = self
3667 .dialog_items
3668 .get(&dialog_ptr)
3669 .cloned()
3670 .unwrap_or(raw_items);
3671 let remove_count = (number_items as usize).min(items.len());
3672 let keep_count = items.len().saturating_sub(remove_count);
3673
3674 for item_no in (keep_count + 1)..=items.len() {
3675 let item_no = item_no as i16;
3676 let handle = Self::dialog_item_handle(bus, dialog_ptr, item_no);
3677 self.dialog_control_values.remove(&(dialog_ptr, item_no));
3678 self.dialog_control_handles.remove(&handle);
3679 self.dialog_item_handles.remove(&handle);
3680 }
3681
3682 let removed_items = items[keep_count..].to_vec();
3683 items.truncate(keep_count);
3684 if ditl_ptr != 0 && ditl_len >= 2 {
3685 let max_index = if keep_count == 0 {
3686 0xFFFF
3687 } else {
3688 (keep_count as u16).saturating_sub(1)
3689 };
3690 bus.write_word(ditl_ptr, max_index);
3691 }
3692 self.dialog_items.insert(dialog_ptr, items);
3693 self.erase_retained_dialog_items_after_ditl_shorten(bus, dialog_ptr, &removed_items);
3694
3695 keep_count as u16
3696 }
3697
3698 fn parse_dlog(
3705 bus: &MacMemoryBus,
3706 ptr: u32,
3707 data_len: u32,
3708 ) -> ((i16, i16, i16, i16), i16, bool, i16, String, u16) {
3709 let top = bus.read_word(ptr) as i16;
3710 let left = bus.read_word(ptr + 2) as i16;
3711 let bottom = bus.read_word(ptr + 4) as i16;
3712 let right = bus.read_word(ptr + 6) as i16;
3713 let proc_id = bus.read_word(ptr + 8) as i16;
3714 let visible = bus.read_byte(ptr + 10) != 0;
3715 let _ref_con = bus.read_long(ptr + 14);
3719 let items_id = bus.read_word(ptr + 18) as i16;
3720 let title_len = bus.read_byte(ptr + 20) as usize;
3722 let mut title_bytes = vec![0u8; title_len];
3723 for (i, byte) in title_bytes.iter_mut().enumerate() {
3724 *byte = bus.read_byte(ptr + 21 + i as u32);
3725 }
3726 let title = decode_mac_roman_for_render(&title_bytes);
3727
3728 let title_end = 21 + title_len as u32;
3732 let padded_end = (title_end + 1) & !1;
3733 let position = if padded_end + 2 <= data_len {
3734 bus.read_word(ptr + padded_end)
3735 } else {
3736 0
3737 };
3738
3739 (
3740 (top, left, bottom, right),
3741 proc_id,
3742 visible,
3743 items_id,
3744 title,
3745 position,
3746 )
3747 }
3748
3749 fn parse_alrt(
3754 bus: &MacMemoryBus,
3755 ptr: u32,
3756 data_len: u32,
3757 ) -> ((i16, i16, i16, i16), i16, u16, u16) {
3758 let bounds = if data_len >= 8 {
3759 (
3760 bus.read_word(ptr) as i16,
3761 bus.read_word(ptr + 2) as i16,
3762 bus.read_word(ptr + 4) as i16,
3763 bus.read_word(ptr + 6) as i16,
3764 )
3765 } else {
3766 (0, 0, 0, 0)
3767 };
3768 let items_id = if data_len >= 10 {
3769 bus.read_word(ptr + 8) as i16
3770 } else {
3771 0
3772 };
3773 let stages = if data_len >= 12 {
3774 bus.read_word(ptr + 10)
3775 } else {
3776 0
3777 };
3778 let position = if data_len >= 14 {
3782 bus.read_word(ptr + 12)
3783 } else {
3784 0
3785 };
3786 (bounds, items_id, stages, position)
3787 }
3788
3789 fn positioned_dialog_bounds(
3790 &self,
3791 mut bounds: (i16, i16, i16, i16),
3792 position: u16,
3793 ) -> (i16, i16, i16, i16) {
3794 match position {
3795 0x300A | 0x700A | 0xB00A => {
3799 let (_, _, screen_w, screen_h, _) = self.get_screen_params();
3800 let dialog_w = bounds.3 - bounds.1;
3801 let dialog_h = bounds.2 - bounds.0;
3802 let new_left = (screen_w - dialog_w) / 2;
3803 let new_top = (screen_h - dialog_h) / 5;
3804 bounds = (new_top, new_left, new_top + dialog_h, new_left + dialog_w);
3805 }
3806 0x280A | 0x680A | 0xA80A | 0x380A => {
3809 let (_, _, screen_w, screen_h, _) = self.get_screen_params();
3810 let dialog_w = bounds.3 - bounds.1;
3811 let dialog_h = bounds.2 - bounds.0;
3812 let new_left = (screen_w - dialog_w) / 2;
3813 let new_top = (screen_h - dialog_h) / 2;
3814 bounds = (new_top, new_left, new_top + dialog_h, new_left + dialog_w);
3815 }
3816 _ => {} }
3818 bounds
3819 }
3820
3821 fn alert_stage_default_item(stages: u16, stage_word: u16) -> (u32, u32, Option<i16>) {
3822 let stage_idx = (stage_word as u32).min(3);
3823 let nibble = ((stages as u32) >> (stage_idx * 4)) & 0xF;
3825 let box_drawn = (nibble & 0x04) != 0;
3826 let default_item = if (nibble & 0x08) == 0 { 1 } else { 2 };
3827 (stage_idx, nibble, box_drawn.then_some(default_item))
3828 }
3829
3830 fn enabled_button_count(items: &[DialogItem]) -> usize {
3831 items
3832 .iter()
3833 .filter(|item| (item.item_type & 0x7F) == 4 && (item.item_type & 0x80) == 0)
3834 .count()
3835 }
3836
3837 fn begin_interactive_alert<C: CpuOps>(
3838 &mut self,
3839 cpu: &mut C,
3840 bus: &mut MacMemoryBus,
3841 sp: u32,
3842 alert_id: i16,
3843 _filter_proc: u32,
3844 bounds: (i16, i16, i16, i16),
3845 items_id: i16,
3846 position: u16,
3847 default_item: i16,
3848 ) -> bool {
3849 let Some((_, ditl_data)) = self.find_resource_any(*b"DITL", items_id) else {
3850 return false;
3851 };
3852 let ditl_len = bus.get_alloc_size(ditl_data).unwrap_or(0);
3853 let items = Self::parse_ditl(bus, ditl_data, ditl_len);
3854 if Self::enabled_button_count(&items) <= 1 {
3855 return false;
3856 }
3857
3858 let items_handle = {
3859 let handle = bus.alloc(4);
3860 bus.write_long(handle, ditl_data);
3861 Self::duplicate_handle_data(bus, handle)
3862 };
3863 let bounds = self.positioned_dialog_bounds(bounds, position);
3864 let dialog_ptr = self.finish_dialog_creation(
3865 bus,
3866 cpu,
3867 0,
3868 bounds,
3869 "",
3870 true,
3871 1,
3872 false,
3873 alert_id as u32,
3874 items_handle,
3875 items.clone(),
3876 );
3877 if dialog_ptr == 0 {
3878 return false;
3879 }
3880 bus.write_word(dialog_ptr + 168, default_item as u16);
3881
3882 let saved_pixels = self
3883 .dialog_saved_pixels
3884 .get(&dialog_ptr)
3885 .cloned()
3886 .unwrap_or_else(|| self.save_dialog_pixels(bus, bounds));
3887 let rendered_pixels = self.save_dialog_pixels(bus, bounds);
3888 bus.write_word(sp + 6, 0);
3889 self.dialog_modal_entered.insert(dialog_ptr);
3890 self.dialog_tracking = Some(DialogTrackingState {
3891 dialog_ptr,
3892 bounds,
3893 title: String::new(),
3894 proc_id: 1,
3895 items,
3896 default_item,
3897 cancel_item: 0,
3898 edit_text: String::new(),
3899 edit_item: 0,
3900 saved_pixels,
3901 stack_ptr: sp.wrapping_sub(2),
3906 item_hit_ptr: sp + 6,
3907 rendered_pixels,
3908 flash_remaining: 0,
3909 flash_delay: 0,
3910 flash_item: 0,
3911 edit_text_modified: false,
3912 draw_proc_queue: VecDeque::new(),
3913 draw_procs_done: true,
3914 rendered_pixels_final: true,
3915 filter_proc: 0,
3916 game_managed: false,
3917 last_filter_event: None,
3918 popup_draws: Vec::new(),
3919 active_popup: None,
3920 active_button: None,
3921 active_user_item: None,
3922 });
3923 true
3924 }
3925
3926 fn finish_interactive_alert<C: CpuOps>(
3927 &mut self,
3928 cpu: &mut C,
3929 bus: &mut MacMemoryBus,
3930 hit: i16,
3931 ) {
3932 let Some(saved) = self.dialog_tracking.take() else {
3933 return;
3934 };
3935 if saved.item_hit_ptr != 0 {
3936 bus.write_word(saved.item_hit_ptr, hit as u16);
3937 }
3938 let stack_after = saved.stack_ptr + 8;
3939 let dialog_ptr = saved.dialog_ptr;
3940 self.dialog_saved_pixels
3941 .insert(dialog_ptr, saved.saved_pixels.clone());
3942 self.close_dialog_window(bus, cpu, dialog_ptr, true);
3943 cpu.write_reg(Register::A7, stack_after);
3944 }
3945
3946 fn handle_interactive_alert_refire<C: CpuOps>(&mut self, cpu: &mut C, bus: &mut MacMemoryBus) {
3947 if self.dialog_tracking.is_none() {
3948 return;
3949 }
3950
3951 if self
3952 .dialog_tracking
3953 .as_ref()
3954 .is_some_and(|tracking| tracking.active_button.is_some())
3955 {
3956 self.handle_dialog_button_tracking(bus);
3957 return;
3958 }
3959
3960 if self
3961 .dialog_tracking
3962 .as_ref()
3963 .is_some_and(|tracking| tracking.flash_remaining > 0)
3964 {
3965 let (remaining, button_draw, finished_hit) = {
3966 let t = self.dialog_tracking.as_mut().unwrap();
3967 if t.flash_delay > 0 {
3968 t.flash_delay -= 1;
3969 return;
3970 }
3971 t.flash_remaining -= 1;
3972 t.flash_delay = 3;
3973 let remaining = t.flash_remaining;
3974 let flash_item = t.flash_item;
3975 let button_draw =
3976 if remaining > 0 && flash_item > 0 && (flash_item as usize) <= t.items.len() {
3977 let item = &t.items[(flash_item - 1) as usize];
3978 Some((
3979 Self::dialog_item_screen_rect(t.bounds, item.rect),
3980 item.text.clone(),
3981 flash_item == t.default_item,
3982 remaining % 2 == 0,
3983 ))
3984 } else {
3985 None
3986 };
3987 (remaining, button_draw, flash_item)
3988 };
3989
3990 if let Some((screen_rect, title, is_default, highlighted)) = button_draw {
3991 self.draw_dialog_button_highlight_state(
3992 bus,
3993 screen_rect,
3994 &title,
3995 is_default,
3996 highlighted,
3997 );
3998 }
3999 if remaining == 0 {
4000 self.finish_interactive_alert(cpu, bus, finished_hit);
4001 }
4002 return;
4003 }
4004
4005 let event = if !self.event_queue.is_empty() {
4006 let mut event = None;
4007 while let Some(e) = self.event_queue.pop_front() {
4008 match e.what {
4009 1 | 2 | 3 | 6 => {
4010 event = Some(e);
4011 break;
4012 }
4013 _ => {}
4014 }
4015 }
4016 event
4017 } else {
4018 None
4019 };
4020
4021 let Some(event) = event else {
4022 return;
4023 };
4024
4025 match event.what {
4026 1 => {
4027 let (dialog_ptr, bounds, items, default_item) = {
4028 let tracking = self.dialog_tracking.as_ref().unwrap();
4029 (
4030 tracking.dialog_ptr,
4031 tracking.bounds,
4032 tracking.items.clone(),
4033 tracking.default_item,
4034 )
4035 };
4036 let mut hit = self.dialog_item_hit_test(
4037 bus,
4038 &items,
4039 bounds,
4040 event.where_v,
4041 event.where_h,
4042 &self.dialog_popup_original_rects,
4043 dialog_ptr,
4044 );
4045 if hit <= 0 {
4046 hit =
4047 Self::dialog_button_hit_test(&items, bounds, event.where_v, event.where_h);
4048 }
4049 if hit <= 0 {
4050 return;
4051 }
4052 let item = &items[(hit - 1) as usize];
4053 let base_type = item.item_type & 0x7F;
4054 let is_disabled = (item.item_type & 0x80) != 0;
4055 if base_type != 4 || is_disabled {
4056 return;
4057 }
4058 let rect = Self::dialog_item_screen_rect(bounds, item.rect);
4059 let is_default = hit == default_item;
4060 self.draw_dialog_button_highlight_state(bus, rect, &item.text, is_default, true);
4061 if self.mouse_button {
4062 let tracking = self.dialog_tracking.as_mut().unwrap();
4063 tracking.active_button = Some(super::dispatch::DialogButtonTrackingState {
4064 item_no: hit,
4065 rect: item.rect,
4066 title: item.text.clone(),
4067 is_default,
4068 highlighted: true,
4069 });
4070 } else {
4071 self.start_dialog_button_flash(
4072 bus, bounds, hit, item.rect, &item.text, is_default, true,
4073 );
4074 }
4075 }
4076 3 => {
4077 let (default_item, cancel_item) = self
4078 .dialog_tracking
4079 .as_ref()
4080 .map(|tracking| (tracking.default_item, tracking.cancel_item))
4081 .unwrap_or((0, 0));
4082 let key_code = (event.message >> 8) as u16;
4083 let char_code = (event.message & 0xFF) as u8;
4084 let hit = if char_code == b'\r' || char_code == 0x03 {
4085 default_item
4086 } else if char_code == 0x1B || key_code == 0x35 {
4087 cancel_item
4088 } else {
4089 0
4090 };
4091 if hit > 0 {
4092 self.finish_interactive_alert(cpu, bus, hit);
4093 }
4094 }
4095 6 => {
4096 let Some(tracking) = self.dialog_tracking.as_ref() else {
4097 return;
4098 };
4099 let dialog_ptr = tracking.dialog_ptr;
4100 let bounds = tracking.bounds;
4101 let items = tracking.items.clone();
4102 let default_item = tracking.default_item;
4103 self.begin_update_window(bus, dialog_ptr);
4104 self.set_current_port_state(bus, cpu, dialog_ptr, None);
4105 self.redraw_standard_dialog_items(
4106 bus,
4107 bounds,
4108 &items,
4109 default_item,
4110 "",
4111 0,
4112 dialog_ptr,
4113 );
4114 let rendered = self.save_dialog_pixels(bus, bounds);
4115 if let Some(tracking) = self.dialog_tracking.as_mut() {
4116 tracking.rendered_pixels = rendered;
4117 tracking.rendered_pixels_final = true;
4118 }
4119 self.end_update_window(bus, dialog_ptr);
4120 }
4121 _ => {}
4122 }
4123 }
4124
4125 fn parse_ditl(bus: &MacMemoryBus, ptr: u32, data_len: u32) -> Vec<DialogItem> {
4128 if data_len < 2 {
4129 return Vec::new();
4130 }
4131 let max_index = bus.read_word(ptr) as i16; let count = if max_index < 0 {
4133 0
4134 } else {
4135 max_index as usize + 1
4136 };
4137 let mut items = Vec::with_capacity(count);
4138 let mut offset = 2u32; for _ in 0..count {
4141 if offset + 14 > data_len {
4142 break;
4143 }
4144 let item_handle = bus.read_long(ptr + offset);
4149 offset += 4;
4150 let top = bus.read_word(ptr + offset) as i16;
4152 let left = bus.read_word(ptr + offset + 2) as i16;
4153 let bottom = bus.read_word(ptr + offset + 4) as i16;
4154 let right = bus.read_word(ptr + offset + 6) as i16;
4155 offset += 8;
4156 let item_type = bus.read_byte(ptr + offset);
4158 let data_len_byte = bus.read_byte(ptr + offset + 1);
4159 offset += 2;
4160
4161 let base_type = item_type & 0x7F; let mut text = String::new();
4164 let mut resource_id: i16 = 0;
4165 let remaining = data_len - offset;
4166 let Some(payload_len) =
4167 Self::ditl_item_payload_len(base_type, data_len_byte, remaining)
4168 else {
4169 break;
4170 };
4171 if matches!(base_type, 7 | 32 | 64) {
4172 resource_id = bus.read_word(ptr + offset) as i16;
4173 }
4174
4175 let padded = (payload_len + 1) & !1;
4176 if padded > remaining {
4177 break;
4178 }
4179
4180 if payload_len > 0 {
4181 match base_type {
4182 4 | 5 | 6 | 8 | 16 => {
4185 let bytes = bus.read_bytes(ptr + offset, payload_len as usize);
4186 text = decode_mac_roman_for_render(&bytes);
4187 }
4188 _ => {}
4189 }
4190 }
4191
4192 offset += padded;
4194
4195 let proc_ptr = if base_type == 0 { item_handle } else { 0 };
4201
4202 items.push(DialogItem {
4203 item_type,
4204 rect: (top, left, bottom, right),
4205 text,
4206 resource_id,
4207 proc_ptr,
4208 sel_start: 0,
4209 sel_end: 0,
4210 });
4211 }
4212 items
4213 }
4214
4215 fn ditl_item_payload_len(base_type: u8, data_len_byte: u8, remaining: u32) -> Option<u32> {
4216 let payload_len = match base_type {
4217 0 => 0,
4220 1 => u32::from(data_len_byte),
4223 7 | 32 | 64 => {
4228 if remaining < 2 {
4229 return None;
4230 }
4231 if data_len_byte >= 2 {
4232 u32::from(data_len_byte)
4233 } else {
4234 2
4235 }
4236 }
4237 _ => u32::from(data_len_byte),
4238 };
4239 Some(payload_len)
4240 }
4241
4242 fn refresh_ditl_proc_ptrs(bus: &MacMemoryBus, dialog_ptr: u32, items: &mut [DialogItem]) {
4247 let items_handle = bus.read_long(dialog_ptr + 156);
4249 if items_handle == 0 {
4250 return;
4251 }
4252 let ditl_ptr = bus.read_long(items_handle);
4253 if ditl_ptr == 0 {
4254 return;
4255 }
4256
4257 let mut offset = 2u32; for item in items.iter_mut() {
4266 let handle = bus.read_long(ditl_ptr + offset);
4267 offset += 4; offset += 8; let _item_type = bus.read_byte(ditl_ptr + offset);
4270 let data_len = bus.read_byte(ditl_ptr + offset + 1) as u32;
4271 offset += 2; let padded = (data_len + 1) & !1;
4273 offset += padded;
4274
4275 let base_type = item.item_type & 0x7F;
4276 if base_type == 0 {
4277 item.proc_ptr = handle;
4278 }
4279 }
4280 }
4281
4282 fn duplicate_handle_data(bus: &mut MacMemoryBus, handle: u32) -> u32 {
4283 if handle == 0 {
4284 return 0;
4285 }
4286
4287 let data_ptr = bus.read_long(handle);
4288 let new_handle = bus.alloc(4);
4289 if data_ptr == 0 {
4290 bus.write_long(new_handle, 0);
4291 return new_handle;
4292 }
4293
4294 let size = bus.get_alloc_size(data_ptr).unwrap_or(0);
4295 let new_data_ptr = bus.alloc(size);
4296 for offset in 0..size {
4297 bus.write_byte(new_data_ptr + offset, bus.read_byte(data_ptr + offset));
4298 }
4299 bus.write_long(new_handle, new_data_ptr);
4300 new_handle
4301 }
4302
4303 fn dispose_dialog_handle_storage(&mut self, bus: &mut MacMemoryBus, handle: u32) {
4304 if handle == 0 {
4305 return;
4306 }
4307
4308 let resource_backing = self.loaded_handles.get(&handle).copied();
4309 self.detached_handles.remove(&handle);
4310 self.forget_resource_handle_index_for_handle(handle);
4311 self.loaded_handles.remove(&handle);
4312 self.resource_handle_files.remove(&handle);
4313 self.detached_handle_files.remove(&handle);
4314 self.handle_state_bits.remove(&handle);
4315
4316 let data_ptr = bus.read_long(handle);
4317 if resource_backing.is_none() {
4318 bus.free(data_ptr);
4319 }
4320 bus.free(handle);
4321 }
4322
4323 fn dispose_dialog_te_storage(&mut self, bus: &mut MacMemoryBus, te_handle: u32) {
4324 if te_handle == 0 {
4325 return;
4326 }
4327
4328 let te_ptr = bus.read_long(te_handle);
4329 if te_ptr != 0 {
4330 let h_text = bus.read_long(te_ptr + Self::TE_HTEXT_OFFSET);
4331 self.dispose_dialog_handle_storage(bus, h_text);
4332
4333 if Self::te_is_styled_record(bus, te_ptr) {
4334 let style_handle = bus.read_long(te_ptr + Self::TE_TX_FONT_OFFSET);
4335 self.dispose_dialog_handle_storage(bus, style_handle);
4336 }
4337 bus.free(te_ptr);
4338 }
4339 bus.free(te_handle);
4340 self.textedit_states.remove(&te_handle);
4341 }
4342
4343 fn dispose_dialog_control_storage(&mut self, bus: &mut MacMemoryBus, ctrl_handle: u32) {
4344 if ctrl_handle == 0 {
4345 return;
4346 }
4347
4348 let ctrl_ptr = bus.read_long(ctrl_handle);
4349 if ctrl_ptr != 0 {
4350 let owner = bus.read_long(ctrl_ptr + 4);
4351 if owner != 0 {
4352 let mut prev_handle = 0u32;
4353 let mut cur_handle = bus.read_long(owner + 140);
4354 while cur_handle != 0 {
4355 let cur_ptr = bus.read_long(cur_handle);
4356 if cur_ptr == 0 {
4357 break;
4358 }
4359 if cur_handle == ctrl_handle {
4360 let next = bus.read_long(cur_ptr);
4361 if prev_handle == 0 {
4362 bus.write_long(owner + 140, next);
4363 } else {
4364 let prev_ptr = bus.read_long(prev_handle);
4365 if prev_ptr != 0 {
4366 bus.write_long(prev_ptr, next);
4367 }
4368 }
4369 break;
4370 }
4371 prev_handle = cur_handle;
4372 cur_handle = bus.read_long(cur_ptr);
4373 }
4374 }
4375 self.release_control_aux_record(bus, ctrl_handle);
4376 self.control_proc_ids.remove(&ctrl_ptr);
4377 bus.free(ctrl_ptr);
4378 }
4379 bus.free(ctrl_handle);
4380 }
4381
4382 fn dialog_item_storage_handles_from_ditl(
4383 bus: &MacMemoryBus,
4384 dialog_ptr: u32,
4385 ) -> Vec<(u8, u32)> {
4386 let items_handle = bus.read_long(dialog_ptr + 156);
4387 if items_handle == 0 {
4388 return Vec::new();
4389 }
4390 let ditl_ptr = bus.read_long(items_handle);
4391 if ditl_ptr == 0 {
4392 return Vec::new();
4393 }
4394 let data_len = bus.get_alloc_size(ditl_ptr).unwrap_or(0);
4395 if data_len < 2 {
4396 return Vec::new();
4397 }
4398
4399 let max_index = bus.read_word(ditl_ptr) as i16;
4400 if max_index < 0 {
4401 return Vec::new();
4402 }
4403
4404 let mut handles = Vec::new();
4405 let mut offset = 2u32;
4406 for _ in 0..=max_index {
4407 if offset + 14 > data_len {
4408 break;
4409 }
4410
4411 let item_handle = bus.read_long(ditl_ptr + offset);
4412 offset += 12;
4413 let item_type = bus.read_byte(ditl_ptr + offset);
4414 let data_len_byte = bus.read_byte(ditl_ptr + offset + 1);
4415 offset += 2;
4416
4417 let base_type = item_type & 0x7F;
4418 handles.push((base_type, item_handle));
4419
4420 let payload_len = match base_type {
4421 0 => 0,
4422 7 | 32 | 64 if data_len_byte < 2 => 2,
4423 _ => u32::from(data_len_byte),
4424 };
4425 let padded = (payload_len + 1) & !1;
4426 if padded > data_len.saturating_sub(offset) {
4427 break;
4428 }
4429 offset += padded;
4430 }
4431
4432 handles
4433 }
4434
4435 fn clear_dialog_scoped_item_state(&mut self, dialog_ptr: u32) {
4436 self.dialog_items.remove(&dialog_ptr);
4437 self.dialog_item_handles
4438 .retain(|_, (dlg, _)| *dlg != dialog_ptr);
4439 self.dialog_control_handles
4440 .retain(|_, (dlg, _)| *dlg != dialog_ptr);
4441 self.dialog_control_values
4442 .retain(|(dlg, _), _| *dlg != dialog_ptr);
4443 self.dialog_edit_text_modified_items
4444 .retain(|(dlg, _)| *dlg != dialog_ptr);
4445 self.hidden_dialog_item_rects
4446 .retain(|(dlg, _), _| *dlg != dialog_ptr);
4447 self.dialog_item_popup_menus
4448 .retain(|(dlg, _), _| *dlg != dialog_ptr);
4449 self.dialog_popup_original_rects
4450 .retain(|(dlg, _), _| *dlg != dialog_ptr);
4451 self.dialog_popup_candidate_items
4452 .retain(|(dlg, _)| *dlg != dialog_ptr);
4453 if self
4454 .pending_dialog_popup_menu
4455 .is_some_and(|pending| pending.dialog_ptr == dialog_ptr)
4456 {
4457 self.pending_dialog_popup_menu = None;
4458 }
4459 self.dialog_cancel_items.remove(&dialog_ptr);
4460 }
4461
4462 fn dispose_dialog_owned_items(&mut self, bus: &mut MacMemoryBus, dialog_ptr: u32) {
4463 let can_read_dialog_record =
4464 self.dialog_items.contains_key(&dialog_ptr) || self.window_list.contains(&dialog_ptr);
4465 let mut text_handles = Vec::new();
4466 let mut control_handles = Vec::new();
4467
4468 if can_read_dialog_record {
4469 for (base_type, handle) in Self::dialog_item_storage_handles_from_ditl(bus, dialog_ptr)
4470 {
4471 match base_type {
4472 8 | 16 => text_handles.push(handle),
4473 4..=7 => control_handles.push(handle),
4474 _ => {}
4475 }
4476 }
4477 }
4478
4479 text_handles.extend(
4480 self.dialog_item_handles
4481 .iter()
4482 .filter_map(|(&handle, &(dlg, _))| {
4483 if dlg == dialog_ptr {
4484 Some(handle)
4485 } else {
4486 None
4487 }
4488 }),
4489 );
4490 control_handles.extend(self.dialog_control_handles.iter().filter_map(
4491 |(&handle, &(dlg, _))| {
4492 if dlg == dialog_ptr {
4493 Some(handle)
4494 } else {
4495 None
4496 }
4497 },
4498 ));
4499
4500 text_handles.sort_unstable();
4501 text_handles.dedup();
4502 control_handles.sort_unstable();
4503 control_handles.dedup();
4504
4505 for handle in text_handles {
4506 self.dispose_dialog_handle_storage(bus, handle);
4507 }
4508 for handle in control_handles {
4509 self.dispose_dialog_control_storage(bus, handle);
4510 }
4511
4512 if can_read_dialog_record {
4513 let text_h = bus.read_long(dialog_ptr + 160);
4514 self.dispose_dialog_te_storage(bus, text_h);
4515 bus.write_long(dialog_ptr + 160, 0);
4516 }
4517
4518 self.clear_dialog_scoped_item_state(dialog_ptr);
4519 }
4520
4521 fn dispose_dialog_record_and_item_list(&mut self, bus: &mut MacMemoryBus, dialog_ptr: u32) {
4522 if !self.dialog_items.contains_key(&dialog_ptr) && !self.window_list.contains(&dialog_ptr) {
4523 return;
4524 }
4525
4526 let items_handle = bus.read_long(dialog_ptr + 156);
4527 self.dispose_dialog_handle_storage(bus, items_handle);
4528 bus.free(dialog_ptr);
4529 }
4530
4531 pub(crate) fn dialog_screen_bounds(
4532 bus: &MacMemoryBus,
4533 dialog_ptr: u32,
4534 ) -> (i16, i16, i16, i16) {
4535 let port_height =
4536 bus.read_word(dialog_ptr + 20) as i16 - bus.read_word(dialog_ptr + 16) as i16;
4537 let port_width =
4538 bus.read_word(dialog_ptr + 22) as i16 - bus.read_word(dialog_ptr + 18) as i16;
4539
4540 let port_version = bus.read_word(dialog_ptr + 6);
4541 let is_cgraf = (port_version & 0xC000) == 0xC000;
4542 let (top, left) = if is_cgraf {
4543 let pixmap_handle = bus.read_long(dialog_ptr + 2);
4544 let pixmap_ptr = if pixmap_handle != 0 {
4545 bus.read_long(pixmap_handle)
4546 } else {
4547 0
4548 };
4549 if pixmap_ptr != 0 {
4550 (
4551 -(bus.read_word(pixmap_ptr + 6) as i16),
4552 -(bus.read_word(pixmap_ptr + 8) as i16),
4553 )
4554 } else {
4555 (0, 0)
4556 }
4557 } else {
4558 (
4559 -(bus.read_word(dialog_ptr + 8) as i16),
4560 -(bus.read_word(dialog_ptr + 10) as i16),
4561 )
4562 };
4563
4564 (top, left, top + port_height, left + port_width)
4565 }
4566
4567 fn dialog_edit_state(
4568 bus: &MacMemoryBus,
4569 dialog_ptr: u32,
4570 items: &[DialogItem],
4571 ) -> (String, i16, i16) {
4572 let default_item = match bus.read_word(dialog_ptr + 168) as i16 {
4573 value if value > 0 => value,
4574 _ => 1,
4575 };
4576
4577 let edit_field = bus.read_word(dialog_ptr + 164) as i16;
4578 let stored_edit_item = if edit_field >= 0 { edit_field + 1 } else { 0 };
4579 let stored_is_valid = stored_edit_item > 0
4580 && items
4581 .get((stored_edit_item - 1) as usize)
4582 .is_some_and(|item| (item.item_type & 0x7F) == 16 && (item.item_type & 0x80) == 0);
4583 let edit_item = if stored_is_valid {
4584 stored_edit_item
4585 } else {
4586 items
4587 .iter()
4588 .position(|item| (item.item_type & 0x7F) == 16 && (item.item_type & 0x80) == 0)
4589 .map(|idx| (idx + 1) as i16)
4590 .unwrap_or(0)
4591 };
4592 if edit_item <= 0 {
4593 return (String::new(), 0, default_item);
4594 }
4595
4596 let edit_text = Self::text_item_string_from_handle_if_present(
4597 bus,
4598 Self::dialog_item_handle(bus, dialog_ptr, edit_item),
4599 );
4600 if let Some(edit_text) = edit_text {
4601 return (edit_text, edit_item, default_item);
4602 }
4603
4604 let fallback = items
4605 .get((edit_item - 1) as usize)
4606 .map(|item| item.text.clone())
4607 .unwrap_or_default();
4608 (fallback, edit_item, default_item)
4609 }
4610
4611 fn sync_tracking_active_edit_item(tracking: &mut DialogTrackingState) {
4612 let edit_item = tracking.edit_item;
4613 if edit_item <= 0 {
4614 return;
4615 }
4616 if let Some(item) = tracking.items.get_mut((edit_item - 1) as usize) {
4617 if (item.item_type & 0x7F) == 16 {
4618 item.text = tracking.edit_text.clone();
4619 }
4620 }
4621 }
4622
4623 fn set_tracking_active_edit_selection(
4624 tracking: &mut DialogTrackingState,
4625 sel_start: usize,
4626 sel_end: usize,
4627 ) {
4628 let edit_item = tracking.edit_item;
4629 if edit_item <= 0 {
4630 return;
4631 }
4632 if let Some(item) = tracking.items.get_mut((edit_item - 1) as usize) {
4633 if (item.item_type & 0x7F) == 16 {
4634 let text_len = tracking.edit_text.len();
4635 item.sel_start = sel_start.min(text_len).min(i16::MAX as usize) as i16;
4636 item.sel_end = sel_end.min(text_len).min(i16::MAX as usize) as i16;
4637 }
4638 }
4639 }
4640
4641 fn textedit_key_result(
4642 existing: &[u8],
4643 sel_start: usize,
4644 sel_end: usize,
4645 key: u8,
4646 ) -> (Vec<u8>, usize) {
4647 let text_len = existing.len();
4648 let s = sel_start.min(text_len);
4649 let e = sel_end.min(text_len);
4650 let (s, e) = if s > e { (e, s) } else { (s, e) };
4651
4652 if key == 0x08 {
4653 if s != e {
4654 let mut merged = Vec::with_capacity(text_len - (e - s));
4655 merged.extend_from_slice(&existing[..s]);
4656 merged.extend_from_slice(&existing[e..]);
4657 return (merged, s);
4658 }
4659 if s > 0 {
4660 let mut merged = Vec::with_capacity(text_len - 1);
4661 merged.extend_from_slice(&existing[..s - 1]);
4662 merged.extend_from_slice(&existing[s..]);
4663 return (merged, s - 1);
4664 }
4665 return (existing.to_vec(), s);
4666 }
4667
4668 let mut merged = Vec::with_capacity(s + 1 + text_len.saturating_sub(e));
4669 merged.extend_from_slice(&existing[..s]);
4670 merged.push(key);
4671 merged.extend_from_slice(&existing[e..]);
4672 (merged, s + 1)
4673 }
4674
4675 fn apply_dialog_select_key_to_edit_item(
4676 &mut self,
4677 bus: &mut MacMemoryBus,
4678 dialog_ptr: u32,
4679 items: &mut [DialogItem],
4680 edit_item: i16,
4681 key: u8,
4682 ) -> bool {
4683 if edit_item <= 0 {
4684 return false;
4685 }
4686 let Some(item) = items.get_mut((edit_item - 1) as usize) else {
4687 return false;
4688 };
4689 let base_type = item.item_type & 0x7F;
4690 let is_disabled = (item.item_type & 0x80) != 0;
4691 if base_type != 16 || is_disabled {
4692 return false;
4693 }
4694
4695 let item_handle = Self::dialog_item_handle(bus, dialog_ptr, edit_item);
4696 let existing = Self::text_item_bytes_from_handle_if_present(bus, item_handle)
4697 .unwrap_or_else(|| item.text.as_bytes().to_vec());
4698 let text_handle = bus.read_long(dialog_ptr + 160);
4699 let te_ptr = Self::te_record_ptr(bus, text_handle);
4700 let (sel_start, sel_end) = if te_ptr != 0 {
4701 (
4702 bus.read_word(te_ptr + Self::TE_SEL_START_OFFSET) as usize,
4703 bus.read_word(te_ptr + Self::TE_SEL_END_OFFSET) as usize,
4704 )
4705 } else {
4706 (item.sel_start.max(0) as usize, item.sel_end.max(0) as usize)
4707 };
4708
4709 let (updated, insertion_point) =
4710 Self::textedit_key_result(&existing, sel_start, sel_end, key);
4711 let clamped_insertion = insertion_point.min(u16::MAX as usize) as u16;
4712 item.text = decode_mac_roman_for_render(&updated);
4713 item.sel_start = clamped_insertion as i16;
4714 item.sel_end = clamped_insertion as i16;
4715
4716 if item_handle != 0 {
4717 let len = updated.len().min(255);
4718 let data_ptr = Self::ensure_text_handle_size(bus, item_handle, len);
4719 if data_ptr != 0 && len > 0 {
4720 bus.write_bytes(data_ptr, &updated[..len]);
4721 }
4722 }
4723
4724 if te_ptr != 0 {
4725 self.te_set_text_contents(bus, text_handle, &updated);
4726 bus.write_word(te_ptr + Self::TE_SEL_START_OFFSET, clamped_insertion);
4727 bus.write_word(te_ptr + Self::TE_SEL_END_OFFSET, clamped_insertion);
4728 }
4729
4730 true
4731 }
4732
4733 fn activate_dialog_edit_item<C: CpuOps>(
4734 &mut self,
4735 bus: &mut MacMemoryBus,
4736 cpu: &mut C,
4737 dialog_ptr: u32,
4738 items: &[DialogItem],
4739 edit_item: i16,
4740 ) -> bool {
4741 if edit_item <= 0 {
4742 return false;
4743 }
4744 let Some(item) = items.get((edit_item - 1) as usize) else {
4745 return false;
4746 };
4747 if (item.item_type & 0x7F) != 16 || (item.item_type & 0x80) != 0 {
4748 return false;
4749 }
4750
4751 bus.write_word(dialog_ptr + 164, (edit_item - 1) as u16);
4752
4753 let text_handle = bus.read_long(dialog_ptr + 160);
4754 let te_ptr = Self::te_record_ptr(bus, text_handle);
4755 if te_ptr == 0 {
4756 return true;
4757 }
4758
4759 let item_handle = Self::dialog_item_handle(bus, dialog_ptr, edit_item);
4760 let text = Self::text_item_bytes_from_handle_if_present(bus, item_handle)
4761 .unwrap_or_else(|| item.text.as_bytes().to_vec());
4762 self.te_set_text_contents(bus, text_handle, &text);
4763
4764 let text_len = text.len().min(u16::MAX as usize);
4765 let sel_start = item.sel_start.max(0) as usize;
4766 let sel_end = item.sel_end.max(0) as usize;
4767 bus.write_word(
4768 te_ptr + Self::TE_SEL_START_OFFSET,
4769 sel_start.min(text_len) as u16,
4770 );
4771 bus.write_word(
4772 te_ptr + Self::TE_SEL_END_OFFSET,
4773 sel_end.min(text_len) as u16,
4774 );
4775 bus.write_word(te_ptr + Self::TE_ACTIVE_OFFSET, 1);
4781 bus.write_long(te_ptr + Self::TE_CARET_TIME_OFFSET, self.tick_count);
4782 bus.write_word(te_ptr + Self::TE_CARET_STATE_OFFSET, 0);
4783 self.draw_te_contents(cpu, bus, text_handle);
4784 true
4785 }
4786
4787 fn dialog_item_selection_range(item: &DialogItem, text_len: usize) -> Option<(usize, usize)> {
4788 let start = (item.sel_start.max(0) as usize).min(text_len);
4789 let end = (item.sel_end.max(0) as usize).min(text_len);
4790 let (start, end) = if start <= end {
4791 (start, end)
4792 } else {
4793 (end, start)
4794 };
4795 (start < end).then_some((start, end))
4796 }
4797
4798 fn redraw_dialog_text_item(&mut self, bus: &mut MacMemoryBus, dialog_ptr: u32, item_no: i16) {
4799 if !self.window_visible(bus, dialog_ptr) {
4800 return;
4801 }
4802 let Some(items) = self.dialog_items.get(&dialog_ptr).cloned() else {
4803 return;
4804 };
4805 let Some(item) = items.get((item_no - 1) as usize) else {
4806 return;
4807 };
4808 let base_type = item.item_type & 0x7F;
4809 if base_type != 8 && base_type != 16 {
4810 return;
4811 }
4812
4813 let bounds = Self::dialog_screen_bounds(bus, dialog_ptr);
4814 let (top, left, bottom, right) = bounds;
4815 let (it, il, ib, ir) = item.rect;
4816 let abs_top = top + it;
4817 let abs_left = left + il;
4818 let abs_bottom = top + ib;
4819 let abs_right = left + ir;
4820 if abs_top >= bottom || abs_bottom <= top || abs_left >= right || abs_right <= left {
4821 return;
4822 }
4823
4824 let (edit_text, edit_item, default_item) = Self::dialog_edit_state(bus, dialog_ptr, &items);
4825 self.redraw_standard_dialog_items(
4826 bus,
4827 bounds,
4828 &items,
4829 default_item,
4830 &edit_text,
4831 edit_item,
4832 dialog_ptr,
4833 );
4834
4835 if self.dialog_visible_snapshots.contains_key(&dialog_ptr) {
4836 let pixels = self.save_dialog_pixels(bus, bounds);
4837 self.dialog_visible_snapshots
4838 .insert(dialog_ptr, PersistentDialogSnapshot { bounds, pixels });
4839 }
4840 self.capture_gui_frame(
4841 bus,
4842 &format!("set_dialog_item_text_{:08X}_{}", dialog_ptr, item_no),
4843 );
4844 }
4845
4846 fn flush_dialog_edit_item_texts(
4847 &mut self,
4848 bus: &mut MacMemoryBus,
4849 dialog_ptr: u32,
4850 items: &[DialogItem],
4851 active_edit_item: i16,
4852 active_edit_text: &str,
4853 ) {
4854 for (idx, item) in items.iter().enumerate() {
4855 if (item.item_type & 0x7F) != 16 {
4856 continue;
4857 }
4858 let item_no = (idx + 1) as i16;
4859 let text = if item_no == active_edit_item {
4860 active_edit_text
4861 } else {
4862 &item.text
4863 };
4864 let bytes = text.as_bytes();
4865 let len = bytes.len().min(255);
4866 let item_handle = Self::dialog_item_handle(bus, dialog_ptr, item_no);
4867 if item_handle != 0 {
4868 let data_ptr = Self::ensure_text_handle_size(bus, item_handle, len);
4869 if data_ptr != 0 && len > 0 {
4870 bus.write_bytes(data_ptr, &bytes[..len]);
4871 }
4872 }
4873 if let Some(cached_items) = self.dialog_items.get_mut(&dialog_ptr) {
4874 if idx < cached_items.len() {
4875 cached_items[idx].text = text.to_string();
4876 cached_items[idx].sel_start = item.sel_start;
4877 cached_items[idx].sel_end = item.sel_end;
4878 }
4879 }
4880 }
4881 }
4882
4883 fn finish_dialog_creation<C: CpuOps>(
4884 &mut self,
4885 bus: &mut MacMemoryBus,
4886 cpu: &mut C,
4887 storage_ptr: u32,
4888 bounds: (i16, i16, i16, i16),
4889 title: &str,
4890 visible: bool,
4891 proc_id: i16,
4892 go_away_flag: bool,
4893 ref_con: u32,
4894 items_handle: u32,
4895 items: Vec<DialogItem>,
4896 ) -> u32 {
4897 let dlg_ptr = if storage_ptr != 0 {
4898 storage_ptr
4899 } else {
4900 bus.alloc_aligned(170, MANAGER_DIALOG_RECORD_ALIGNMENT)
4906 };
4907
4908 self.window_stack.push((
4909 self.front_window,
4910 self.window_bounds,
4911 self.window_proc_id,
4912 self.window_title.clone(),
4913 ));
4914
4915 let screen_base: u32 = bus.read_long(0x0824);
4916 self.init_cgraf_window(
4917 bus,
4918 cpu,
4919 dlg_ptr,
4920 screen_base,
4921 bounds.0,
4922 bounds.1,
4923 bounds.2,
4924 bounds.3,
4925 title,
4926 proc_id,
4927 visible,
4928 false,
4929 go_away_flag,
4930 ref_con,
4931 );
4932 self.set_current_port_state(bus, cpu, dlg_ptr, None);
4933
4934 bus.write_word(dlg_ptr + 108, 2);
4939 bus.write_long(dlg_ptr + 156, items_handle);
4940
4941 let dialog_font = bus.read_word(crate::memory::globals::addr::DLG_FONT) as i16;
4945 bus.write_word(dlg_ptr + 68, dialog_font as u16);
4946 self.tx_font = dialog_font;
4947 if let Some(state) = self.port_draw_states.get_mut(&dlg_ptr) {
4948 state.tx_font = dialog_font;
4949 }
4950
4951 let text_h = Self::allocate_te_handle(bus);
4952 bus.write_long(dlg_ptr + 160, text_h);
4953 bus.write_word(dlg_ptr + 164, 0xFFFF); bus.write_word(dlg_ptr + 166, 0); bus.write_word(dlg_ptr + 168, 1); self.initialize_dialog_item_handles(bus, dlg_ptr, &items);
4958 self.dialog_items.insert(dlg_ptr, items.clone());
4959
4960 if visible {
4961 let (edit_text, edit_item, default_item) =
4967 Self::dialog_edit_state(bus, dlg_ptr, &items);
4968 self.draw_dialog(
4969 bus,
4970 bounds,
4971 proc_id,
4972 title,
4973 &items,
4974 default_item,
4975 &edit_text,
4976 edit_item,
4977 false,
4978 dlg_ptr,
4979 );
4980 if !Self::dialog_is_game_managed(bounds, &items) {
4981 let pixels = self.save_dialog_pixels(bus, bounds);
4982 self.dialog_visible_snapshots
4983 .insert(dlg_ptr, PersistentDialogSnapshot { bounds, pixels });
4984 }
4985 for item in &items {
4986 if Self::dialog_item_intersects_bounds(bounds, item) {
4987 self.validate_window_rect(bus, dlg_ptr, item.rect);
4988 }
4989 }
4990 self.queue_window_update_event(dlg_ptr);
4991 self.capture_gui_frame(bus, &format!("get_new_dialog_initial_{:08X}", dlg_ptr));
4992 } else {
4993 self.dialog_initial_draw_deferred.remove(&dlg_ptr);
4994 }
4995
4996 dlg_ptr
4997 }
4998
4999 pub(crate) fn save_dialog_pixels(
5004 &self,
5005 bus: &MacMemoryBus,
5006 rect: (i16, i16, i16, i16),
5007 ) -> Vec<u8> {
5008 let (screen_base, row_bytes, _, screen_h, pixel_size) = self.get_screen_params();
5009 let (save_top, save_left, save_bottom, save_right) = Self::dialog_saved_pixel_rect(rect);
5010 let row_width = (save_right - save_left) as usize;
5014 let row_count = (save_bottom - save_top) as usize;
5015 let mut saved = Vec::new();
5016 let screen_h_i16 = screen_h;
5017 for y in save_top..save_bottom {
5018 let y_on_screen = y >= 0 && y < screen_h_i16;
5019 if pixel_size == 8 {
5020 let on_screen_row =
5021 y_on_screen && save_left >= 0 && (save_right as u32) <= row_bytes;
5022 if on_screen_row {
5023 if saved.capacity() == 0 {
5025 saved.reserve(row_count * row_width);
5026 }
5027 let start = saved.len();
5028 saved.resize(start + row_width, 0);
5029 let row_addr = screen_base + (y as u32) * row_bytes + (save_left as u32);
5030 bus.read_bytes_into(row_addr, &mut saved[start..start + row_width]);
5031 } else if y_on_screen {
5032 for x in save_left..save_right {
5033 if x < 0 || (x as u32) >= row_bytes {
5034 saved.push(0);
5035 } else {
5036 let addr = screen_base + (y as u32) * row_bytes + (x as u32);
5037 saved.push(bus.read_byte(addr));
5038 }
5039 }
5040 } else {
5041 saved.resize(saved.len() + row_width, 0);
5043 }
5044 } else if y_on_screen {
5045 let byte_left = (save_left.max(0) as u32) / 8;
5046 let byte_right = (save_right as u32).div_ceil(8);
5047 let bx_end = byte_right.min(row_bytes);
5048 if byte_left < bx_end {
5049 let len = (bx_end - byte_left) as usize;
5050 let start = saved.len();
5051 saved.resize(start + len, 0);
5052 let row_addr = screen_base + (y as u32) * row_bytes + byte_left;
5053 bus.read_bytes_into(row_addr, &mut saved[start..start + len]);
5054 }
5055 }
5056 }
5060 saved
5061 }
5062
5063 pub(crate) fn ensure_dialog_background_saved(
5064 &mut self,
5065 bus: &MacMemoryBus,
5066 dialog_ptr: u32,
5067 bounds: (i16, i16, i16, i16),
5068 ) {
5069 if dialog_ptr == 0
5070 || !self.dialog_items.contains_key(&dialog_ptr)
5071 || self.dialog_saved_pixels.contains_key(&dialog_ptr)
5072 {
5073 return;
5074 }
5075 let background = self.save_dialog_pixels(bus, bounds);
5076 self.dialog_saved_pixels.insert(dialog_ptr, background);
5077 }
5078
5079 pub(crate) fn ensure_dialog_background_saved_for_screen_port(
5080 &mut self,
5081 bus: &MacMemoryBus,
5082 port: u32,
5083 ) {
5084 if port == 0 || !self.dialog_items.contains_key(&port) {
5085 return;
5086 }
5087 let bounds = Self::dialog_screen_bounds(bus, port);
5088 self.ensure_dialog_background_saved(bus, port, bounds);
5089 }
5090
5091 fn dialog_saved_pixel_rect(rect: (i16, i16, i16, i16)) -> (i16, i16, i16, i16) {
5092 let (top, left, bottom, right) = rect;
5093 let margin = Self::DBOX_FRAME_MARGIN;
5094 (top - margin, left - margin, bottom + margin, right + margin)
5095 }
5096
5097 pub(crate) fn refresh_dialog_saved_pixels_after_screen_draw(
5098 &mut self,
5099 bus: &MacMemoryBus,
5100 drawing_port: u32,
5101 screen_rect: (i16, i16, i16, i16),
5102 ) {
5103 if screen_rect.0 >= screen_rect.2 || screen_rect.1 >= screen_rect.3 {
5104 return;
5105 }
5106 let mut dialogs: Vec<(u32, (i16, i16, i16, i16))> = self
5107 .dialog_visible_snapshots
5108 .iter()
5109 .map(|(&dialog_ptr, snapshot)| (dialog_ptr, snapshot.bounds))
5110 .collect();
5111 if let Some(tracking) = self.dialog_tracking.as_ref() {
5112 if !dialogs
5113 .iter()
5114 .any(|(dialog_ptr, _)| *dialog_ptr == tracking.dialog_ptr)
5115 {
5116 dialogs.push((tracking.dialog_ptr, tracking.bounds));
5117 }
5118 }
5119 for (dialog_ptr, bounds) in dialogs {
5120 let drawing_active_modal_dialog =
5121 self.dialog_tracking.as_ref().is_some_and(|tracking| {
5122 tracking.dialog_ptr == dialog_ptr && dialog_ptr == drawing_port
5123 });
5124 let drawing_premodal_dialog =
5125 dialog_ptr == drawing_port && !self.dialog_modal_entered.contains(&dialog_ptr);
5126 if drawing_active_modal_dialog
5127 || drawing_premodal_dialog
5128 || self.active_modeless_dialog_draw_proc == Some(dialog_ptr)
5129 {
5130 continue;
5131 }
5132 self.refresh_single_dialog_saved_pixels_after_screen_draw(
5133 bus,
5134 dialog_ptr,
5135 bounds,
5136 screen_rect,
5137 );
5138 }
5139 }
5140
5141 fn refresh_single_dialog_saved_pixels_after_screen_draw(
5142 &mut self,
5143 bus: &MacMemoryBus,
5144 dialog_ptr: u32,
5145 bounds: (i16, i16, i16, i16),
5146 screen_rect: (i16, i16, i16, i16),
5147 ) {
5148 let save_rect = Self::dialog_saved_pixel_rect(bounds);
5149 let Some(intersection) = Self::rect_intersection(save_rect, screen_rect) else {
5150 return;
5151 };
5152
5153 let (screen_base, row_bytes, screen_w, screen_h, pixel_size) = self.get_screen_params();
5154 let Some(saved) = self.dialog_saved_pixels.get_mut(&dialog_ptr) else {
5155 return;
5156 };
5157 let (save_top, save_left, save_bottom, save_right) = save_rect;
5158 let row_width = save_right.saturating_sub(save_left) as usize;
5159 let row_count = save_bottom.saturating_sub(save_top) as usize;
5160 if row_width == 0 || row_count == 0 {
5161 return;
5162 }
5163
5164 match pixel_size {
5165 8 => {
5166 let expected = row_width.saturating_mul(row_count);
5167 if saved.len() < expected {
5168 return;
5169 }
5170 let top = intersection.0.max(0);
5171 let left = intersection.1.max(0);
5172 let bottom = intersection.2.min(screen_h);
5173 let right = intersection.3.min(screen_w).min(row_bytes as i16);
5174 if top >= bottom || left >= right {
5175 return;
5176 }
5177 for y in top..bottom {
5178 let saved_offset =
5179 (y - save_top) as usize * row_width + (left - save_left) as usize;
5180 let len = (right - left) as usize;
5181 let row_addr = screen_base + (y as u32) * row_bytes + (left as u32);
5182 let row = bus.read_bytes(row_addr, len);
5183 saved[saved_offset..saved_offset + len].copy_from_slice(&row);
5184 }
5185 }
5186 1 => {
5187 if save_right <= 0 {
5188 return;
5189 }
5190 let byte_left = (save_left.max(0) as u32) / 8;
5191 let byte_right = (save_right as u32).div_ceil(8);
5192 let byte_end = byte_right.min(row_bytes);
5193 if byte_left >= byte_end {
5194 return;
5195 }
5196 let row_len = (byte_end - byte_left) as usize;
5197 let first_saved_row = save_top.max(0);
5198 let top = intersection.0.max(0);
5199 let left = intersection.1.max(0);
5200 let bottom = intersection.2.min(screen_h);
5201 let right = intersection
5202 .3
5203 .min(screen_w)
5204 .min((row_bytes.saturating_mul(8)) as i16);
5205 if top >= bottom || left >= right {
5206 return;
5207 }
5208 for y in top..bottom {
5209 let row_offset = (y - first_saved_row) as usize * row_len;
5210 if row_offset + row_len > saved.len() {
5211 return;
5212 }
5213 for x in left..right {
5214 let byte_x = (x as u32) / 8;
5215 if byte_x < byte_left || byte_x >= byte_end {
5216 continue;
5217 }
5218 let bit = 7 - ((x as u32) & 7);
5219 let mask = 1u8 << bit;
5220 let screen_byte =
5221 bus.read_byte(screen_base + (y as u32) * row_bytes + byte_x);
5222 let saved_idx = row_offset + (byte_x - byte_left) as usize;
5223 if (screen_byte & mask) != 0 {
5224 saved[saved_idx] |= mask;
5225 } else {
5226 saved[saved_idx] &= !mask;
5227 }
5228 }
5229 }
5230 }
5231 _ => {}
5232 }
5233 }
5234
5235 pub(crate) fn restore_dialog_pixels(
5237 &self,
5238 bus: &mut MacMemoryBus,
5239 rect: (i16, i16, i16, i16),
5240 saved: &[u8],
5241 ) {
5242 let (screen_base, row_bytes, _, screen_h, pixel_size) = self.get_screen_params();
5243 let (save_top, save_left, save_bottom, save_right) = Self::dialog_saved_pixel_rect(rect);
5244 let row_width = (save_right - save_left) as usize;
5247 let mut idx = 0;
5248 let screen_h_i16 = screen_h;
5249 for y in save_top..save_bottom {
5250 let y_on_screen = y >= 0 && y < screen_h_i16;
5251 if pixel_size == 8 {
5252 let on_screen_row =
5253 y_on_screen && save_left >= 0 && (save_right as u32) <= row_bytes;
5254 if on_screen_row && idx + row_width <= saved.len() {
5255 let row_addr = screen_base + (y as u32) * row_bytes + (save_left as u32);
5256 bus.write_bytes(row_addr, &saved[idx..idx + row_width]);
5257 idx += row_width;
5258 } else if y_on_screen {
5259 for x in save_left..save_right {
5260 if idx < saved.len() {
5261 if x >= 0 && (x as u32) < row_bytes {
5262 let addr = screen_base + (y as u32) * row_bytes + (x as u32);
5263 bus.write_byte(addr, saved[idx]);
5264 }
5265 idx += 1;
5266 }
5267 }
5268 } else {
5269 idx += row_width.min(saved.len() - idx);
5272 }
5273 } else if y_on_screen {
5274 let byte_left = (save_left.max(0) as u32) / 8;
5275 let byte_right = (save_right as u32).div_ceil(8);
5276 let bx_end = byte_right.min(row_bytes);
5277 if byte_left < bx_end {
5278 let len = (bx_end - byte_left) as usize;
5279 if idx + len <= saved.len() {
5280 let row_addr = screen_base + (y as u32) * row_bytes + byte_left;
5281 bus.write_bytes(row_addr, &saved[idx..idx + len]);
5282 idx += len;
5283 } else {
5284 for bx in byte_left..bx_end {
5285 if idx < saved.len() {
5286 bus.write_byte(
5287 screen_base + (y as u32) * row_bytes + bx,
5288 saved[idx],
5289 );
5290 idx += 1;
5291 }
5292 }
5293 }
5294 }
5295 }
5296 }
5299 }
5300
5301 fn restore_dialog_pixels_outside_rect(
5302 &self,
5303 bus: &mut MacMemoryBus,
5304 rect: (i16, i16, i16, i16),
5305 saved: &[u8],
5306 keep_rect: (i16, i16, i16, i16),
5307 ) {
5308 let (screen_base, row_bytes, _, screen_h, pixel_size) = self.get_screen_params();
5309 let (top, left, bottom, right) = rect;
5310 let margin = Self::DBOX_FRAME_MARGIN;
5311 let save_top = top - margin;
5312 let save_left = left - margin;
5313 let save_bottom = bottom + margin;
5314 let save_right = right + margin;
5315 let row_width = (save_right - save_left) as usize;
5316 let mut idx = 0;
5317 let screen_h_i16 = screen_h;
5318 for y in save_top..save_bottom {
5319 let y_on_screen = y >= 0 && y < screen_h_i16;
5320 if pixel_size == 8 {
5321 if y_on_screen {
5322 for x in save_left..save_right {
5323 if idx < saved.len() {
5324 if (y < keep_rect.0
5325 || y >= keep_rect.2
5326 || x < keep_rect.1
5327 || x >= keep_rect.3)
5328 && x >= 0
5329 && (x as u32) < row_bytes
5330 {
5331 let addr = screen_base + (y as u32) * row_bytes + (x as u32);
5332 bus.write_byte(addr, saved[idx]);
5333 }
5334 idx += 1;
5335 }
5336 }
5337 } else {
5338 idx += row_width.min(saved.len().saturating_sub(idx));
5339 }
5340 } else if y_on_screen {
5341 let byte_left = (save_left.max(0) as u32) / 8;
5342 let byte_right = (save_right as u32).div_ceil(8);
5343 let bx_end = byte_right.min(row_bytes);
5344 if byte_left < bx_end {
5345 for bx in byte_left..bx_end {
5346 if idx >= saved.len() {
5347 break;
5348 }
5349 let mut restore_mask = 0u8;
5350 for bit in 0..8u32 {
5351 let x = (bx * 8 + bit) as i16;
5352 if x >= save_left
5353 && x < save_right
5354 && (y < keep_rect.0
5355 || y >= keep_rect.2
5356 || x < keep_rect.1
5357 || x >= keep_rect.3)
5358 {
5359 restore_mask |= 0x80 >> bit;
5360 }
5361 }
5362 if restore_mask != 0 {
5363 let addr = screen_base + (y as u32) * row_bytes + bx;
5364 let current = bus.read_byte(addr);
5365 bus.write_byte(
5366 addr,
5367 (current & !restore_mask) | (saved[idx] & restore_mask),
5368 );
5369 }
5370 idx += 1;
5371 }
5372 }
5373 }
5374 }
5375 }
5376
5377 fn save_rect_pixels(&self, bus: &MacMemoryBus, rect: (i16, i16, i16, i16)) -> Vec<u8> {
5381 let (screen_base, row_bytes, _, screen_h, pixel_size) = self.get_screen_params();
5382 let (top, left, bottom, right) = rect;
5383 let row_width = (right - left).max(0) as usize;
5384 let mut saved = Vec::with_capacity((bottom - top).max(0) as usize * row_width);
5385 let screen_h_i16 = screen_h;
5386 for y in top..bottom {
5387 let y_on_screen = y >= 0 && y < screen_h_i16;
5388 if pixel_size == 8 {
5389 let on_screen_row = y_on_screen && left >= 0 && (right as u32) <= row_bytes;
5390 if on_screen_row {
5391 let row_addr = screen_base + (y as u32) * row_bytes + (left as u32);
5392 saved.extend_from_slice(&bus.read_bytes(row_addr, row_width));
5393 } else if y_on_screen {
5394 for x in left..right {
5395 if x < 0 || (x as u32) >= row_bytes {
5396 saved.push(0);
5397 } else {
5398 saved.push(
5399 bus.read_byte(screen_base + (y as u32) * row_bytes + (x as u32)),
5400 );
5401 }
5402 }
5403 } else {
5404 saved.resize(saved.len() + row_width, 0);
5405 }
5406 } else if y_on_screen {
5407 let byte_left = (left.max(0) as u32) / 8;
5408 let byte_right = (right.max(0) as u32).div_ceil(8);
5409 let bx_end = byte_right.min(row_bytes);
5410 if byte_left < bx_end {
5411 let len = (bx_end - byte_left) as usize;
5412 let row_addr = screen_base + (y as u32) * row_bytes + byte_left;
5413 saved.extend_from_slice(&bus.read_bytes(row_addr, len));
5414 }
5415 }
5416 }
5417 saved
5418 }
5419
5420 fn saved_rect_has_non_background_content(&self, saved: &[u8]) -> bool {
5421 saved.iter().any(|&byte| byte != 0)
5422 }
5423
5424 fn user_item_preserve_rects(
5425 &self,
5426 dialog_ptr: u32,
5427 bounds: (i16, i16, i16, i16),
5428 items: &[DialogItem],
5429 skip_popup_user_items: bool,
5430 skip_disabled_placeholders: bool,
5431 ) -> Vec<((i16, i16, i16, i16), bool)> {
5432 items
5433 .iter()
5434 .enumerate()
5435 .filter(|(i, it)| {
5436 (it.item_type & 0x7F) == 0
5437 && (!skip_disabled_placeholders
5438 || (it.item_type & 0x80) == 0
5439 || it.proc_ptr != 0)
5440 && (!skip_popup_user_items
5441 || !self
5442 .dialog_item_popup_menus
5443 .contains_key(&(dialog_ptr, (i + 1) as i16)))
5444 })
5445 .map(|(_, it)| {
5446 let (it_t, it_l, it_b, it_r) = it.rect;
5447 let rect = (
5448 bounds.0 + it_t,
5449 bounds.1 + it_l,
5450 bounds.0 + it_b,
5451 bounds.1 + it_r,
5452 );
5453 (rect, it.item_type == 0)
5454 })
5455 .collect()
5456 }
5457
5458 fn restore_user_item_preserved_pixels(
5459 &self,
5460 bus: &mut MacMemoryBus,
5461 rects: &[((i16, i16, i16, i16), bool)],
5462 backups: &[Vec<u8>],
5463 ) {
5464 for ((rect, always_restore), pixels) in rects.iter().zip(backups.iter()) {
5465 if *always_restore || self.saved_rect_has_non_background_content(pixels) {
5466 self.restore_rect_pixels(bus, *rect, pixels);
5467 }
5468 }
5469 }
5470
5471 fn draw_dialog_preserving_user_items(
5472 &mut self,
5473 bus: &mut MacMemoryBus,
5474 bounds: (i16, i16, i16, i16),
5475 proc_id: i16,
5476 title: &str,
5477 items: &[DialogItem],
5478 default_item: i16,
5479 edit_text: &str,
5480 edit_item: i16,
5481 skip_pictures: bool,
5482 dialog_ptr: u32,
5483 preserve_user_items: bool,
5484 skip_popup_user_items: bool,
5485 skip_disabled_placeholders: bool,
5486 ) {
5487 let user_item_rects = if preserve_user_items {
5488 self.user_item_preserve_rects(
5489 dialog_ptr,
5490 bounds,
5491 items,
5492 skip_popup_user_items,
5493 skip_disabled_placeholders,
5494 )
5495 } else {
5496 Vec::new()
5497 };
5498 let user_item_backups: Vec<Vec<u8>> = user_item_rects
5499 .iter()
5500 .map(|&(r, _)| self.save_rect_pixels(bus, r))
5501 .collect();
5502
5503 self.draw_dialog(
5504 bus,
5505 bounds,
5506 proc_id,
5507 title,
5508 items,
5509 default_item,
5510 edit_text,
5511 edit_item,
5512 skip_pictures,
5513 dialog_ptr,
5514 );
5515
5516 self.restore_user_item_preserved_pixels(bus, &user_item_rects, &user_item_backups);
5517 self.capture_gui_frame(bus, &format!("draw_dialog_preserved_{:08X}", dialog_ptr));
5518 }
5519
5520 fn fill_rect_clipped_to_dialog(
5521 &self,
5522 bus: &mut MacMemoryBus,
5523 bounds: (i16, i16, i16, i16),
5524 rect: (i16, i16, i16, i16),
5525 value: bool,
5526 ) {
5527 let top = rect.0.max(bounds.0);
5528 let left = rect.1.max(bounds.1);
5529 let bottom = rect.2.min(bounds.2);
5530 let right = rect.3.min(bounds.3);
5531 if top >= bottom || left >= right {
5532 return;
5533 }
5534
5535 let (screen_base, row_bytes, screen_width, screen_height, pixel_size) =
5536 self.get_screen_params();
5537 Self::fb_fill_rect(
5538 bus,
5539 screen_base,
5540 row_bytes,
5541 pixel_size,
5542 screen_width,
5543 screen_height,
5544 top,
5545 left,
5546 bottom,
5547 right,
5548 value,
5549 );
5550 }
5551
5552 fn dialog_item_enclosing_local_rect(
5553 item_type: u8,
5554 rect: (i16, i16, i16, i16),
5555 ) -> (i16, i16, i16, i16) {
5556 match item_type & 0x7F {
5557 16 => (rect.0 - 3, rect.1 - 3, rect.2 + 3, rect.3 + 3),
5560 _ => rect,
5561 }
5562 }
5563
5564 fn erase_dialog_item_enclosing_rect(
5565 &self,
5566 bus: &mut MacMemoryBus,
5567 dialog_ptr: u32,
5568 local_rect: (i16, i16, i16, i16),
5569 ) {
5570 let bounds = Self::dialog_screen_bounds(bus, dialog_ptr);
5571 let screen_rect = (
5572 bounds.0 + local_rect.0,
5573 bounds.1 + local_rect.1,
5574 bounds.0 + local_rect.2,
5575 bounds.1 + local_rect.3,
5576 );
5577 self.fill_rect_clipped_to_dialog(bus, bounds, screen_rect, false);
5578 }
5579
5580 fn restore_rect_pixels(
5585 &self,
5586 bus: &mut MacMemoryBus,
5587 rect: (i16, i16, i16, i16),
5588 saved: &[u8],
5589 ) {
5590 let (screen_base, row_bytes, _, screen_h, pixel_size) = self.get_screen_params();
5591 let (top, left, bottom, right) = rect;
5592 let row_width = (right - left).max(0) as usize;
5593 let mut idx = 0;
5594 let screen_h_i16 = screen_h;
5595 for y in top..bottom {
5596 let y_on_screen = y >= 0 && y < screen_h_i16;
5597 if pixel_size == 8 {
5598 let on_screen_row = y_on_screen && left >= 0 && (right as u32) <= row_bytes;
5599 if on_screen_row && idx + row_width <= saved.len() {
5600 let row_addr = screen_base + (y as u32) * row_bytes + (left as u32);
5601 bus.write_bytes(row_addr, &saved[idx..idx + row_width]);
5602 idx += row_width;
5603 } else if y_on_screen {
5604 for x in left..right {
5605 if idx < saved.len() {
5606 if x >= 0 && (x as u32) < row_bytes {
5607 bus.write_byte(
5608 screen_base + (y as u32) * row_bytes + (x as u32),
5609 saved[idx],
5610 );
5611 }
5612 idx += 1;
5613 }
5614 }
5615 } else {
5616 idx = (idx + row_width).min(saved.len());
5617 }
5618 } else if y_on_screen {
5619 let byte_left = (left.max(0) as u32) / 8;
5620 let byte_right = (right.max(0) as u32).div_ceil(8);
5621 let bx_end = byte_right.min(row_bytes);
5622 if byte_left < bx_end {
5623 let len = (bx_end - byte_left) as usize;
5624 if idx + len <= saved.len() {
5625 let row_addr = screen_base + (y as u32) * row_bytes + byte_left;
5626 bus.write_bytes(row_addr, &saved[idx..idx + len]);
5627 idx += len;
5628 } else {
5629 for bx in byte_left..bx_end {
5630 if idx < saved.len() {
5631 bus.write_byte(
5632 screen_base + (y as u32) * row_bytes + bx,
5633 saved[idx],
5634 );
5635 idx += 1;
5636 }
5637 }
5638 }
5639 }
5640 }
5641 }
5642 }
5643
5644 pub(crate) fn draw_dialog(
5648 &mut self,
5649 bus: &mut MacMemoryBus,
5650 bounds: (i16, i16, i16, i16),
5651 proc_id: i16,
5652 title: &str,
5653 items: &[DialogItem],
5654 default_item: i16,
5655 edit_text: &str,
5656 edit_item: i16,
5657 skip_pictures: bool,
5658 dialog_ptr: u32,
5659 ) {
5660 self.ensure_dialog_background_saved(bus, dialog_ptr, bounds);
5661 self.dialog_initial_draw_deferred.remove(&dialog_ptr);
5662
5663 let (screen_base, row_bytes, screen_width, screen_height, pixel_size) =
5664 self.get_screen_params();
5665 let (top, left, bottom, right) = bounds;
5666
5667 let game_managed = Self::dialog_is_game_managed(bounds, items);
5671 if game_managed {
5672 eprintln!(
5673 "[DIALOG] Skipping white fill for game-managed dialog at ({},{},{},{}), {} items",
5674 top,
5675 left,
5676 bottom,
5677 right,
5678 items.len()
5679 );
5680 }
5681 let themed_frame = match proc_id {
5682 1 => (
5683 top - Self::DBOX_FRAME_MARGIN,
5684 left - Self::DBOX_FRAME_MARGIN,
5685 bottom + Self::DBOX_FRAME_MARGIN,
5686 right + Self::DBOX_FRAME_MARGIN,
5687 ),
5688 2 => (top - 1, left - 1, bottom + 1, right + 1),
5689 3 => (top - 1, left - 1, bottom + 3, right + 3),
5690 _ => (top, left, bottom + 2, right + 2),
5691 };
5692 if !self.draw_theme_dialog_frame(
5693 bus,
5694 (top, left, bottom, right),
5695 themed_frame,
5696 proc_id,
5697 true,
5698 !game_managed,
5699 ) {
5700 if !game_managed {
5701 Self::fb_fill_rect(
5702 bus,
5703 screen_base,
5704 row_bytes,
5705 pixel_size,
5706 screen_width,
5707 screen_height,
5708 top,
5709 left,
5710 bottom,
5711 right,
5712 false,
5713 );
5714 }
5715
5716 match proc_id {
5724 1 => {
5725 self.draw_classic_dbox_frame(bus, top, left, bottom, right);
5726 }
5727 2 => {
5728 self.draw_rect_border(bus, top - 1, left - 1, bottom + 1, right + 1);
5732 }
5733 3 => {
5734 self.draw_rect_border(bus, top - 1, left - 1, bottom + 1, right + 1);
5736 self.draw_shadow(bus, top - 1, left - 1, bottom + 1, right + 1);
5737 }
5738 0 | 4 => {
5739 let saved_bounds = self.window_bounds;
5745 let saved_proc_id = self.window_proc_id;
5746 let saved_title = self.window_title.clone();
5747 let saved_go_away = self.go_away_flag;
5748 self.window_bounds = bounds;
5749 self.window_proc_id = proc_id;
5750 self.window_title = if title.is_empty() {
5751 Self::dialog_window_title(bus, dialog_ptr)
5752 } else {
5753 title.to_string()
5754 };
5755 self.go_away_flag = dialog_ptr != 0 && bus.read_byte(dialog_ptr + 112) != 0;
5756 self.draw_window_frame(bus);
5757 self.window_bounds = saved_bounds;
5758 self.window_proc_id = saved_proc_id;
5759 self.window_title = saved_title;
5760 self.go_away_flag = saved_go_away;
5761 }
5762 _ => {
5763 self.draw_rect_border(bus, top, left, bottom, right);
5765 self.draw_shadow(bus, top, left, bottom, right);
5766 }
5767 }
5768 }
5769 let trace_items = trace_dialog_items_enabled();
5773 if trace_items {
5774 eprintln!(
5775 "[DLG] draw_dialog bounds=({},{},{},{}) proc_id={} edit_item={} default_item={} items={}",
5776 top,
5777 left,
5778 bottom,
5779 right,
5780 proc_id,
5781 edit_item,
5782 default_item,
5783 items.len()
5784 );
5785 }
5786
5787 let auto_default_outline = false;
5795
5796 for (i, item) in items.iter().enumerate() {
5798 let item_num = (i + 1) as i16; let (it, il, ib, ir) = item.rect;
5800 let abs_top = top + it;
5802 let abs_left = left + il;
5803 let abs_bottom = top + ib;
5804 let abs_right = left + ir;
5805
5806 let base_type = item.item_type & 0x7F;
5807 if trace_items {
5808 eprintln!(
5809 "[DLG] item #{} type=0x{:02X} (base={}) rel=({},{},{},{}) abs=({},{},{},{}) rsrc={} text={:?}",
5810 item_num,
5811 item.item_type,
5812 base_type,
5813 it,
5814 il,
5815 ib,
5816 ir,
5817 abs_top,
5818 abs_left,
5819 abs_bottom,
5820 abs_right,
5821 item.resource_id,
5822 item.text,
5823 );
5824 }
5825
5826 if abs_top >= bottom || abs_bottom <= top || abs_left >= right || abs_right <= left {
5835 if trace_items {
5836 eprintln!("[DLG] -> skipped (fully outside dialog rect)");
5837 }
5838 continue;
5839 }
5840
5841 if matches!(base_type, 4..=7) {
5847 continue;
5848 }
5849
5850 let enabled = (item.item_type & 0x80) == 0;
5851 match base_type {
5852 4 => {
5854 self.draw_button_with_enabled(
5860 bus,
5861 abs_top,
5862 abs_left,
5863 abs_bottom,
5864 abs_right,
5865 &item.text,
5866 auto_default_outline && item_num == default_item,
5867 enabled,
5868 );
5869 }
5870 5 => {
5872 let checked = self
5873 .dialog_control_values
5874 .get(&(dialog_ptr, item_num))
5875 .copied()
5876 .unwrap_or(0)
5877 != 0;
5878 self.draw_checkbox_with_enabled(
5879 bus, abs_top, abs_left, abs_bottom, abs_right, &item.text, checked, enabled,
5880 );
5881 }
5882 6 => {
5884 let selected = self
5885 .dialog_control_values
5886 .get(&(dialog_ptr, item_num))
5887 .copied()
5888 .unwrap_or(0)
5889 != 0;
5890 self.draw_radio_with_enabled(
5891 bus, abs_top, abs_left, abs_bottom, abs_right, &item.text, selected,
5892 enabled,
5893 );
5894 }
5895 8 => {
5897 self.draw_static_text(
5898 bus, abs_top, abs_left, abs_bottom, abs_right, &item.text,
5899 );
5900 }
5901 16 => {
5903 let display_text = if item_num == edit_item {
5904 edit_text
5905 } else {
5906 &item.text
5907 };
5908 let selection_range = if item_num == edit_item {
5913 Self::dialog_item_selection_range(item, display_text.len())
5914 } else {
5915 None
5916 };
5917 self.draw_edit_text_with_cursor(
5918 bus,
5919 abs_top,
5920 abs_left,
5921 abs_bottom,
5922 abs_right,
5923 display_text,
5924 selection_range,
5925 false,
5926 enabled,
5927 );
5928 }
5929 32 => {
5931 if !skip_pictures && item.resource_id != 0 {
5932 let drew_cicn = self
5933 .find_resource_any(*b"cicn", item.resource_id)
5934 .is_some_and(|(_, icon_ptr)| {
5935 self.draw_cicn_icon(
5936 bus, abs_top, abs_left, abs_bottom, abs_right, icon_ptr,
5937 )
5938 });
5939 if !drew_cicn {
5940 if let Some((_, icon_ptr)) =
5941 self.find_resource_any(*b"ICON", item.resource_id)
5942 {
5943 self.draw_icon(
5946 bus, abs_top, abs_left, abs_bottom, abs_right, icon_ptr,
5947 );
5948 }
5949 }
5950 }
5951 }
5952 64 => {
5954 if !skip_pictures && item.resource_id != 0 {
5957 if let Some((_, pic_ptr)) =
5958 self.find_resource_any(*b"PICT", item.resource_id)
5959 {
5960 let device_ct_seed =
5962 Self::ctab_seed(bus, self.current_gdevice_ctab_handle(bus))
5963 .unwrap_or(0);
5964 super::pict::draw_picture(
5965 bus,
5966 pic_ptr,
5967 abs_top,
5968 abs_left,
5969 abs_bottom,
5970 abs_right,
5971 self.screen_mode,
5972 &self.device_clut,
5973 device_ct_seed,
5974 None,
5975 );
5976 }
5977 }
5978 }
5979 7 => {
5982 if let Some(ctrl_handle) =
5983 self.dialog_control_handle_for_item(dialog_ptr, item_num)
5984 {
5985 let ctrl_ptr = bus.read_long(ctrl_handle);
5986 let proc_id_ctrl =
5987 self.control_proc_ids.get(&ctrl_ptr).copied().unwrap_or(0);
5988 let value = self
5989 .dialog_control_values
5990 .get(&(dialog_ptr, item_num))
5991 .copied()
5992 .unwrap_or_else(|| bus.read_word(ctrl_ptr + 18) as i16);
5993 let min = bus.read_word(ctrl_ptr + 20) as i16;
5994 let max = bus.read_word(ctrl_ptr + 22) as i16;
5995 let hilite = bus.read_byte(ctrl_ptr + 17);
5996 let title =
5997 decode_mac_roman_for_render(&Self::control_title_bytes(bus, ctrl_ptr));
5998
5999 match proc_id_ctrl {
6000 0 => self.draw_button_with_enabled(
6001 bus,
6002 abs_top,
6003 abs_left,
6004 abs_bottom,
6005 abs_right,
6006 &title,
6007 auto_default_outline && item_num == default_item,
6008 enabled,
6009 ),
6010 1 => self.draw_checkbox_with_enabled(
6011 bus,
6012 abs_top,
6013 abs_left,
6014 abs_bottom,
6015 abs_right,
6016 &title,
6017 value != 0,
6018 enabled,
6019 ),
6020 2 => self.draw_radio_with_enabled(
6021 bus,
6022 abs_top,
6023 abs_left,
6024 abs_bottom,
6025 abs_right,
6026 &title,
6027 value != 0,
6028 enabled,
6029 ),
6030 16 => self.draw_scroll_bar(
6031 bus, abs_top, abs_left, abs_bottom, abs_right, value, min, max,
6032 hilite,
6033 ),
6034 proc_id if Self::is_popup_menu_proc_id(proc_id) => {
6035 let menu_id = self.popup_control_menu_id(bus, ctrl_ptr, min);
6036 let selected = value.max(1) as usize;
6037 let item_title = self.popup_menu_item_title(bus, menu_id, selected);
6038 let (draw_top, draw_left, draw_bottom, draw_right) = self
6039 .popup_control_box_rect(
6040 bus, abs_top, abs_left, abs_bottom, abs_right, menu_id,
6041 max, proc_id,
6042 );
6043 self.draw_popup_control_label(
6044 bus,
6045 abs_top,
6046 abs_left,
6047 abs_bottom,
6048 draw_left,
6049 &title,
6050 enabled && hilite != 255,
6051 );
6052 self.draw_popup_control_with_state(
6053 bus,
6054 draw_top,
6055 draw_left,
6056 draw_bottom,
6057 draw_right,
6058 &item_title.unwrap_or_default(),
6059 enabled && hilite != 255,
6060 hilite == 1,
6061 );
6062 }
6063 _ => {
6064 self.draw_button_with_enabled(
6065 bus,
6066 abs_top,
6067 abs_left,
6068 abs_bottom,
6069 abs_right,
6070 &title,
6071 auto_default_outline && item_num == default_item,
6072 enabled,
6073 );
6074 }
6075 }
6076 } else {
6077 self.draw_button_with_enabled(
6078 bus,
6079 abs_top,
6080 abs_left,
6081 abs_bottom,
6082 abs_right,
6083 "",
6084 auto_default_outline && item_num == default_item,
6085 enabled,
6086 );
6087 }
6088 }
6089 _ => {}
6091 }
6092 }
6093
6094 for (i, item) in items.iter().enumerate().rev() {
6101 let item_num = (i + 1) as i16;
6102 let (it, il, ib, ir) = item.rect;
6103 let abs_top = top + it;
6104 let abs_left = left + il;
6105 let abs_bottom = top + ib;
6106 let abs_right = left + ir;
6107 let base_type = item.item_type & 0x7F;
6108 if !matches!(base_type, 4..=7) {
6109 continue;
6110 }
6111 if abs_top >= bottom || abs_bottom <= top || abs_left >= right || abs_right <= left {
6112 continue;
6113 }
6114
6115 let enabled = (item.item_type & 0x80) == 0;
6116 if trace_items {
6117 if let Some(ctrl_handle) = self.dialog_control_handle_for_item(dialog_ptr, item_num)
6118 {
6119 let ctrl_ptr = bus.read_long(ctrl_handle);
6120 let proc_id_ctrl = self.control_proc_ids.get(&ctrl_ptr).copied().unwrap_or(0);
6121 eprintln!(
6122 "[DLG] item={} control handle=${:08X} ptr=${:08X} visible={} hilite={} value={} min={} max={} proc_id={}",
6123 item_num,
6124 ctrl_handle,
6125 ctrl_ptr,
6126 ctrl_ptr != 0 && bus.read_byte(ctrl_ptr + 16) != 0,
6127 if ctrl_ptr == 0 {
6128 -1
6129 } else {
6130 bus.read_byte(ctrl_ptr + 17) as i16
6131 },
6132 if ctrl_ptr == 0 {
6133 0
6134 } else {
6135 bus.read_word(ctrl_ptr + 18) as i16
6136 },
6137 if ctrl_ptr == 0 {
6138 0
6139 } else {
6140 bus.read_word(ctrl_ptr + 20) as i16
6141 },
6142 if ctrl_ptr == 0 {
6143 0
6144 } else {
6145 bus.read_word(ctrl_ptr + 22) as i16
6146 },
6147 proc_id_ctrl,
6148 );
6149 } else {
6150 eprintln!("[DLG] item={} control handle=<none>", item_num);
6151 }
6152 }
6153 match base_type {
6154 4 => {
6155 self.draw_button_with_enabled(
6156 bus,
6157 abs_top,
6158 abs_left,
6159 abs_bottom,
6160 abs_right,
6161 &item.text,
6162 auto_default_outline && item_num == default_item,
6163 enabled,
6164 );
6165 }
6166 5 => {
6167 let inactive = self.dialog_control_inactive(bus, dialog_ptr, item_num);
6168 let checked = self
6169 .dialog_control_values
6170 .get(&(dialog_ptr, item_num))
6171 .copied()
6172 .unwrap_or(0)
6173 != 0;
6174 self.draw_checkbox_with_enabled_and_inactive(
6175 bus, abs_top, abs_left, abs_bottom, abs_right, &item.text, checked,
6176 enabled, inactive,
6177 );
6178 }
6179 6 => {
6180 let inactive = self.dialog_control_inactive(bus, dialog_ptr, item_num);
6181 let selected = self
6182 .dialog_control_values
6183 .get(&(dialog_ptr, item_num))
6184 .copied()
6185 .unwrap_or(0)
6186 != 0;
6187 self.draw_radio_with_enabled_and_inactive(
6188 bus, abs_top, abs_left, abs_bottom, abs_right, &item.text, selected,
6189 enabled, inactive,
6190 );
6191 }
6192 7 => {
6193 if let Some(ctrl_handle) =
6194 self.dialog_control_handle_for_item(dialog_ptr, item_num)
6195 {
6196 let ctrl_ptr = bus.read_long(ctrl_handle);
6197 let proc_id_ctrl =
6198 self.control_proc_ids.get(&ctrl_ptr).copied().unwrap_or(0);
6199 let value = self
6200 .dialog_control_values
6201 .get(&(dialog_ptr, item_num))
6202 .copied()
6203 .unwrap_or_else(|| bus.read_word(ctrl_ptr + 18) as i16);
6204 let min = bus.read_word(ctrl_ptr + 20) as i16;
6205 let max = bus.read_word(ctrl_ptr + 22) as i16;
6206 let hilite = bus.read_byte(ctrl_ptr + 17);
6207 let title =
6208 decode_mac_roman_for_render(&Self::control_title_bytes(bus, ctrl_ptr));
6209
6210 match proc_id_ctrl {
6211 0 => self.draw_button_with_enabled(
6212 bus,
6213 abs_top,
6214 abs_left,
6215 abs_bottom,
6216 abs_right,
6217 &title,
6218 auto_default_outline && item_num == default_item,
6219 enabled,
6220 ),
6221 1 => self.draw_checkbox_with_enabled_and_inactive(
6222 bus,
6223 abs_top,
6224 abs_left,
6225 abs_bottom,
6226 abs_right,
6227 &title,
6228 value != 0,
6229 enabled,
6230 hilite == 255,
6231 ),
6232 2 => self.draw_radio_with_enabled_and_inactive(
6233 bus,
6234 abs_top,
6235 abs_left,
6236 abs_bottom,
6237 abs_right,
6238 &title,
6239 value != 0,
6240 enabled,
6241 hilite == 255,
6242 ),
6243 16 => self.draw_scroll_bar(
6244 bus, abs_top, abs_left, abs_bottom, abs_right, value, min, max,
6245 hilite,
6246 ),
6247 proc_id if Self::is_popup_menu_proc_id(proc_id) => {
6248 let menu_id = self.popup_control_menu_id(bus, ctrl_ptr, min);
6249 let selected = value.max(1) as usize;
6250 let item_title = self.popup_menu_item_title(bus, menu_id, selected);
6251 let (draw_top, draw_left, draw_bottom, draw_right) = self
6252 .popup_control_box_rect(
6253 bus, abs_top, abs_left, abs_bottom, abs_right, menu_id,
6254 max, proc_id,
6255 );
6256 self.draw_popup_control_label(
6257 bus,
6258 abs_top,
6259 abs_left,
6260 abs_bottom,
6261 draw_left,
6262 &title,
6263 enabled && hilite != 255,
6264 );
6265 self.draw_popup_control_with_state(
6266 bus,
6267 draw_top,
6268 draw_left,
6269 draw_bottom,
6270 draw_right,
6271 &item_title.unwrap_or_default(),
6272 enabled && hilite != 255,
6273 hilite == 1,
6274 );
6275 }
6276 _ => {
6277 self.draw_button_with_enabled(
6278 bus,
6279 abs_top,
6280 abs_left,
6281 abs_bottom,
6282 abs_right,
6283 &title,
6284 auto_default_outline && item_num == default_item,
6285 enabled,
6286 );
6287 }
6288 }
6289 } else {
6290 self.draw_button_with_enabled(
6291 bus,
6292 abs_top,
6293 abs_left,
6294 abs_bottom,
6295 abs_right,
6296 "",
6297 auto_default_outline && item_num == default_item,
6298 enabled,
6299 );
6300 }
6301 }
6302 _ => {}
6303 }
6304 }
6305 self.capture_gui_frame(bus, &format!("draw_dialog_{:08X}", dialog_ptr));
6306 }
6307
6308 fn redraw_standard_dialog_items(
6309 &self,
6310 bus: &mut MacMemoryBus,
6311 bounds: (i16, i16, i16, i16),
6312 items: &[DialogItem],
6313 default_item: i16,
6314 edit_text: &str,
6315 edit_item: i16,
6316 dialog_ptr: u32,
6317 ) {
6318 let (top, left, bottom, right) = bounds;
6319 let auto_default_outline = false;
6323 for (i, item) in items.iter().enumerate() {
6324 let item_num = (i + 1) as i16;
6325 let (it, il, ib, ir) = item.rect;
6326 let abs_top = top + it;
6327 let abs_left = left + il;
6328 let abs_bottom = top + ib;
6329 let abs_right = left + ir;
6330 if abs_top >= bottom || abs_bottom <= top || abs_left >= right || abs_right <= left {
6331 continue;
6332 }
6333
6334 let enabled = (item.item_type & 0x80) == 0;
6335 match item.item_type & 0x7F {
6336 4 => self.draw_button_with_enabled(
6337 bus,
6338 abs_top,
6339 abs_left,
6340 abs_bottom,
6341 abs_right,
6342 &item.text,
6343 auto_default_outline && item_num == default_item,
6344 enabled,
6345 ),
6346 5 => {
6347 let inactive = self.dialog_control_inactive(bus, dialog_ptr, item_num);
6348 let checked = self
6349 .dialog_control_values
6350 .get(&(dialog_ptr, item_num))
6351 .copied()
6352 .unwrap_or(0)
6353 != 0;
6354 self.fill_rect_clipped_to_dialog(
6355 bus,
6356 bounds,
6357 (abs_top, abs_left, abs_bottom, abs_right),
6358 false,
6359 );
6360 self.draw_checkbox_with_enabled_and_inactive(
6361 bus, abs_top, abs_left, abs_bottom, abs_right, &item.text, checked,
6362 enabled, inactive,
6363 );
6364 }
6365 6 => {
6366 let inactive = self.dialog_control_inactive(bus, dialog_ptr, item_num);
6367 self.fill_rect_clipped_to_dialog(
6368 bus,
6369 bounds,
6370 (abs_top, abs_left, abs_bottom, abs_right),
6371 false,
6372 );
6373 self.draw_radio_with_enabled_and_inactive(
6374 bus, abs_top, abs_left, abs_bottom, abs_right, &item.text, false, enabled,
6375 inactive,
6376 );
6377 }
6378 7 => {
6379 if let Some(ctrl_handle) =
6380 self.dialog_control_handle_for_item(dialog_ptr, item_num)
6381 {
6382 let ctrl_ptr = bus.read_long(ctrl_handle);
6383 let proc_id_ctrl =
6384 self.control_proc_ids.get(&ctrl_ptr).copied().unwrap_or(0);
6385 let value = self
6386 .dialog_control_values
6387 .get(&(dialog_ptr, item_num))
6388 .copied()
6389 .unwrap_or_else(|| bus.read_word(ctrl_ptr + 18) as i16);
6390 let min = bus.read_word(ctrl_ptr + 20) as i16;
6391 let max = bus.read_word(ctrl_ptr + 22) as i16;
6392 let hilite = bus.read_byte(ctrl_ptr + 17);
6393 let title =
6394 decode_mac_roman_for_render(&Self::control_title_bytes(bus, ctrl_ptr));
6395 match proc_id_ctrl {
6396 0 => self.draw_button_with_enabled(
6397 bus,
6398 abs_top,
6399 abs_left,
6400 abs_bottom,
6401 abs_right,
6402 &title,
6403 auto_default_outline && item_num == default_item,
6404 enabled,
6405 ),
6406 1 => {
6407 self.fill_rect_clipped_to_dialog(
6408 bus,
6409 bounds,
6410 (abs_top, abs_left, abs_bottom, abs_right),
6411 false,
6412 );
6413 self.draw_checkbox_with_enabled_and_inactive(
6414 bus,
6415 abs_top,
6416 abs_left,
6417 abs_bottom,
6418 abs_right,
6419 &title,
6420 value != 0,
6421 enabled,
6422 hilite == 255,
6423 )
6424 }
6425 2 => {
6426 self.fill_rect_clipped_to_dialog(
6427 bus,
6428 bounds,
6429 (abs_top, abs_left, abs_bottom, abs_right),
6430 false,
6431 );
6432 self.draw_radio_with_enabled_and_inactive(
6433 bus,
6434 abs_top,
6435 abs_left,
6436 abs_bottom,
6437 abs_right,
6438 &title,
6439 value != 0,
6440 enabled,
6441 hilite == 255,
6442 )
6443 }
6444 16 => self.draw_scroll_bar(
6445 bus, abs_top, abs_left, abs_bottom, abs_right, value, min, max,
6446 hilite,
6447 ),
6448 proc_id if Self::is_popup_menu_proc_id(proc_id) => {
6449 let menu_id = self.popup_control_menu_id(bus, ctrl_ptr, min);
6450 let selected = value.max(1) as usize;
6451 let item_title = self.popup_menu_item_title(bus, menu_id, selected);
6452 let (draw_top, draw_left, draw_bottom, draw_right) = self
6453 .popup_control_box_rect(
6454 bus, abs_top, abs_left, abs_bottom, abs_right, menu_id,
6455 max, proc_id,
6456 );
6457 self.draw_popup_control_label(
6458 bus,
6459 abs_top,
6460 abs_left,
6461 abs_bottom,
6462 draw_left,
6463 &title,
6464 enabled && hilite != 255,
6465 );
6466 self.draw_popup_control_with_state(
6467 bus,
6468 draw_top,
6469 draw_left,
6470 draw_bottom,
6471 draw_right,
6472 &item_title.unwrap_or_default(),
6473 enabled && hilite != 255,
6474 hilite == 1,
6475 );
6476 }
6477 _ => self.draw_button_with_enabled(
6478 bus,
6479 abs_top,
6480 abs_left,
6481 abs_bottom,
6482 abs_right,
6483 &title,
6484 auto_default_outline && item_num == default_item,
6485 enabled,
6486 ),
6487 }
6488 } else {
6489 self.draw_button_with_enabled(
6490 bus,
6491 abs_top,
6492 abs_left,
6493 abs_bottom,
6494 abs_right,
6495 "",
6496 auto_default_outline && item_num == default_item,
6497 enabled,
6498 );
6499 }
6500 }
6501 8 => {
6502 self.fill_rect_clipped_to_dialog(
6503 bus,
6504 bounds,
6505 (abs_top, abs_left, abs_bottom, abs_right),
6506 false,
6507 );
6508 self.draw_static_text(bus, abs_top, abs_left, abs_bottom, abs_right, &item.text)
6509 }
6510 16 => {
6511 let display_text = if item_num == edit_item {
6512 edit_text
6513 } else {
6514 &item.text
6515 };
6516 let selection_range = if item_num == edit_item {
6517 Self::dialog_item_selection_range(item, display_text.len())
6518 } else {
6519 None
6520 };
6521 self.draw_edit_text_with_cursor(
6522 bus,
6523 abs_top,
6524 abs_left,
6525 abs_bottom,
6526 abs_right,
6527 display_text,
6528 selection_range,
6529 false,
6530 enabled,
6531 );
6532 }
6533 _ => {}
6534 }
6535 }
6536 }
6537
6538 fn refresh_dialog_tracking_snapshot(&mut self, bus: &mut MacMemoryBus) {
6539 let Some(tracking) = self.dialog_tracking.as_ref() else {
6540 return;
6541 };
6542 if tracking.game_managed || !tracking.rendered_pixels_final {
6543 return;
6544 }
6545 let bounds = tracking.bounds;
6546 let items = tracking.items.clone();
6547 let default_item = tracking.default_item;
6548 let edit_text = tracking.edit_text.clone();
6549 let edit_item = tracking.edit_item;
6550 let dialog_ptr = tracking.dialog_ptr;
6551 let popup_draws = tracking.popup_draws.clone();
6552 let rendered_pixels = tracking.rendered_pixels.clone();
6553 if !rendered_pixels.is_empty() {
6554 self.restore_dialog_pixels(bus, bounds, &rendered_pixels);
6555 }
6556 self.redraw_standard_dialog_items(
6557 bus,
6558 bounds,
6559 &items,
6560 default_item,
6561 &edit_text,
6562 edit_item,
6563 dialog_ptr,
6564 );
6565 self.redraw_dialog_popup_controls(bus, &popup_draws);
6566 let rendered = self.save_dialog_pixels(bus, bounds);
6567 if let Some(tracking) = self.dialog_tracking.as_mut() {
6568 tracking.rendered_pixels = rendered;
6569 }
6570 }
6571
6572 fn persist_visible_dialog_snapshot(
6573 &mut self,
6574 bus: &MacMemoryBus,
6575 tracking: &DialogTrackingState,
6576 ) {
6577 let pixels = if tracking.rendered_pixels.is_empty() {
6578 self.save_dialog_pixels(bus, tracking.bounds)
6579 } else {
6580 tracking.rendered_pixels.clone()
6581 };
6582 self.dialog_visible_snapshots.insert(
6583 tracking.dialog_ptr,
6584 PersistentDialogSnapshot {
6585 bounds: tracking.bounds,
6586 pixels,
6587 },
6588 );
6589 }
6590
6591 pub(crate) fn refresh_visible_dialog_snapshot_for_port(
6592 &mut self,
6593 bus: &MacMemoryBus,
6594 port: u32,
6595 ) {
6596 if port == 0 {
6597 return;
6598 }
6599 let Some(bounds) = self
6600 .dialog_visible_snapshots
6601 .get(&port)
6602 .map(|snapshot| snapshot.bounds)
6603 .or_else(|| {
6604 if self.dialog_items.contains_key(&port) && self.window_visible(bus, port) {
6605 Some(Self::dialog_screen_bounds(bus, port))
6606 } else {
6607 None
6608 }
6609 })
6610 else {
6611 return;
6612 };
6613 let pixels = self.save_dialog_pixels(bus, bounds);
6614 self.dialog_visible_snapshots
6615 .insert(port, PersistentDialogSnapshot { bounds, pixels });
6616 }
6617
6618 pub(crate) fn refresh_visible_dialog_snapshot_after_bulk_port_draw(
6619 &mut self,
6620 bus: &MacMemoryBus,
6621 port: u32,
6622 ) {
6623 if port == 0 {
6624 return;
6625 }
6626 let game_managed_visible = self
6627 .dialog_items
6628 .get(&port)
6629 .filter(|_| self.window_visible(bus, port))
6630 .is_some_and(|items| {
6631 let bounds = Self::dialog_screen_bounds(bus, port);
6632 Self::dialog_is_game_managed(bounds, items)
6633 });
6634 if game_managed_visible {
6635 self.refresh_visible_dialog_snapshot_for_port(bus, port);
6636 return;
6637 }
6638 if !self.dialog_visible_snapshots.contains_key(&port) {
6639 return;
6640 }
6641 let modal_tracking_matches = self
6653 .dialog_tracking
6654 .as_ref()
6655 .is_some_and(|tracking| tracking.dialog_ptr == port);
6656 self.refresh_visible_dialog_snapshot_for_port(bus, port);
6657 if modal_tracking_matches {
6662 let bounds = self.dialog_tracking.as_ref().map(|t| t.bounds);
6663 if let Some(bounds) = bounds {
6664 let rendered = self.save_dialog_pixels(bus, bounds);
6665 if let Some(tracking) = self.dialog_tracking.as_mut() {
6666 tracking.rendered_pixels = rendered;
6667 tracking.rendered_pixels_final = true;
6668 }
6669 }
6670 }
6671 }
6672
6673 pub(crate) fn redraw_retained_modal_dialog_click(&self, bus: &mut MacMemoryBus) {
6674 let Some(click) = self.retained_modal_dialog_click.as_ref() else {
6675 return;
6676 };
6677 if !click.highlighted || click.item_no <= 0 || self.front_window != click.dialog_ptr {
6678 return;
6679 }
6680 let bounds = self
6681 .dialog_visible_snapshots
6682 .get(&click.dialog_ptr)
6683 .map(|snapshot| snapshot.bounds)
6684 .unwrap_or_else(|| Self::dialog_screen_bounds(bus, click.dialog_ptr));
6685 let rect = Self::dialog_item_screen_rect(bounds, click.rect);
6686 self.draw_dialog_button_highlight_state(bus, rect, &click.title, click.is_default, true);
6687 }
6688
6689 pub(crate) fn finalize_dialog_draw_procs_if_idle(&mut self, bus: &mut MacMemoryBus) {
6690 let Some(tracking) = self.dialog_tracking.as_ref() else {
6691 if let Some(dialog_ptr) = self.active_modeless_dialog_draw_proc.take() {
6692 self.refresh_visible_dialog_snapshot_for_port(bus, dialog_ptr);
6693 self.capture_gui_frame(
6694 bus,
6695 &format!("modeless_dialog_draw_proc_{:08X}", dialog_ptr),
6696 );
6697 }
6698 return;
6699 };
6700 if tracking.draw_procs_done || !tracking.draw_proc_queue.is_empty() {
6701 return;
6702 }
6703
6704 let bounds = tracking.bounds;
6705 let items = tracking.items.clone();
6706 let default_item = tracking.default_item;
6707 let edit_text = tracking.edit_text.clone();
6708 let edit_item = tracking.edit_item;
6709 let dialog_ptr = tracking.dialog_ptr;
6710 let popup_draws = tracking.popup_draws.clone();
6711 let game_managed = tracking.game_managed;
6712
6713 if !game_managed {
6714 if self.front_window == dialog_ptr {
6715 self.blit_window_to_screen(bus);
6716 }
6717 self.redraw_standard_dialog_items(
6718 bus,
6719 bounds,
6720 &items,
6721 default_item,
6722 &edit_text,
6723 edit_item,
6724 dialog_ptr,
6725 );
6726 }
6727 self.redraw_dialog_popup_controls(bus, &popup_draws);
6728 let rendered = self.save_dialog_pixels(bus, bounds);
6729
6730 if let Some(tracking) = self.dialog_tracking.as_mut() {
6731 tracking.rendered_pixels = rendered;
6732 tracking.rendered_pixels_final = true;
6733 tracking.draw_procs_done = true;
6734 }
6735 }
6736
6737 pub(crate) fn draw_rect_border(
6739 &self,
6740 bus: &mut MacMemoryBus,
6741 top: i16,
6742 left: i16,
6743 bottom: i16,
6744 right: i16,
6745 ) {
6746 let (screen_base, row_bytes, screen_width, screen_height, pixel_size) =
6747 self.get_screen_params();
6748 Self::fb_hline(
6750 bus,
6751 screen_base,
6752 row_bytes,
6753 pixel_size,
6754 screen_width,
6755 screen_height,
6756 top,
6757 left,
6758 right - 1,
6759 true,
6760 );
6761 Self::fb_hline(
6763 bus,
6764 screen_base,
6765 row_bytes,
6766 pixel_size,
6767 screen_width,
6768 screen_height,
6769 bottom - 1,
6770 left,
6771 right - 1,
6772 true,
6773 );
6774 for y in top..bottom {
6776 Self::fb_set_pixel(
6777 bus,
6778 screen_base,
6779 row_bytes,
6780 pixel_size,
6781 screen_width,
6782 screen_height,
6783 left,
6784 y,
6785 true,
6786 );
6787 }
6788 for y in top..bottom {
6790 Self::fb_set_pixel(
6791 bus,
6792 screen_base,
6793 row_bytes,
6794 pixel_size,
6795 screen_width,
6796 screen_height,
6797 right - 1,
6798 y,
6799 true,
6800 );
6801 }
6802 }
6803
6804 fn fill_dialog_rect(
6805 &self,
6806 bus: &mut MacMemoryBus,
6807 top: i16,
6808 left: i16,
6809 bottom: i16,
6810 right: i16,
6811 black: bool,
6812 ) {
6813 let (screen_base, row_bytes, screen_width, screen_height, pixel_size) =
6814 self.get_screen_params();
6815 Self::fb_fill_rect(
6816 bus,
6817 screen_base,
6818 row_bytes,
6819 pixel_size,
6820 screen_width,
6821 screen_height,
6822 top,
6823 left,
6824 bottom,
6825 right,
6826 black,
6827 );
6828 }
6829
6830 fn draw_classic_dbox_frame(
6831 &self,
6832 bus: &mut MacMemoryBus,
6833 top: i16,
6834 left: i16,
6835 bottom: i16,
6836 right: i16,
6837 ) {
6838 let margin = Self::DBOX_FRAME_MARGIN;
6839 self.fill_dialog_rect(
6844 bus,
6845 top - margin,
6846 left - margin,
6847 bottom + margin,
6848 right + margin,
6849 false,
6850 );
6851
6852 self.fill_dialog_rect(bus, top - 8, left - 8, top - 7, right + 8, true);
6856 self.fill_dialog_rect(bus, top - 8, left - 8, bottom + 8, left - 7, true);
6857 self.fill_dialog_rect(bus, top - 8, right + 6, bottom + 8, right + 8, true);
6858 self.fill_dialog_rect(bus, bottom + 6, left - 8, bottom + 8, right + 8, true);
6859
6860 self.fill_dialog_rect(bus, top - 5, left - 5, top - 4, right + 5, true);
6861 self.fill_dialog_rect(bus, top - 4, left - 5, top - 3, right + 4, true);
6862 self.fill_dialog_rect(bus, top - 5, left - 5, bottom + 5, left - 4, true);
6863 self.fill_dialog_rect(bus, top - 5, left - 4, bottom + 4, left - 3, true);
6864 self.fill_dialog_rect(bus, top - 5, right + 3, bottom + 4, right + 4, true);
6865 self.fill_dialog_rect(bus, bottom + 3, left - 5, bottom + 4, right + 4, true);
6866 }
6867
6868 pub(crate) fn draw_shadow(
6870 &self,
6871 bus: &mut MacMemoryBus,
6872 top: i16,
6873 left: i16,
6874 bottom: i16,
6875 right: i16,
6876 ) {
6877 let (screen_base, row_bytes, screen_width, screen_height, pixel_size) =
6878 self.get_screen_params();
6879 for dx in 0..2i16 {
6881 for y in (top + 2)..=(bottom + dx) {
6882 Self::fb_set_pixel(
6883 bus,
6884 screen_base,
6885 row_bytes,
6886 pixel_size,
6887 screen_width,
6888 screen_height,
6889 right + dx,
6890 y,
6891 true,
6892 );
6893 }
6894 }
6895 for dy in 0..2i16 {
6897 Self::fb_hline(
6898 bus,
6899 screen_base,
6900 row_bytes,
6901 pixel_size,
6902 screen_width,
6903 screen_height,
6904 bottom + dy,
6905 left + 2,
6906 right + 2,
6907 true,
6908 );
6909 }
6910 }
6911
6912 pub(crate) fn draw_popup_control(
6917 &self,
6918 bus: &mut MacMemoryBus,
6919 top: i16,
6920 left: i16,
6921 bottom: i16,
6922 right: i16,
6923 title: &str,
6924 ) {
6925 self.draw_popup_control_with_state(bus, top, left, bottom, right, title, true, false);
6926 }
6927
6928 pub(crate) fn popup_control_box_rect(
6929 &self,
6930 bus: &MacMemoryBus,
6931 top: i16,
6932 left: i16,
6933 bottom: i16,
6934 right: i16,
6935 menu_id: i16,
6936 title_width: i16,
6937 proc_id: i16,
6938 ) -> (i16, i16, i16, i16) {
6939 let (_, _, screen_width, _, _) = self.get_screen_params();
6940 let box_top = top + 1;
6941 let box_left = left + title_width.max(0);
6942 let box_bottom = bottom - 2;
6943 let fixed_width = ((proc_id - 1008) & 0x0001) != 0;
6944 let box_right = if fixed_width {
6945 right - 1
6946 } else {
6947 let text_width = self.popup_menu_max_item_width(bus, menu_id);
6948 let auto_width = (text_width + 40).max(80);
6949 (box_left + auto_width).min(right - 1)
6950 };
6951 (
6952 box_top,
6953 box_left.min(screen_width),
6954 box_bottom,
6955 box_right.min(screen_width),
6956 )
6957 }
6958
6959 fn popup_menu_max_item_width(&self, bus: &MacMemoryBus, menu_id: i16) -> i16 {
6960 let font_id = 0i16;
6961 let font_size = 12i16;
6962 let mut max_width = 0;
6963 for item_no in 1..=255usize {
6964 let Some(title) = self.popup_menu_item_title(bus, menu_id, item_no) else {
6965 break;
6966 };
6967 max_width = max_width.max(Self::fb_measure_string(&title, font_id, font_size));
6968 }
6969 max_width
6970 }
6971
6972 pub(crate) fn draw_popup_control_label(
6973 &self,
6974 bus: &mut MacMemoryBus,
6975 top: i16,
6976 left: i16,
6977 bottom: i16,
6978 popup_left: i16,
6979 title: &str,
6980 enabled: bool,
6981 ) {
6982 if title.is_empty() || popup_left <= left + 4 {
6983 return;
6984 }
6985
6986 let (screen_base, row_bytes, screen_width, screen_height, pixel_size) =
6987 self.get_screen_params();
6988 let font_id = 0i16;
6989 let font_size = 12i16;
6990 let metrics = get_font_metrics(font_id, font_size);
6991 let text_width = Self::fb_measure_string(title, font_id, font_size);
6992 let text_right = popup_left - 6;
6993 let text_x = (text_right - text_width).max(left);
6994 let text_y = top + ((bottom - top) + metrics.ascent - metrics.descent) / 2;
6995
6996 Self::fb_draw_string(
6997 bus,
6998 screen_base,
6999 row_bytes,
7000 pixel_size,
7001 screen_width,
7002 screen_height,
7003 text_x,
7004 text_y,
7005 title,
7006 font_id,
7007 font_size,
7008 );
7009 if !enabled {
7010 self.dim_rect(bus, top, left, bottom, popup_left);
7011 }
7012 }
7013
7014 pub(crate) fn draw_popup_control_with_state(
7015 &self,
7016 bus: &mut MacMemoryBus,
7017 top: i16,
7018 left: i16,
7019 bottom: i16,
7020 right: i16,
7021 title: &str,
7022 enabled: bool,
7023 pressed: bool,
7024 ) {
7025 let (screen_base, row_bytes, screen_width, screen_height, pixel_size) =
7026 self.get_screen_params();
7027
7028 let font_id = 0i16;
7029 let font_size = 12i16;
7030
7031 if !self.draw_theme_control_chrome(
7032 bus,
7033 ControlKind::PopupButton,
7034 top,
7035 left,
7036 bottom,
7037 right,
7038 enabled,
7039 pressed,
7040 false,
7041 false,
7042 ) {
7043 Self::fb_fill_rect(
7046 bus,
7047 screen_base,
7048 row_bytes,
7049 pixel_size,
7050 screen_width,
7051 screen_height,
7052 top,
7053 left,
7054 bottom,
7055 right + 1,
7056 false,
7057 );
7058 if enabled {
7059 Self::fb_hline(
7060 bus,
7061 screen_base,
7062 row_bytes,
7063 pixel_size,
7064 screen_width,
7065 screen_height,
7066 top,
7067 left,
7068 right,
7069 true,
7070 );
7071 Self::fb_hline(
7072 bus,
7073 screen_base,
7074 row_bytes,
7075 pixel_size,
7076 screen_width,
7077 screen_height,
7078 bottom - 2,
7079 left,
7080 right,
7081 true,
7082 );
7083 for y in top..(bottom - 1) {
7084 Self::fb_set_pixel(
7085 bus,
7086 screen_base,
7087 row_bytes,
7088 pixel_size,
7089 screen_width,
7090 screen_height,
7091 left,
7092 y,
7093 true,
7094 );
7095 Self::fb_set_pixel(
7096 bus,
7097 screen_base,
7098 row_bytes,
7099 pixel_size,
7100 screen_width,
7101 screen_height,
7102 right - 1,
7103 y,
7104 true,
7105 );
7106 }
7107 for y in (top + 3)..(bottom - 1) {
7108 Self::fb_set_pixel(
7109 bus,
7110 screen_base,
7111 row_bytes,
7112 pixel_size,
7113 screen_width,
7114 screen_height,
7115 right,
7116 y,
7117 true,
7118 );
7119 }
7120 Self::fb_hline(
7121 bus,
7122 screen_base,
7123 row_bytes,
7124 pixel_size,
7125 screen_width,
7126 screen_height,
7127 bottom - 1,
7128 left + 3,
7129 right + 1,
7130 true,
7131 );
7132 } else {
7133 for y in (top + 3)..(bottom - 2) {
7134 if (y - top) % 2 == 1 {
7135 Self::fb_set_pixel(
7136 bus,
7137 screen_base,
7138 row_bytes,
7139 pixel_size,
7140 screen_width,
7141 screen_height,
7142 right,
7143 y,
7144 true,
7145 );
7146 }
7147 }
7148 for x in (left + 1)..=right {
7149 if (x - left) % 2 == 1 {
7150 Self::fb_set_pixel(
7151 bus,
7152 screen_base,
7153 row_bytes,
7154 pixel_size,
7155 screen_width,
7156 screen_height,
7157 x,
7158 bottom - 2,
7159 true,
7160 );
7161 }
7162 }
7163 }
7164
7165 let tri_x = right - 12;
7168 if enabled {
7169 for row in 0..6i16 {
7170 Self::fb_hline(
7171 bus,
7172 screen_base,
7173 row_bytes,
7174 pixel_size,
7175 screen_width,
7176 screen_height,
7177 top + 6 + row,
7178 tri_x - 5 + row,
7179 tri_x + 6 - row,
7180 true,
7181 );
7182 }
7183 } else {
7184 for row in [0i16, 2, 4] {
7185 let start = tri_x - 4 + row;
7186 let end = tri_x + 5 - row;
7187 for x in start..end {
7188 if (x - start) % 2 == 0 {
7189 Self::fb_set_pixel(
7190 bus,
7191 screen_base,
7192 row_bytes,
7193 pixel_size,
7194 screen_width,
7195 screen_height,
7196 x,
7197 top + 7 + row,
7198 true,
7199 );
7200 }
7201 }
7202 }
7203 }
7204 }
7205
7206 if enabled && !title.is_empty() {
7209 let metrics = get_font_metrics(font_id, font_size);
7210 let text_x = left + 15;
7211 let text_y =
7212 top + (bottom - top - (metrics.ascent + metrics.descent)) / 2 + metrics.ascent - 1;
7213 Self::fb_draw_string_styled(
7214 bus,
7215 screen_base,
7216 row_bytes,
7217 pixel_size,
7218 screen_width,
7219 screen_height,
7220 text_x,
7221 text_y,
7222 title,
7223 font_id,
7224 font_size,
7225 0,
7226 );
7227 }
7228 }
7229
7230 fn redraw_dialog_popup_controls(
7231 &self,
7232 bus: &mut MacMemoryBus,
7233 popup_draws: &[DialogPopupDraw],
7234 ) {
7235 for draw in popup_draws {
7236 let (top, left, bottom, right) = draw.rect;
7237 if draw.enabled && !draw.pressed {
7238 self.draw_popup_control(bus, top, left, bottom, right, &draw.title);
7239 continue;
7240 }
7241 self.draw_popup_control_with_state(
7242 bus,
7243 top,
7244 left,
7245 bottom,
7246 right,
7247 &draw.title,
7248 draw.enabled,
7249 draw.pressed,
7250 );
7251 }
7252 }
7253
7254 fn fb_frame_round_rect(
7258 &self,
7259 bus: &mut MacMemoryBus,
7260 top: i16,
7261 left: i16,
7262 bottom: i16,
7263 right: i16,
7264 oval_width: i16,
7265 oval_height: i16,
7266 pen_size: i16,
7267 ) {
7268 let (screen_base, row_bytes, screen_width, screen_height, pixel_size) =
7269 self.get_screen_params();
7270 let r = Rect {
7271 top,
7272 left,
7273 bottom,
7274 right,
7275 };
7276 let outer_spans = self.compute_rrect_spans(&r, oval_width, oval_height);
7277
7278 let r_inset = Rect {
7279 top: top + pen_size,
7280 left: left + pen_size,
7281 bottom: bottom - pen_size,
7282 right: right - pen_size,
7283 };
7284 let inner_spans = self.compute_rrect_spans(
7285 &r_inset,
7286 (oval_width - 2 * pen_size).max(0),
7287 (oval_height - 2 * pen_size).max(0),
7288 );
7289
7290 for y in top..bottom {
7291 let outer_idx = (y - top) as usize;
7292 if outer_idx >= outer_spans.len() {
7293 continue;
7294 }
7295 let (ol, or) = outer_spans[outer_idx];
7296
7297 let inner_idx = (y - r_inset.top) as usize;
7298 let (il, ir) =
7299 if y >= r_inset.top && y < r_inset.bottom && inner_idx < inner_spans.len() {
7300 inner_spans[inner_idx]
7301 } else {
7302 (or, ol) };
7304
7305 for x in ol..il.min(or) {
7307 Self::fb_set_pixel(
7308 bus,
7309 screen_base,
7310 row_bytes,
7311 pixel_size,
7312 screen_width,
7313 screen_height,
7314 x,
7315 y,
7316 true,
7317 );
7318 }
7319 for x in ir.max(ol)..or {
7321 Self::fb_set_pixel(
7322 bus,
7323 screen_base,
7324 row_bytes,
7325 pixel_size,
7326 screen_width,
7327 screen_height,
7328 x,
7329 y,
7330 true,
7331 );
7332 }
7333 }
7334 }
7335
7336 pub(crate) fn draw_button(
7339 &self,
7340 bus: &mut MacMemoryBus,
7341 top: i16,
7342 left: i16,
7343 bottom: i16,
7344 right: i16,
7345 title: &str,
7346 is_default: bool,
7347 ) {
7348 self.draw_button_state(bus, top, left, bottom, right, title, is_default, true);
7349 }
7350
7351 fn draw_button_with_enabled(
7352 &self,
7353 bus: &mut MacMemoryBus,
7354 top: i16,
7355 left: i16,
7356 bottom: i16,
7357 right: i16,
7358 title: &str,
7359 is_default: bool,
7360 enabled: bool,
7361 ) {
7362 if enabled {
7363 self.draw_button(bus, top, left, bottom, right, title, is_default);
7364 return;
7365 }
7366 self.draw_button_state(bus, top, left, bottom, right, title, is_default, enabled);
7367 }
7368
7369 fn draw_button_state(
7370 &self,
7371 bus: &mut MacMemoryBus,
7372 top: i16,
7373 left: i16,
7374 bottom: i16,
7375 right: i16,
7376 title: &str,
7377 is_default: bool,
7378 enabled: bool,
7379 ) {
7380 if !self.draw_theme_push_button_chrome(
7381 bus, top, left, bottom, right, enabled, false, is_default,
7382 ) {
7383 self.fill_classic_button_shape(bus, top, left, bottom, right);
7384 self.draw_classic_button_outline(bus, top, left, bottom, right);
7385
7386 if is_default {
7390 let hilite_top = top - 4;
7391 let hilite_left = left - 4;
7392 let hilite_bottom = bottom + 4;
7393 let hilite_right = right + 4;
7394 let hilite_height = hilite_bottom - hilite_top;
7395 let oval = (hilite_height / 2 - 4).max(4);
7396 self.fb_frame_round_rect(
7397 bus,
7398 hilite_top,
7399 hilite_left,
7400 hilite_bottom,
7401 hilite_right,
7402 oval,
7403 oval,
7404 3,
7405 );
7406 }
7407 }
7408
7409 self.draw_button_label(bus, top, left, bottom, right, title);
7410 }
7411
7412 fn fill_classic_button_shape(
7413 &self,
7414 bus: &mut MacMemoryBus,
7415 top: i16,
7416 left: i16,
7417 bottom: i16,
7418 right: i16,
7419 ) {
7420 if bottom - top < 8 || right - left < 8 {
7421 let (screen_base, row_bytes, screen_width, screen_height, pixel_size) =
7422 self.get_screen_params();
7423 Self::fb_fill_rect(
7424 bus,
7425 screen_base,
7426 row_bytes,
7427 pixel_size,
7428 screen_width,
7429 screen_height,
7430 top,
7431 left,
7432 bottom,
7433 right,
7434 false,
7435 );
7436 return;
7437 }
7438
7439 if bottom - top >= 24 {
7444 self.fill_dialog_rect(bus, top, left + 4, top + 1, right - 4, false);
7445 self.fill_dialog_rect(bus, top + 1, left + 2, top + 2, right - 2, false);
7446 self.fill_dialog_rect(bus, top + 2, left + 1, top + 4, right - 1, false);
7447 self.fill_dialog_rect(bus, top + 4, left, bottom - 4, right, false);
7448 self.fill_dialog_rect(bus, bottom - 4, left + 1, bottom - 2, right - 1, false);
7449 self.fill_dialog_rect(bus, bottom - 2, left + 2, bottom - 1, right - 2, false);
7450 self.fill_dialog_rect(bus, bottom - 1, left + 4, bottom, right - 4, false);
7451 return;
7452 }
7453
7454 self.fill_dialog_rect(bus, top, left + 3, top + 1, right - 3, false);
7455 self.fill_dialog_rect(bus, top + 1, left + 1, top + 3, right - 1, false);
7456 self.fill_dialog_rect(bus, top + 3, left, bottom - 3, right, false);
7457 self.fill_dialog_rect(bus, bottom - 3, left + 1, bottom - 1, right - 1, false);
7458 self.fill_dialog_rect(bus, bottom - 1, left + 3, bottom, right - 3, false);
7459 }
7460
7461 fn draw_button_label(
7462 &self,
7463 bus: &mut MacMemoryBus,
7464 top: i16,
7465 left: i16,
7466 bottom: i16,
7467 right: i16,
7468 title: &str,
7469 ) {
7470 let (screen_base, row_bytes, screen_width, screen_height, pixel_size) =
7471 self.get_screen_params();
7472 let font_id = 0i16; let font_size = 12i16;
7474 let metrics = get_font_metrics(font_id, font_size);
7475 let text_w = Self::fb_measure_string(title, font_id, font_size);
7476 let text_x = left + (right - left - text_w) / 2;
7477 let text_y = top + (bottom - top - (metrics.ascent + metrics.descent)) / 2 + metrics.ascent;
7478 Self::fb_draw_string(
7479 bus,
7480 screen_base,
7481 row_bytes,
7482 pixel_size,
7483 screen_width,
7484 screen_height,
7485 text_x,
7486 text_y,
7487 title,
7488 font_id,
7489 font_size,
7490 );
7491 }
7492
7493 fn draw_classic_button_outline(
7494 &self,
7495 bus: &mut MacMemoryBus,
7496 top: i16,
7497 left: i16,
7498 bottom: i16,
7499 right: i16,
7500 ) {
7501 if bottom - top < 8 || right - left < 8 {
7502 self.draw_rect_border(bus, top, left, bottom, right);
7503 return;
7504 }
7505
7506 if bottom - top >= 24 {
7511 self.fill_dialog_rect(bus, top, left + 4, top + 1, right - 4, true);
7512 self.fill_dialog_rect(bus, top + 1, left + 2, top + 2, left + 4, true);
7513 self.fill_dialog_rect(bus, top + 1, right - 4, top + 2, right - 2, true);
7514 self.fill_dialog_rect(bus, top + 2, left + 1, top + 3, left + 2, true);
7515 self.fill_dialog_rect(bus, top + 2, right - 2, top + 3, right - 1, true);
7516 self.fill_dialog_rect(bus, top + 3, left + 1, top + 4, left + 2, true);
7517 self.fill_dialog_rect(bus, top + 3, right - 2, top + 4, right - 1, true);
7518 self.fill_dialog_rect(bus, top + 4, left, bottom - 4, left + 1, true);
7519 self.fill_dialog_rect(bus, top + 4, right - 1, bottom - 4, right, true);
7520 self.fill_dialog_rect(bus, bottom - 4, left + 1, bottom - 3, left + 2, true);
7521 self.fill_dialog_rect(bus, bottom - 4, right - 2, bottom - 3, right - 1, true);
7522 self.fill_dialog_rect(bus, bottom - 3, left + 1, bottom - 2, left + 2, true);
7523 self.fill_dialog_rect(bus, bottom - 3, right - 2, bottom - 2, right - 1, true);
7524 self.fill_dialog_rect(bus, bottom - 2, left + 2, bottom - 1, left + 4, true);
7525 self.fill_dialog_rect(bus, bottom - 2, right - 4, bottom - 1, right - 2, true);
7526 self.fill_dialog_rect(bus, bottom - 1, left + 4, bottom, right - 4, true);
7527 return;
7528 }
7529
7530 self.fill_dialog_rect(bus, top, left + 3, top + 1, right - 3, true);
7531 self.fill_dialog_rect(bus, top + 1, left + 1, top + 2, left + 3, true);
7532 self.fill_dialog_rect(bus, top + 1, right - 3, top + 2, right - 1, true);
7533 self.fill_dialog_rect(bus, top + 2, left + 1, top + 3, left + 2, true);
7534 self.fill_dialog_rect(bus, top + 2, right - 2, top + 3, right - 1, true);
7535 self.fill_dialog_rect(bus, top + 3, left, bottom - 3, left + 1, true);
7536 self.fill_dialog_rect(bus, top + 3, right - 1, bottom - 3, right, true);
7537 self.fill_dialog_rect(bus, bottom - 3, left + 1, bottom - 2, left + 2, true);
7538 self.fill_dialog_rect(bus, bottom - 3, right - 2, bottom - 2, right - 1, true);
7539 self.fill_dialog_rect(bus, bottom - 2, left + 1, bottom - 1, left + 3, true);
7540 self.fill_dialog_rect(bus, bottom - 2, right - 3, bottom - 1, right - 1, true);
7541 self.fill_dialog_rect(bus, bottom - 1, left + 3, bottom, right - 3, true);
7542 }
7543
7544 fn draw_dialog_button_highlight_state(
7545 &self,
7546 bus: &mut MacMemoryBus,
7547 rect: (i16, i16, i16, i16),
7548 title: &str,
7549 is_default: bool,
7550 highlighted: bool,
7551 ) {
7552 let (top, left, bottom, right) = rect;
7553 if self.draw_theme_push_button_chrome(
7554 bus,
7555 top,
7556 left,
7557 bottom,
7558 right,
7559 true,
7560 highlighted,
7561 is_default,
7562 ) {
7563 self.draw_button_label(bus, top, left, bottom, right, title);
7564 return;
7565 }
7566 self.invert_button_rect(bus, top, left, bottom, right);
7567 }
7568
7569 pub(crate) fn draw_checkbox(
7571 &self,
7572 bus: &mut MacMemoryBus,
7573 top: i16,
7574 left: i16,
7575 bottom: i16,
7576 _right: i16,
7577 title: &str,
7578 checked: bool,
7579 ) {
7580 self.draw_checkbox_state(bus, top, left, bottom, _right, title, checked, true, false);
7581 }
7582
7583 fn draw_checkbox_with_enabled(
7584 &self,
7585 bus: &mut MacMemoryBus,
7586 top: i16,
7587 left: i16,
7588 bottom: i16,
7589 _right: i16,
7590 title: &str,
7591 checked: bool,
7592 enabled: bool,
7593 ) {
7594 self.draw_checkbox_with_enabled_and_inactive(
7595 bus, top, left, bottom, _right, title, checked, enabled, false,
7596 );
7597 }
7598
7599 fn draw_checkbox_with_enabled_and_inactive(
7600 &self,
7601 bus: &mut MacMemoryBus,
7602 top: i16,
7603 left: i16,
7604 bottom: i16,
7605 _right: i16,
7606 title: &str,
7607 checked: bool,
7608 enabled: bool,
7609 inactive: bool,
7610 ) {
7611 if enabled && !inactive {
7612 self.draw_checkbox(bus, top, left, bottom, _right, title, checked);
7613 return;
7614 }
7615 self.draw_checkbox_state(
7616 bus, top, left, bottom, _right, title, checked, enabled, inactive,
7617 );
7618 }
7619
7620 fn draw_checkbox_state(
7621 &self,
7622 bus: &mut MacMemoryBus,
7623 top: i16,
7624 left: i16,
7625 bottom: i16,
7626 _right: i16,
7627 title: &str,
7628 checked: bool,
7629 enabled: bool,
7630 inactive: bool,
7631 ) {
7632 let (screen_base, row_bytes, screen_width, screen_height, pixel_size) =
7633 self.get_screen_params();
7634 let box_size = Self::STANDARD_CONTROL_MARK_SIZE;
7635 let height = bottom - top;
7639 let box_top = top + (height - box_size) / 2;
7640 let box_left = left + Self::STANDARD_CONTROL_MARK_LEFT_INSET;
7641 if !self.draw_theme_control_chrome(
7642 bus,
7643 ControlKind::Checkbox,
7644 box_top,
7645 box_left,
7646 box_top + box_size,
7647 box_left + box_size,
7648 enabled && !inactive,
7649 false,
7650 checked,
7651 false,
7652 ) {
7653 Self::fb_fill_rect(
7655 bus,
7656 screen_base,
7657 row_bytes,
7658 pixel_size,
7659 screen_width,
7660 screen_height,
7661 box_top,
7662 box_left,
7663 box_top + box_size,
7664 box_left + box_size,
7665 false,
7666 );
7667 self.draw_rect_border(
7668 bus,
7669 box_top,
7670 box_left,
7671 box_top + box_size,
7672 box_left + box_size,
7673 );
7674 if checked {
7675 for i in 1..box_size - 1 {
7677 Self::fb_set_pixel(
7678 bus,
7679 screen_base,
7680 row_bytes,
7681 pixel_size,
7682 screen_width,
7683 screen_height,
7684 box_left + i,
7685 box_top + i,
7686 true,
7687 );
7688 Self::fb_set_pixel(
7689 bus,
7690 screen_base,
7691 row_bytes,
7692 pixel_size,
7693 screen_width,
7694 screen_height,
7695 box_left + box_size - 1 - i,
7696 box_top + i,
7697 true,
7698 );
7699 }
7700 }
7701 }
7702 let font_id = 0i16;
7706 let font_size = 12i16;
7707 let metrics = get_font_metrics(font_id, font_size);
7708 let text_x = box_left + box_size + Self::STANDARD_CONTROL_TITLE_GAP;
7709 let text_y = top + (height + metrics.ascent - metrics.descent) / 2;
7710 let label_left = box_left + box_size + 1;
7711 let dim_disabled_title = !enabled && self.ui_theme_id() != UiThemeId::ClassicSystem7;
7712 self.draw_standard_control_title_text(
7713 bus,
7714 top,
7715 label_left,
7716 bottom,
7717 _right,
7718 text_x,
7719 text_y,
7720 title,
7721 font_id,
7722 font_size,
7723 inactive,
7724 dim_disabled_title,
7725 );
7726 }
7727
7728 pub(crate) fn draw_radio(
7730 &self,
7731 bus: &mut MacMemoryBus,
7732 top: i16,
7733 left: i16,
7734 bottom: i16,
7735 _right: i16,
7736 title: &str,
7737 selected: bool,
7738 ) {
7739 self.draw_radio_state(bus, top, left, bottom, _right, title, selected, true, false);
7740 }
7741
7742 fn draw_radio_with_enabled(
7743 &self,
7744 bus: &mut MacMemoryBus,
7745 top: i16,
7746 left: i16,
7747 bottom: i16,
7748 _right: i16,
7749 title: &str,
7750 selected: bool,
7751 enabled: bool,
7752 ) {
7753 self.draw_radio_with_enabled_and_inactive(
7754 bus, top, left, bottom, _right, title, selected, enabled, false,
7755 );
7756 }
7757
7758 fn draw_radio_with_enabled_and_inactive(
7759 &self,
7760 bus: &mut MacMemoryBus,
7761 top: i16,
7762 left: i16,
7763 bottom: i16,
7764 _right: i16,
7765 title: &str,
7766 selected: bool,
7767 enabled: bool,
7768 inactive: bool,
7769 ) {
7770 if enabled && !inactive {
7771 self.draw_radio(bus, top, left, bottom, _right, title, selected);
7772 return;
7773 }
7774 self.draw_radio_state(
7775 bus, top, left, bottom, _right, title, selected, enabled, inactive,
7776 );
7777 }
7778
7779 fn draw_radio_state(
7780 &self,
7781 bus: &mut MacMemoryBus,
7782 top: i16,
7783 left: i16,
7784 bottom: i16,
7785 _right: i16,
7786 title: &str,
7787 selected: bool,
7788 enabled: bool,
7789 inactive: bool,
7790 ) {
7791 let (screen_base, row_bytes, screen_width, screen_height, pixel_size) =
7792 self.get_screen_params();
7793 let mark_size = Self::STANDARD_CONTROL_MARK_SIZE;
7794 let height = bottom - top;
7795 let mark_top = top + (height - mark_size) / 2;
7796 let mark_left = left + Self::STANDARD_CONTROL_MARK_LEFT_INSET;
7797 if !self.draw_theme_control_chrome(
7798 bus,
7799 ControlKind::RadioButton,
7800 mark_top,
7801 mark_left,
7802 mark_top + mark_size,
7803 mark_left + mark_size,
7804 enabled && !inactive,
7805 false,
7806 selected,
7807 false,
7808 ) {
7809 Self::fb_fill_rect(
7810 bus,
7811 screen_base,
7812 row_bytes,
7813 pixel_size,
7814 screen_width,
7815 screen_height,
7816 mark_top,
7817 mark_left,
7818 mark_top + mark_size,
7819 mark_left + mark_size,
7820 false,
7821 );
7822 self.draw_control_mask_12(
7825 bus,
7826 screen_base,
7827 row_bytes,
7828 pixel_size,
7829 screen_width,
7830 screen_height,
7831 mark_left,
7832 mark_top,
7833 &Self::CLASSIC_RADIO_OUTLINE_12,
7834 );
7835 if selected {
7836 self.draw_control_mask_12(
7837 bus,
7838 screen_base,
7839 row_bytes,
7840 pixel_size,
7841 screen_width,
7842 screen_height,
7843 mark_left,
7844 mark_top,
7845 &Self::CLASSIC_RADIO_DOT_12,
7846 );
7847 }
7848 }
7849 let font_id = 0i16;
7853 let font_size = 12i16;
7854 let metrics = get_font_metrics(font_id, font_size);
7855 let text_x = mark_left + mark_size + Self::STANDARD_CONTROL_TITLE_GAP;
7856 let text_y = top + (height + metrics.ascent - metrics.descent) / 2;
7857 let label_left = mark_left + mark_size + 1;
7858 let dim_disabled_title = !enabled && self.ui_theme_id() != UiThemeId::ClassicSystem7;
7859 self.draw_standard_control_title_text(
7860 bus,
7861 top,
7862 label_left,
7863 bottom,
7864 _right,
7865 text_x,
7866 text_y,
7867 title,
7868 font_id,
7869 font_size,
7870 inactive,
7871 dim_disabled_title,
7872 );
7873 }
7874
7875 fn draw_standard_control_title_text(
7876 &self,
7877 bus: &mut MacMemoryBus,
7878 label_top: i16,
7879 label_left: i16,
7880 label_bottom: i16,
7881 label_right: i16,
7882 text_x: i16,
7883 text_y: i16,
7884 title: &str,
7885 font_id: i16,
7886 font_size: i16,
7887 inactive: bool,
7888 dim_disabled_title: bool,
7889 ) {
7890 let (screen_base, row_bytes, screen_width, screen_height, pixel_size) =
7891 self.get_screen_params();
7892 if inactive && pixel_size == 8 {
7893 let [r, g, b] = Self::INACTIVE_STANDARD_CONTROL_TITLE_RGB;
7898 let gray_index = super::pict::closest_clut_index(r, g, b, &self.device_clut);
7899 Self::fb_draw_string_styled_index(
7900 bus,
7901 screen_base,
7902 row_bytes,
7903 pixel_size,
7904 screen_width,
7905 screen_height,
7906 text_x,
7907 text_y,
7908 title,
7909 font_id,
7910 font_size,
7911 0,
7912 gray_index,
7913 );
7914 return;
7915 }
7916
7917 Self::fb_draw_string(
7918 bus,
7919 screen_base,
7920 row_bytes,
7921 pixel_size,
7922 screen_width,
7923 screen_height,
7924 text_x,
7925 text_y,
7926 title,
7927 font_id,
7928 font_size,
7929 );
7930 if inactive || dim_disabled_title {
7931 self.dim_rect(bus, label_top, label_left, label_bottom, label_right);
7932 }
7933 }
7934
7935 fn draw_control_mask_12(
7936 &self,
7937 bus: &mut MacMemoryBus,
7938 screen_base: u32,
7939 row_bytes: u32,
7940 pixel_size: u16,
7941 screen_width: i16,
7942 screen_height: i16,
7943 left: i16,
7944 top: i16,
7945 mask: &[&str; 12],
7946 ) {
7947 for (dy, row) in mask.iter().enumerate() {
7948 for (dx, pixel) in row.as_bytes().iter().enumerate() {
7949 if *pixel == b'#' {
7950 Self::fb_set_pixel(
7951 bus,
7952 screen_base,
7953 row_bytes,
7954 pixel_size,
7955 screen_width,
7956 screen_height,
7957 left + dx as i16,
7958 top + dy as i16,
7959 true,
7960 );
7961 }
7962 }
7963 }
7964 }
7965
7966 pub(crate) fn apply_param_text<'a>(&self, text: &'a str) -> std::borrow::Cow<'a, str> {
7973 if !text.contains('^') {
7974 return std::borrow::Cow::Borrowed(text);
7975 }
7976 let mut out = String::with_capacity(text.len());
7977 let mut chars = text.chars().peekable();
7978 while let Some(ch) = chars.next() {
7979 if ch == '^' {
7980 if let Some(&next) = chars.peek() {
7981 if let Some(idx) = next.to_digit(10) {
7982 if (idx as usize) < self.param_text.len() {
7983 chars.next();
7984 out.push_str(&decode_mac_roman_for_render(
7985 &self.param_text[idx as usize],
7986 ));
7987 continue;
7988 }
7989 }
7990 }
7991 }
7992 out.push(ch);
7993 }
7994 std::borrow::Cow::Owned(out)
7995 }
7996
7997 fn draw_static_text(
7998 &self,
7999 bus: &mut MacMemoryBus,
8000 top: i16,
8001 left: i16,
8002 bottom: i16,
8003 right: i16,
8004 text: &str,
8005 ) {
8006 let (screen_base, row_bytes, screen_width, screen_height, pixel_size) =
8007 self.get_screen_params();
8008 let font_id = self.tx_font;
8011 let font_size = Self::font_lookup_size(self.tx_size);
8012 let metrics = get_font_metrics(font_id, font_size);
8013 let line_height = metrics.ascent + metrics.descent + metrics.leading;
8014 let max_width = (right - left).saturating_sub(Self::TE_LINE_LEFT_INSET);
8015 let mut y = top + metrics.ascent;
8016 if y >= bottom && bottom > top {
8017 y = bottom - 1;
8018 }
8019
8020 let substituted = self.apply_param_text(text);
8021 let text_bytes = substituted.as_bytes();
8022 let lines = self.te_wrap_lines(font_id, self.tx_size, text_bytes, max_width);
8023
8024 for (start, end) in lines {
8025 let mut trimmed_end = end;
8026 while trimmed_end > start && matches!(text_bytes[trimmed_end - 1], b' ' | b'\r' | b'\n')
8027 {
8028 trimmed_end -= 1;
8029 }
8030 if trimmed_end > start && y <= bottom {
8031 let line: String = text_bytes[start..trimmed_end]
8035 .iter()
8036 .map(|&byte| byte as char)
8037 .collect();
8038 Self::fb_draw_string(
8039 bus,
8040 screen_base,
8041 row_bytes,
8042 pixel_size,
8043 screen_width,
8044 screen_height,
8045 left + Self::TE_LINE_LEFT_INSET,
8046 y,
8047 &line,
8048 font_id,
8049 font_size,
8050 );
8051 }
8052 y += line_height;
8053 if y > bottom {
8054 break;
8055 }
8056 }
8057 }
8058
8059 pub(crate) fn draw_edit_text(
8061 &self,
8062 bus: &mut MacMemoryBus,
8063 top: i16,
8064 left: i16,
8065 bottom: i16,
8066 right: i16,
8067 text: &str,
8068 selected: bool,
8069 ) {
8070 let selection_range = selected.then_some((0, text.len()));
8071 self.draw_edit_text_with_cursor(
8072 bus,
8073 top,
8074 left,
8075 bottom,
8076 right,
8077 text,
8078 selection_range,
8079 !selected,
8080 true,
8081 );
8082 }
8083
8084 fn draw_edit_text_with_cursor(
8085 &self,
8086 bus: &mut MacMemoryBus,
8087 top: i16,
8088 left: i16,
8089 bottom: i16,
8090 right: i16,
8091 text: &str,
8092 selection_range: Option<(usize, usize)>,
8093 show_cursor: bool,
8094 enabled: bool,
8095 ) {
8096 let (screen_base, row_bytes, screen_width, screen_height, pixel_size) =
8097 self.get_screen_params();
8098 let frame_top = top - Self::EDIT_TEXT_FRAME_OUTSET;
8103 let frame_left = left - Self::EDIT_TEXT_FRAME_OUTSET;
8104 let frame_bottom = bottom + Self::EDIT_TEXT_FRAME_OUTSET;
8105 let frame_right = right + Self::EDIT_TEXT_FRAME_OUTSET;
8106
8107 if !self.draw_theme_text_field(
8108 bus,
8109 frame_top,
8110 frame_left,
8111 frame_bottom,
8112 frame_right,
8113 enabled,
8114 selection_range.is_some() || show_cursor,
8115 ) {
8116 Self::fb_fill_rect(
8118 bus,
8119 screen_base,
8120 row_bytes,
8121 pixel_size,
8122 screen_width,
8123 screen_height,
8124 frame_top,
8125 frame_left,
8126 frame_bottom,
8127 frame_right,
8128 false,
8129 );
8130 self.draw_rect_border(bus, frame_top, frame_left, frame_bottom, frame_right);
8134 }
8135 let font_id = self.tx_font;
8136 let font_size = Self::font_lookup_size(self.tx_size);
8137 let metrics = get_font_metrics(font_id, font_size);
8138 let text_y = top + metrics.ascent;
8139 Self::fb_draw_string(
8140 bus,
8141 screen_base,
8142 row_bytes,
8143 pixel_size,
8144 screen_width,
8145 screen_height,
8146 left + 1,
8147 text_y,
8148 text,
8149 font_id,
8150 font_size,
8151 );
8152 if let Some((selection_start, selection_end)) = selection_range {
8153 if !text.is_empty() {
8159 let text_bytes = text.as_bytes();
8160 let start = selection_start.min(text_bytes.len());
8161 let end = selection_end.min(text_bytes.len());
8162 if start < end {
8163 let line_height = metrics.ascent + metrics.descent + metrics.leading;
8164 let selection_top = top;
8165 let selection_bottom = top.saturating_add(line_height).min(bottom);
8166 let selection_left = if start == 0 {
8167 left
8168 } else {
8169 left + Self::TE_LINE_LEFT_INSET
8170 + self.te_measure_text_width(
8171 font_id,
8172 self.tx_size,
8173 text_bytes,
8174 0,
8175 start,
8176 )
8177 };
8178 let selection_right = if end >= text_bytes.len() {
8179 right
8180 } else {
8181 left + Self::TE_LINE_LEFT_INSET
8182 + self.te_measure_text_width(font_id, self.tx_size, text_bytes, 0, end)
8183 };
8184 if selection_left < selection_right && selection_top < selection_bottom {
8185 if self.ui_theme_id() == UiThemeId::ClassicSystem7 {
8186 self.invert_rect_exact(
8187 bus,
8188 selection_top,
8189 selection_left,
8190 selection_bottom,
8191 selection_right,
8192 );
8193 } else {
8194 self.draw_theme_text_selection(
8195 bus,
8196 selection_top,
8197 selection_left,
8198 selection_bottom,
8199 selection_right,
8200 true,
8201 );
8202 }
8203 }
8204 }
8205 }
8206 } else if show_cursor {
8207 let text_width = Self::fb_measure_string(text, font_id, font_size);
8209 let cursor_x = left + 3 + text_width;
8210 if cursor_x < right - 1
8211 && !self.draw_theme_caret(bus, top + 2, cursor_x, bottom - 1, cursor_x + 1)
8212 {
8213 for y in (top + 2)..=(bottom - 2) {
8214 Self::fb_set_pixel(
8215 bus,
8216 screen_base,
8217 row_bytes,
8218 pixel_size,
8219 screen_width,
8220 screen_height,
8221 cursor_x,
8222 y,
8223 true,
8224 );
8225 }
8226 }
8227 }
8228 }
8229
8230 fn dialog_cicn_layout(bus: &MacMemoryBus, icon_ptr: u32) -> Option<DialogCIconLayout> {
8231 if bus.get_alloc_size(icon_ptr).is_some_and(|size| size < 82) {
8232 return None;
8233 }
8234
8235 let pm_row_bytes = (bus.read_word(icon_ptr + 4) & 0x3FFF) as u32;
8240 let pm_top = bus.read_word(icon_ptr + 6) as i16;
8241 let pm_left = bus.read_word(icon_ptr + 8) as i16;
8242 let pm_bottom = bus.read_word(icon_ptr + 10) as i16;
8243 let pm_right = bus.read_word(icon_ptr + 12) as i16;
8244 let pixel_size = bus.read_word(icon_ptr + 32);
8245 let mask_row_bytes = (bus.read_word(icon_ptr + 54) & 0x3FFF) as u32;
8246 let bmap_row_bytes = (bus.read_word(icon_ptr + 68) & 0x3FFF) as u32;
8247
8248 let width = pm_right - pm_left;
8249 let height = pm_bottom - pm_top;
8250 if width <= 0 || height <= 0 || pm_row_bytes == 0 || mask_row_bytes == 0 {
8251 return None;
8252 }
8253
8254 let height_u32 = height as u32;
8255 let mask_data_size = mask_row_bytes.checked_mul(height_u32)?;
8256 let bmap_data_size = bmap_row_bytes.checked_mul(height_u32)?;
8257 let mask_data_ptr = icon_ptr + 82;
8258 let bmap_data_ptr = mask_data_ptr.checked_add(mask_data_size)?;
8259 let ctab_ptr = bmap_data_ptr.checked_add(bmap_data_size)?;
8260
8261 if let Some(resource_size) = bus.get_alloc_size(icon_ptr) {
8262 let resource_end = icon_ptr.checked_add(resource_size)?;
8263 if ctab_ptr.checked_add(8)? > resource_end {
8264 return None;
8265 }
8266 }
8267
8268 let ct_size = bus.read_word(ctab_ptr + 6) as u32;
8269 let ctab_total_bytes = 8u32.checked_add((ct_size + 1).checked_mul(8)?)?;
8270 let pixel_data_ptr = ctab_ptr.checked_add(ctab_total_bytes)?;
8271
8272 if let Some(resource_size) = bus.get_alloc_size(icon_ptr) {
8273 let resource_end = icon_ptr.checked_add(resource_size)?;
8274 let pixel_data_size = pm_row_bytes.checked_mul(height_u32)?;
8275 if pixel_data_ptr.checked_add(pixel_data_size)? > resource_end {
8276 return None;
8277 }
8278 }
8279
8280 Some(DialogCIconLayout {
8281 width,
8282 height,
8283 pm_row_bytes,
8284 mask_row_bytes,
8285 bmap_row_bytes,
8286 pixel_size,
8287 mask_data_ptr,
8288 bmap_data_ptr,
8289 pixel_data_ptr,
8290 })
8291 }
8292
8293 fn dialog_cicn_pixel_index(
8294 bus: &MacMemoryBus,
8295 data_ptr: u32,
8296 row_bytes: u32,
8297 pixel_size: u16,
8298 x: u32,
8299 y: u32,
8300 ) -> Option<u8> {
8301 let row_ptr = data_ptr.checked_add(y.checked_mul(row_bytes)?)?;
8302 match pixel_size {
8303 1 => {
8304 let byte = bus.read_byte(row_ptr.checked_add(x / 8)?);
8305 Some(((byte >> (7 - (x % 8))) & 1) as u8)
8306 }
8307 2 => {
8308 let byte = bus.read_byte(row_ptr.checked_add(x / 4)?);
8309 Some((byte >> (6 - 2 * (x % 4))) & 0x03)
8310 }
8311 4 => {
8312 let byte = bus.read_byte(row_ptr.checked_add(x / 2)?);
8313 Some(if x % 2 == 0 {
8314 (byte >> 4) & 0x0F
8315 } else {
8316 byte & 0x0F
8317 })
8318 }
8319 8 => Some(bus.read_byte(row_ptr.checked_add(x)?)),
8320 size if size > 8 && size % 8 == 0 => {
8321 let bytes_per_pixel = u32::from(size / 8);
8322 Some(bus.read_byte(row_ptr.checked_add(x.checked_mul(bytes_per_pixel)?)?))
8323 }
8324 _ => None,
8325 }
8326 }
8327
8328 fn draw_cicn_icon(
8329 &self,
8330 bus: &mut MacMemoryBus,
8331 top: i16,
8332 left: i16,
8333 bottom: i16,
8334 right: i16,
8335 icon_ptr: u32,
8336 ) -> bool {
8337 let Some(layout) = Self::dialog_cicn_layout(bus, icon_ptr) else {
8338 return false;
8339 };
8340 let (screen_base, row_bytes, screen_width, screen_height, screen_pixel_size) =
8341 self.get_screen_params();
8342 let dst_w = right - left;
8343 let dst_h = bottom - top;
8344 if dst_w <= 0 || dst_h <= 0 {
8345 return false;
8346 }
8347
8348 for dy in 0..dst_h {
8349 let sy = (dy as i32 * i32::from(layout.height) / i32::from(dst_h)) as u32;
8350 let dst_y = top + dy;
8351 for dx in 0..dst_w {
8352 let sx = (dx as i32 * i32::from(layout.width) / i32::from(dst_w)) as u32;
8353 let dst_x = left + dx;
8354 let mask_byte =
8355 bus.read_byte(layout.mask_data_ptr + sy * layout.mask_row_bytes + sx / 8);
8356 if (mask_byte & (0x80 >> (sx % 8))) == 0 {
8357 continue;
8358 }
8359
8360 if screen_pixel_size == 8 && layout.pixel_size >= 2 {
8361 let Some(pixel_index) = Self::dialog_cicn_pixel_index(
8362 bus,
8363 layout.pixel_data_ptr,
8364 layout.pm_row_bytes,
8365 layout.pixel_size,
8366 sx,
8367 sy,
8368 ) else {
8369 continue;
8370 };
8371 if dst_x >= 0 && dst_y >= 0 && dst_x < screen_width && dst_y < screen_height {
8372 bus.write_byte(
8373 screen_base + (dst_y as u32) * row_bytes + dst_x as u32,
8374 pixel_index,
8375 );
8376 }
8377 continue;
8378 }
8379
8380 let source_data_ptr = if layout.bmap_row_bytes != 0 {
8381 layout.bmap_data_ptr
8382 } else {
8383 layout.pixel_data_ptr
8384 };
8385 let source_row_bytes = if layout.bmap_row_bytes != 0 {
8386 layout.bmap_row_bytes
8387 } else {
8388 layout.pm_row_bytes
8389 };
8390 let source_pixel_size = if layout.bmap_row_bytes != 0 {
8391 1
8392 } else {
8393 layout.pixel_size
8394 };
8395 let Some(pixel_index) = Self::dialog_cicn_pixel_index(
8396 bus,
8397 source_data_ptr,
8398 source_row_bytes,
8399 source_pixel_size,
8400 sx,
8401 sy,
8402 ) else {
8403 continue;
8404 };
8405 Self::fb_set_pixel(
8406 bus,
8407 screen_base,
8408 row_bytes,
8409 screen_pixel_size,
8410 screen_width,
8411 screen_height,
8412 dst_x,
8413 dst_y,
8414 pixel_index != 0,
8415 );
8416 }
8417 }
8418 true
8419 }
8420
8421 fn draw_icon(
8424 &self,
8425 bus: &mut MacMemoryBus,
8426 top: i16,
8427 left: i16,
8428 bottom: i16,
8429 right: i16,
8430 icon_ptr: u32,
8431 ) {
8432 let (screen_base, row_bytes, screen_width, screen_height, pixel_size) =
8433 self.get_screen_params();
8434 let dst_w = right - left;
8435 let dst_h = bottom - top;
8436 for row in 0..32i16 {
8438 let row_data = bus.read_long(icon_ptr + (row as u32) * 4);
8439 for col in 0..32i16 {
8440 let bit = (row_data >> (31 - col)) & 1;
8441 if bit != 0 {
8442 let dx = left + col * dst_w / 32;
8444 let dy = top + row * dst_h / 32;
8445 Self::fb_set_pixel(
8446 bus,
8447 screen_base,
8448 row_bytes,
8449 pixel_size,
8450 screen_width,
8451 screen_height,
8452 dx,
8453 dy,
8454 true,
8455 );
8456 }
8457 }
8458 }
8459 }
8460
8461 pub(crate) fn invert_button_rect(
8463 &self,
8464 bus: &mut MacMemoryBus,
8465 top: i16,
8466 left: i16,
8467 bottom: i16,
8468 right: i16,
8469 ) {
8470 let (screen_base, row_bytes, screen_width, screen_height, pixel_size) =
8471 self.get_screen_params();
8472
8473 let y_start = (top as i32 + 1).clamp(0, screen_height as i32);
8474 let y_end = (bottom as i32 - 1).clamp(0, screen_height as i32);
8475 let x_start = (left as i32 + 1).clamp(0, screen_width as i32);
8476 let x_end = (right as i32 - 1).clamp(0, screen_width as i32);
8477 if y_start >= y_end || x_start >= x_end {
8478 return;
8479 }
8480
8481 for y in y_start..y_end {
8482 for x in x_start..x_end {
8483 if pixel_size == 8 {
8484 let addr = screen_base + (y as u32) * row_bytes + (x as u32);
8485 let b = bus.read_byte(addr);
8486 bus.write_byte(addr, 255 - b);
8487 } else {
8488 let byte_offset = (y as u32) * row_bytes + (x as u32 / 8);
8489 let bit = 7 - (x as u32 % 8);
8490 let addr = screen_base + byte_offset;
8491 let b = bus.read_byte(addr);
8492 bus.write_byte(addr, b ^ (1 << bit));
8493 }
8494 }
8495 }
8496 }
8497
8498 fn invert_rect_exact(
8499 &self,
8500 bus: &mut MacMemoryBus,
8501 top: i16,
8502 left: i16,
8503 bottom: i16,
8504 right: i16,
8505 ) {
8506 let (screen_base, row_bytes, screen_width, screen_height, pixel_size) =
8507 self.get_screen_params();
8508
8509 let y_start = (top as i32).clamp(0, screen_height as i32);
8510 let y_end = (bottom as i32).clamp(0, screen_height as i32);
8511 let x_start = (left as i32).clamp(0, screen_width as i32);
8512 let x_end = (right as i32).clamp(0, screen_width as i32);
8513 if y_start >= y_end || x_start >= x_end {
8514 return;
8515 }
8516
8517 for y in y_start..y_end {
8518 for x in x_start..x_end {
8519 if pixel_size == 8 {
8520 let addr = screen_base + (y as u32) * row_bytes + (x as u32);
8521 let b = bus.read_byte(addr);
8522 bus.write_byte(addr, 255 - b);
8523 } else {
8524 let byte_offset = (y as u32) * row_bytes + (x as u32 / 8);
8525 let bit = 7 - (x as u32 % 8);
8526 let addr = screen_base + byte_offset;
8527 let b = bus.read_byte(addr);
8528 bus.write_byte(addr, b ^ (1 << bit));
8529 }
8530 }
8531 }
8532 }
8533
8534 fn dialog_item_hit_test(
8536 &self,
8537 bus: &MacMemoryBus,
8538 items: &[DialogItem],
8539 bounds: (i16, i16, i16, i16),
8540 screen_v: i16,
8541 screen_h: i16,
8542 popup_original_rects: &std::collections::HashMap<(u32, i16), (i16, i16, i16, i16)>,
8543 dialog_ptr: u32,
8544 ) -> i16 {
8545 let (top, left, _, _) = bounds;
8546 let local_v = screen_v - top;
8547 let local_h = screen_h - left;
8548 for (i, item) in items.iter().enumerate() {
8549 let item_no = (i + 1) as i16;
8550 let mut rect = popup_original_rects
8553 .get(&(dialog_ptr, item_no))
8554 .copied()
8555 .unwrap_or(item.rect);
8556 let base_type = item.item_type & 0x7F;
8557 if (4..=7).contains(&base_type) {
8558 if let Some(ctrl_handle) = self.dialog_control_handle_for_item(dialog_ptr, item_no)
8559 {
8560 let ctrl_ptr = bus.read_long(ctrl_handle);
8561 if ctrl_ptr != 0 && bus.read_byte(ctrl_ptr + 16) != 0 {
8562 rect = (
8563 bus.read_word(ctrl_ptr + 8) as i16,
8564 bus.read_word(ctrl_ptr + 10) as i16,
8565 bus.read_word(ctrl_ptr + 12) as i16,
8566 bus.read_word(ctrl_ptr + 14) as i16,
8567 );
8568 }
8569 }
8570 }
8571 let (it, il, ib, ir) = rect;
8572 if local_v >= it && local_v < ib && local_h >= il && local_h < ir {
8573 return item_no;
8574 }
8575 }
8576 0
8577 }
8578
8579 fn dialog_button_hit_test(
8580 items: &[DialogItem],
8581 bounds: (i16, i16, i16, i16),
8582 screen_v: i16,
8583 screen_h: i16,
8584 ) -> i16 {
8585 for (i, item) in items.iter().enumerate() {
8586 let base_type = item.item_type & 0x7F;
8587 let is_disabled = (item.item_type & 0x80) != 0;
8588 if base_type != 4 || is_disabled {
8589 continue;
8590 }
8591 let (top, left, bottom, right) = Self::dialog_item_screen_rect(bounds, item.rect);
8592 if screen_v >= top && screen_v < bottom && screen_h >= left && screen_h < right {
8593 return (i + 1) as i16;
8594 }
8595 }
8596 0
8597 }
8598
8599 fn is_plain_modal_user_item(
8600 &self,
8601 tracking: &DialogTrackingState,
8602 item_no: i16,
8603 item: &DialogItem,
8604 ) -> bool {
8605 let base_type = item.item_type & 0x7F;
8606 let is_disabled = (item.item_type & 0x80) != 0;
8607 base_type == 0
8608 && !is_disabled
8609 && item.proc_ptr == 0
8610 && !tracking.game_managed
8611 && !self
8612 .dialog_item_popup_menus
8613 .contains_key(&(tracking.dialog_ptr, item_no))
8614 && !self
8615 .dialog_popup_candidate_items
8616 .contains(&(tracking.dialog_ptr, item_no))
8617 }
8618
8619 fn dialog_plain_user_item_hit_test(
8620 &self,
8621 tracking: &DialogTrackingState,
8622 screen_v: i16,
8623 screen_h: i16,
8624 ) -> i16 {
8625 for (i, item) in tracking.items.iter().enumerate() {
8626 let item_no = (i + 1) as i16;
8627 if !self.is_plain_modal_user_item(tracking, item_no, item) {
8628 continue;
8629 }
8630 let (top, left, bottom, right) =
8631 Self::dialog_item_screen_rect(tracking.bounds, item.rect);
8632 if screen_v >= top && screen_v < bottom && screen_h >= left && screen_h < right {
8633 return item_no;
8634 }
8635 }
8636 0
8637 }
8638
8639 pub(crate) fn pending_dialog_plain_user_item_mouse_down(&self) -> bool {
8640 let Some(tracking) = self.dialog_tracking.as_ref() else {
8641 return false;
8642 };
8643 if tracking.active_user_item.is_some() {
8644 return true;
8645 }
8646 let Some(event) = self
8647 .event_queue
8648 .iter()
8649 .find(|event| matches!(event.what, 1 | 2 | 3 | 4 | 6))
8650 else {
8651 return false;
8652 };
8653 event.what == 1
8654 && self.dialog_plain_user_item_hit_test(tracking, event.where_v, event.where_h) > 0
8655 }
8656
8657 pub(crate) fn mouse_down_over_dialog_button(&self) -> bool {
8658 if !self.mouse_button {
8659 return false;
8660 }
8661 let Some(tracking) = self.dialog_tracking.as_ref() else {
8662 return false;
8663 };
8664 Self::dialog_button_hit_test(
8665 &tracking.items,
8666 tracking.bounds,
8667 self.mouse_pos.0,
8668 self.mouse_pos.1,
8669 ) > 0
8670 }
8671
8672 pub(crate) fn mouse_down_over_dialog_plain_user_item(&self) -> bool {
8673 if !self.mouse_button {
8674 return false;
8675 }
8676 let Some(tracking) = self.dialog_tracking.as_ref() else {
8677 return false;
8678 };
8679 tracking.active_user_item.is_some()
8680 || self.dialog_plain_user_item_hit_test(tracking, self.mouse_pos.0, self.mouse_pos.1)
8681 > 0
8682 }
8683
8684 fn read_guest_event_record(bus: &MacMemoryBus, event_ptr: u32) -> (u16, u32, i16, i16, u16) {
8685 if event_ptr == 0 {
8686 return (0, 0, 0, 0, 0);
8687 }
8688 (
8689 bus.read_word(event_ptr),
8690 bus.read_long(event_ptr + 2),
8691 bus.read_word(event_ptr + 10) as i16,
8692 bus.read_word(event_ptr + 12) as i16,
8693 bus.read_word(event_ptr + 14),
8694 )
8695 }
8696
8697 fn front_dialog_ptr(&self) -> Option<u32> {
8698 let dialog_ptr = self.front_window;
8699 if dialog_ptr != 0 && self.dialog_items.contains_key(&dialog_ptr) {
8700 Some(dialog_ptr)
8701 } else {
8702 None
8703 }
8704 }
8705
8706 fn front_retained_modal_dialog(
8707 &self,
8708 bus: &MacMemoryBus,
8709 ) -> Option<(u32, (i16, i16, i16, i16))> {
8710 if self.dialog_tracking.is_some() {
8711 return None;
8712 }
8713 let dialog_ptr = self.front_dialog_ptr()?;
8714 if !self.dialog_modal_entered.contains(&dialog_ptr) {
8722 return None;
8723 }
8724 if !self.window_visible(bus, dialog_ptr) {
8725 return None;
8726 }
8727 let proc_id = self.dialog_window_proc_id(bus, dialog_ptr);
8728 if !matches!(proc_id, 1 | 2 | 3 | 5) {
8734 return None;
8735 }
8736 Some((dialog_ptr, Self::dialog_screen_bounds(bus, dialog_ptr)))
8737 }
8738
8739 fn front_app_owned_modal_dialog(
8740 &self,
8741 bus: &MacMemoryBus,
8742 ) -> Option<(u32, (i16, i16, i16, i16))> {
8743 if self.dialog_tracking.is_some() || self.retained_modal_dialog_click.is_some() {
8744 return None;
8745 }
8746 let dialog_ptr = self.front_dialog_ptr()?;
8747 if self.dialog_modal_entered.contains(&dialog_ptr) || !self.window_visible(bus, dialog_ptr)
8748 {
8749 return None;
8750 }
8751 let proc_id = self.dialog_window_proc_id(bus, dialog_ptr);
8752 if !matches!(proc_id, 1 | 2 | 3 | 5) {
8753 return None;
8754 }
8755 Some((dialog_ptr, Self::dialog_screen_bounds(bus, dialog_ptr)))
8756 }
8757
8758 pub(crate) fn begin_app_owned_modal_dialog_button_tracking(
8759 &mut self,
8760 bus: &mut MacMemoryBus,
8761 event: &QueuedEvent,
8762 ) {
8763 if event.what != 1 {
8764 return;
8765 }
8766 let Some((dialog_ptr, bounds)) = self.front_app_owned_modal_dialog(bus) else {
8767 return;
8768 };
8769 if !Self::dialog_contains_screen_point(bounds, event.where_v, event.where_h) {
8770 return;
8771 }
8772
8773 let Some(mut items) = self.dialog_items.get(&dialog_ptr).cloned() else {
8774 return;
8775 };
8776 Self::refresh_ditl_proc_ptrs(bus, dialog_ptr, &mut items);
8777 let mut hit = self.dialog_item_hit_test(
8778 bus,
8779 &items,
8780 bounds,
8781 event.where_v,
8782 event.where_h,
8783 &self.dialog_popup_original_rects,
8784 dialog_ptr,
8785 );
8786 if hit <= 0 {
8787 hit = Self::dialog_button_hit_test(&items, bounds, event.where_v, event.where_h);
8788 }
8789 self.dialog_items.insert(dialog_ptr, items.clone());
8790 if hit <= 0 {
8791 return;
8792 }
8793
8794 let item = &items[(hit - 1) as usize];
8795 let base_type = item.item_type & 0x7F;
8796 let is_disabled = (item.item_type & 0x80) != 0;
8797 if base_type != 4 || is_disabled {
8798 return;
8799 }
8800
8801 let rect = Self::dialog_item_screen_rect(bounds, item.rect);
8809 self.invert_button_rect(bus, rect.0, rect.1, rect.2, rect.3);
8810 self.retained_modal_dialog_click = Some(RetainedModalDialogClickState {
8811 dialog_ptr,
8812 item_no: hit,
8813 rect: item.rect,
8814 title: item.text.clone(),
8815 is_default: false,
8816 highlighted: true,
8817 delivered_to_app: true,
8818 });
8819 }
8820
8821 fn cancel_app_owned_modal_dialog_button_tracking(
8822 &mut self,
8823 bus: &mut MacMemoryBus,
8824 dialog_ptr: u32,
8825 ) {
8826 let should_cancel = self
8827 .retained_modal_dialog_click
8828 .as_ref()
8829 .is_some_and(|click| click.dialog_ptr == dialog_ptr && click.delivered_to_app);
8830 if !should_cancel {
8831 return;
8832 }
8833 let Some(click) = self.retained_modal_dialog_click.take() else {
8834 return;
8835 };
8836 if click.item_no > 0 && click.highlighted {
8837 let bounds = Self::dialog_screen_bounds(bus, click.dialog_ptr);
8838 let rect = Self::dialog_item_screen_rect(bounds, click.rect);
8839 self.invert_button_rect(bus, rect.0, rect.1, rect.2, rect.3);
8840 }
8841 }
8842
8843 fn dialog_from_window_event(&self, what: u16, message: u32) -> Option<u32> {
8844 match what {
8845 6 | 8 if self.dialog_items.contains_key(&message) => Some(message),
8846 _ => self.front_dialog_ptr(),
8847 }
8848 }
8849
8850 fn dialog_contains_screen_point(bounds: (i16, i16, i16, i16), v: i16, h: i16) -> bool {
8851 v >= bounds.0 && v < bounds.2 && h >= bounds.1 && h < bounds.3
8852 }
8853
8854 fn point_in_screen_rect(v: i16, h: i16, rect: (i16, i16, i16, i16)) -> bool {
8855 v >= rect.0 && v < rect.2 && h >= rect.1 && h < rect.3
8856 }
8857
8858 fn close_dialog_window<C: CpuOps>(
8859 &mut self,
8860 bus: &mut MacMemoryBus,
8861 cpu: &mut C,
8862 dialog_ptr: u32,
8863 dispose_storage: bool,
8864 ) {
8865 let was_front = self.front_window == dialog_ptr;
8866 let retained_visible_bounds = self
8867 .dialog_visible_snapshots
8868 .get(&dialog_ptr)
8869 .map(|snapshot| snapshot.bounds);
8870 let dialog_record_bounds = if dialog_ptr != 0 && self.dialog_items.contains_key(&dialog_ptr)
8871 {
8872 Self::dialog_screen_bounds(bus, dialog_ptr)
8873 } else {
8874 self.window_bounds
8875 };
8876 let exposed_rect = if was_front {
8877 self.window_bounds
8878 } else {
8879 retained_visible_bounds.unwrap_or(dialog_record_bounds)
8880 };
8881 let initial_draw_deferred = self.dialog_initial_draw_deferred.remove(&dialog_ptr);
8882 let retained_stale_saved_under = self
8883 .dialog_saved_pixels
8884 .get(&dialog_ptr)
8885 .is_some_and(|saved| self.saved_pixels_are_mostly_logical_white(saved));
8886 let mut restored_stale_saved_under = false;
8887
8888 if was_front && !initial_draw_deferred {
8889 if let Some(saved) = self.dialog_saved_pixels.remove(&dialog_ptr) {
8890 restored_stale_saved_under = self.saved_pixels_are_mostly_logical_white(&saved);
8891 self.restore_dialog_pixels(bus, self.window_bounds, &saved);
8892 }
8893 } else {
8894 self.dialog_saved_pixels.remove(&dialog_ptr);
8895 }
8896
8897 self.dialog_visible_snapshots.remove(&dialog_ptr);
8898 self.dialog_modal_entered.remove(&dialog_ptr);
8899 if !was_front && retained_stale_saved_under && self.screen_is_hidden_menu_game_surface(bus)
8900 {
8901 let _ = self.restore_dialog_exposure_from_fullscreen_offscreen_port(bus, exposed_rect);
8902 }
8903 if self.pending_modal_button_dispose_dialog == Some(dialog_ptr) {
8904 self.pending_modal_button_dispose_dialog = None;
8905 }
8906 if self
8907 .retained_modal_dialog_click
8908 .as_ref()
8909 .is_some_and(|click| click.dialog_ptr == dialog_ptr)
8910 {
8911 self.retained_modal_dialog_click = None;
8912 }
8913
8914 self.dispose_dialog_owned_items(bus, dialog_ptr);
8915 if dispose_storage {
8916 self.dispose_dialog_record_and_item_list(bus, dialog_ptr);
8917 }
8918
8919 self.untrack_window(bus, dialog_ptr);
8920
8921 if was_front {
8922 if let Some((prev_window, prev_bounds, prev_proc_id, prev_title)) =
8923 self.window_stack.pop()
8924 {
8925 self.set_current_port_state(bus, cpu, prev_window, None);
8926 self.front_window = prev_window;
8927 self.window_bounds = prev_bounds;
8928 self.window_proc_id = prev_proc_id;
8929 self.window_title = prev_title;
8930 if prev_window != 0 {
8931 bus.write_byte(prev_window + 111, 0xFF);
8932 if self.window_visible(bus, prev_window) {
8933 self.blit_window_to_screen(bus);
8934 self.blit_large_manual_cport_to_screen(bus);
8935 }
8936 if !self.event_queue.iter().any(|event| {
8937 event.what == 8
8938 && event.message == prev_window
8939 && (event.modifiers & 1) != 0
8940 }) {
8941 self.event_queue
8942 .push_back(crate::trap::dispatch::QueuedEvent {
8943 what: 8,
8944 message: prev_window,
8945 where_v: 0,
8946 where_h: 0,
8947 modifiers: 1,
8948 });
8949 }
8950 self.draw_single_window_chrome_inline(bus, prev_window, true);
8951 let exposed_local = (
8952 exposed_rect.0.saturating_sub(prev_bounds.0),
8953 exposed_rect.1.saturating_sub(prev_bounds.1),
8954 exposed_rect.2.saturating_sub(prev_bounds.0),
8955 exposed_rect.3.saturating_sub(prev_bounds.1),
8956 );
8957 self.invalidate_window_rect(bus, prev_window, exposed_local);
8958 self.queue_window_update_event(prev_window);
8959 }
8960 let should_repair_stale_exposure = restored_stale_saved_under
8961 && if prev_window != 0 {
8962 self.promoted_window_is_fullscreen_game_surface(bus, prev_bounds)
8963 } else {
8964 self.screen_is_hidden_menu_game_surface(bus)
8965 };
8966 if should_repair_stale_exposure {
8967 let _ = self
8968 .restore_dialog_exposure_from_fullscreen_offscreen_port(bus, exposed_rect);
8969 }
8970 }
8971 }
8972 }
8973
8974 fn dialog_saved_white_like_count(&self, saved: &[u8]) -> usize {
8975 let (_, _, _, _, pixel_size) = self.get_screen_params();
8976 match pixel_size {
8977 8 => saved
8978 .iter()
8979 .filter(|&&idx| {
8980 let [r, g, b] = self.device_clut[idx as usize];
8981 r >= 0xEEEE && g >= 0xEEEE && b >= 0xEEEE
8982 })
8983 .count(),
8984 _ => saved.iter().filter(|&&byte| byte == 0).count(),
8985 }
8986 }
8987
8988 fn saved_pixels_are_mostly_logical_white(&self, saved: &[u8]) -> bool {
8989 let (_, _, _, _, pixel_size) = self.get_screen_params();
8990 pixel_size == 8
8991 && !saved.is_empty()
8992 && self
8993 .dialog_saved_white_like_count(saved)
8994 .saturating_mul(100)
8995 >= saved.len().saturating_mul(80)
8996 }
8997
8998 fn promoted_window_is_fullscreen_game_surface(
8999 &self,
9000 bus: &MacMemoryBus,
9001 bounds: (i16, i16, i16, i16),
9002 ) -> bool {
9003 let (_, _, screen_w, screen_h, _) = self.get_screen_params();
9004 let menu_bar_height = bus.read_word(crate::memory::globals::addr::MBAR_HEIGHT) as i16;
9005 bounds.0 <= 0
9006 && bounds.1 <= 0
9007 && bounds.2 >= screen_h
9008 && bounds.3 >= screen_w
9009 && (self.menu_bar_hidden || self.fullscreen_locked || menu_bar_height == 0)
9010 }
9011
9012 fn screen_is_hidden_menu_game_surface(&self, bus: &MacMemoryBus) -> bool {
9013 let menu_bar_height = bus.read_word(crate::memory::globals::addr::MBAR_HEIGHT) as i16;
9014 self.menu_bar_hidden || self.fullscreen_locked || menu_bar_height == 0
9015 }
9016
9017 fn restore_dialog_exposure_from_fullscreen_offscreen_port(
9018 &self,
9019 bus: &mut MacMemoryBus,
9020 bounds: (i16, i16, i16, i16),
9021 ) -> bool {
9022 let (screen_base, screen_row_bytes, screen_w, screen_h, pixel_size) =
9023 self.get_screen_params();
9024 if pixel_size != 8 || screen_w == 0 || screen_h == 0 {
9025 return false;
9026 }
9027 let (top, left, bottom, right) = Self::dialog_saved_pixel_rect(bounds);
9028 let top = top.max(0).min(screen_h);
9029 let left = left.max(0).min(screen_w);
9030 let bottom = bottom.max(0).min(screen_h);
9031 let right = right.max(0).min(screen_w);
9032 if top >= bottom || left >= right {
9033 return false;
9034 }
9035
9036 let mut ports = Vec::new();
9037 for port in self
9038 .cport_ports
9039 .iter()
9040 .copied()
9041 .chain(self.gworld_devices.keys().copied())
9042 {
9043 if !ports.contains(&port) {
9044 ports.push(port);
9045 }
9046 }
9047
9048 let mut best: Option<FullscreenOffscreenDialogRestoreCandidate> = None;
9049 for port in ports {
9050 let Some(mut candidate) =
9051 self.fullscreen_offscreen_dialog_restore_candidate(bus, port, screen_base)
9052 else {
9053 continue;
9054 };
9055 if candidate.top > 0
9056 || candidate.left > 0
9057 || candidate.bottom < screen_h
9058 || candidate.right < screen_w
9059 {
9060 continue;
9061 }
9062 let Some(score) = self.score_offscreen_dialog_restore_candidate(
9063 bus,
9064 candidate,
9065 (top, left, bottom, right),
9066 ) else {
9067 continue;
9068 };
9069 candidate.score = score;
9070 if best.map(|current| score > current.score).unwrap_or(true) {
9071 best = Some(candidate);
9072 }
9073 }
9074
9075 let Some(candidate) = best else {
9076 return false;
9077 };
9078
9079 let screen_ctab_handle = Self::gdevice_ctab_handle(bus, self.main_gdevice_handle);
9080 let src_ctab_seed = Self::ctab_seed(bus, candidate.ctab_handle);
9081 let dst_ctab_seed = Self::ctab_seed(bus, screen_ctab_handle);
9082 let src_clut = self.read_port_clut(bus, candidate.ctab_handle);
9083 let dst_clut = self.read_port_clut(bus, screen_ctab_handle);
9084 let hardware_palette_active = self.device_clut != self.color_manager_clut;
9085 let skip_canonical_to_screen = Self::uses_canonical_system_8bpp_clut(&src_clut);
9086 let translation = if candidate.ctab_handle != screen_ctab_handle
9087 && matches!(src_ctab_seed, Some(src_seed) if src_seed != 0)
9088 && src_ctab_seed != dst_ctab_seed
9089 && !skip_canonical_to_screen
9090 && !hardware_palette_active
9091 {
9092 Some(self.build_palette_translation(bus, &src_clut, &dst_clut, screen_ctab_handle))
9093 } else {
9094 None
9095 };
9096
9097 let width = (right - left) as u32;
9098 for y in top..bottom {
9099 let src_y = (y - candidate.top) as u32;
9100 let src_x = (left - candidate.left) as u32;
9101 let dst_addr = screen_base + (y as u32) * screen_row_bytes + left as u32;
9102 let src_addr = candidate.base + src_y * candidate.row_bytes + src_x;
9103 if let Some(translation) = translation.as_ref() {
9104 for col in 0..width {
9105 let src_idx = bus.read_byte(src_addr + col);
9106 bus.write_byte(dst_addr + col, translation[src_idx as usize]);
9107 }
9108 } else {
9109 let row = bus.read_bytes(src_addr, width as usize);
9110 bus.write_bytes(dst_addr, &row);
9111 }
9112 }
9113
9114 true
9115 }
9116
9117 fn fullscreen_offscreen_dialog_restore_candidate(
9118 &self,
9119 bus: &MacMemoryBus,
9120 port: u32,
9121 screen_base: u32,
9122 ) -> Option<FullscreenOffscreenDialogRestoreCandidate> {
9123 if port == 0 || (bus.read_word(port + 6) & 0xC000) != 0xC000 {
9124 return None;
9125 }
9126 let pm_handle = bus.read_long(port + 2);
9127 if pm_handle == 0 {
9128 return None;
9129 }
9130 let pm_ptr = bus.read_long(pm_handle);
9131 if pm_ptr == 0 {
9132 return None;
9133 }
9134 let base = bus.read_long(pm_ptr) & 0x3FFF_FFFF;
9135 let row_bytes = (bus.read_word(pm_ptr + 4) & 0x3FFF) as u32;
9136 let top = bus.read_word(pm_ptr + 6) as i16;
9137 let left = bus.read_word(pm_ptr + 8) as i16;
9138 let bottom = bus.read_word(pm_ptr + 10) as i16;
9139 let right = bus.read_word(pm_ptr + 12) as i16;
9140 let pixel_size = bus.read_word(pm_ptr + 32);
9141 if base == 0
9142 || base == screen_base
9143 || pixel_size != 8
9144 || bottom <= top
9145 || right <= left
9146 || row_bytes < (right - left) as u32
9147 {
9148 return None;
9149 }
9150 Some(FullscreenOffscreenDialogRestoreCandidate {
9151 base,
9152 row_bytes,
9153 top,
9154 left,
9155 bottom,
9156 right,
9157 ctab_handle: bus.read_long(pm_ptr + 42),
9158 score: 0,
9159 })
9160 }
9161
9162 fn score_offscreen_dialog_restore_candidate(
9163 &self,
9164 bus: &MacMemoryBus,
9165 candidate: FullscreenOffscreenDialogRestoreCandidate,
9166 rect: (i16, i16, i16, i16),
9167 ) -> Option<u32> {
9168 let (top, left, bottom, right) = rect;
9169 let width = right - left;
9170 let height = bottom - top;
9171 if width <= 0 || height <= 0 {
9172 return None;
9173 }
9174 let step_x = (width as u32 / 32).max(1);
9175 let step_y = (height as u32 / 24).max(1);
9176 let mut total = 0u32;
9177 let mut visible = 0u32;
9178 let mut white_like = 0u32;
9179 let mut black_like = 0u32;
9180 let mut y = top as u32 + step_y / 2;
9181 while y < bottom as u32 {
9182 let mut x = left as u32 + step_x / 2;
9183 while x < right as u32 {
9184 let src_y = y as i16 - candidate.top;
9185 let src_x = x as i16 - candidate.left;
9186 if src_y >= 0 && src_x >= 0 {
9187 let idx = bus.read_byte(
9188 candidate.base + src_y as u32 * candidate.row_bytes + src_x as u32,
9189 ) as usize;
9190 let [r, g, b] = self.device_clut[idx];
9191 total += 1;
9192 if r > 0x1111 || g > 0x1111 || b > 0x1111 {
9193 visible += 1;
9194 }
9195 if r >= 0xEEEE && g >= 0xEEEE && b >= 0xEEEE {
9196 white_like += 1;
9197 }
9198 if r <= 0x1111 && g <= 0x1111 && b <= 0x1111 {
9199 black_like += 1;
9200 }
9201 }
9202 x += step_x;
9203 }
9204 y += step_y;
9205 }
9206 if total == 0
9207 || visible < 8
9208 || white_like.saturating_mul(100) >= total.saturating_mul(70)
9209 || black_like.saturating_mul(100) >= total.saturating_mul(98)
9210 {
9211 return None;
9212 }
9213 Some(visible.saturating_mul(2).saturating_sub(white_like))
9214 }
9215
9216 fn resolve_dispos_dialog_ptr_after_modal_button_hit(
9217 &mut self,
9218 requested_dialog_ptr: u32,
9219 ) -> u32 {
9220 let Some(dialog_ptr) = self.pending_modal_button_dispose_dialog.take() else {
9221 return requested_dialog_ptr;
9222 };
9223
9224 if requested_dialog_ptr != 0
9225 && requested_dialog_ptr != dialog_ptr
9226 && !self.dialog_items.contains_key(&requested_dialog_ptr)
9227 && !self.window_list.contains(&requested_dialog_ptr)
9228 && self.front_window == dialog_ptr
9229 && self.dialog_modal_entered.contains(&dialog_ptr)
9230 && self.dialog_items.contains_key(&dialog_ptr)
9231 {
9232 eprintln!(
9233 "[TRAP] DisposDialog(${:08X}) recovered front ModalDialog button target ${:08X}",
9234 requested_dialog_ptr, dialog_ptr
9235 );
9236 dialog_ptr
9237 } else {
9238 requested_dialog_ptr
9239 }
9240 }
9241
9242 pub(crate) fn consume_retained_modal_dialog_event<C: CpuOps>(
9243 &mut self,
9244 cpu: &mut C,
9245 bus: &mut MacMemoryBus,
9246 event: &QueuedEvent,
9247 ) -> bool {
9248 match event.what {
9249 1 => {
9250 let Some((dialog_ptr, bounds)) = self.front_retained_modal_dialog(bus) else {
9251 return false;
9252 };
9253
9254 if !Self::dialog_contains_screen_point(bounds, event.where_v, event.where_h) {
9255 self.retained_modal_dialog_click = Some(RetainedModalDialogClickState {
9261 dialog_ptr,
9262 item_no: 0,
9263 rect: (0, 0, 0, 0),
9264 title: String::new(),
9265 is_default: false,
9266 highlighted: false,
9267 delivered_to_app: false,
9268 });
9269 return true;
9270 }
9271
9272 let Some(mut items) = self.dialog_items.get(&dialog_ptr).cloned() else {
9273 return false;
9274 };
9275 Self::refresh_ditl_proc_ptrs(bus, dialog_ptr, &mut items);
9276 let mut hit = self.dialog_item_hit_test(
9277 bus,
9278 &items,
9279 bounds,
9280 event.where_v,
9281 event.where_h,
9282 &self.dialog_popup_original_rects,
9283 dialog_ptr,
9284 );
9285 if hit <= 0 {
9286 hit =
9287 Self::dialog_button_hit_test(&items, bounds, event.where_v, event.where_h);
9288 }
9289 self.dialog_items.insert(dialog_ptr, items.clone());
9290
9291 if hit <= 0 {
9292 self.retained_modal_dialog_click = Some(RetainedModalDialogClickState {
9293 dialog_ptr,
9294 item_no: 0,
9295 rect: (0, 0, 0, 0),
9296 title: String::new(),
9297 is_default: false,
9298 highlighted: false,
9299 delivered_to_app: false,
9300 });
9301 return true;
9302 }
9303
9304 let item = &items[(hit - 1) as usize];
9305 let base_type = item.item_type & 0x7F;
9306 let is_disabled = (item.item_type & 0x80) != 0;
9307 if base_type == 4 && !is_disabled {
9308 let rect = Self::dialog_item_screen_rect(bounds, item.rect);
9309 let is_default = hit == bus.read_word(dialog_ptr + 168) as i16;
9310 self.draw_dialog_button_highlight_state(
9311 bus, rect, &item.text, is_default, true,
9312 );
9313 self.retained_modal_dialog_click = Some(RetainedModalDialogClickState {
9314 dialog_ptr,
9315 item_no: hit,
9316 rect: item.rect,
9317 title: item.text.clone(),
9318 is_default,
9319 highlighted: true,
9320 delivered_to_app: false,
9321 });
9322 } else {
9323 self.retained_modal_dialog_click = Some(RetainedModalDialogClickState {
9324 dialog_ptr,
9325 item_no: 0,
9326 rect: (0, 0, 0, 0),
9327 title: String::new(),
9328 is_default: false,
9329 highlighted: false,
9330 delivered_to_app: false,
9331 });
9332 }
9333 true
9334 }
9335 2 => {
9336 let Some(click) = self.retained_modal_dialog_click.take() else {
9337 return false;
9338 };
9339 if click.item_no > 0 {
9340 let bounds = Self::dialog_screen_bounds(bus, click.dialog_ptr);
9341 let rect = Self::dialog_item_screen_rect(bounds, click.rect);
9342 if click.highlighted {
9343 self.draw_dialog_button_highlight_state(
9344 bus,
9345 rect,
9346 &click.title,
9347 click.is_default,
9348 false,
9349 );
9350 }
9351 if self.front_window == click.dialog_ptr
9352 && Self::point_in_screen_rect(event.where_v, event.where_h, rect)
9353 {
9354 self.close_dialog_window(bus, cpu, click.dialog_ptr, true);
9355 self.capture_gui_frame(
9356 bus,
9357 &format!("retained_modal_dialog_button_{}", click.item_no),
9358 );
9359 }
9360 }
9361 !click.delivered_to_app
9362 }
9363 _ => false,
9364 }
9365 }
9366
9367 fn consume_dialog_mouse_up(&mut self) {
9368 if let Some(idx) = self.event_queue.iter().position(|e| e.what == 2) {
9369 self.event_queue.remove(idx);
9370 }
9371 }
9372
9373 fn dialog_item_screen_rect(
9374 bounds: (i16, i16, i16, i16),
9375 rect: (i16, i16, i16, i16),
9376 ) -> (i16, i16, i16, i16) {
9377 let (it, il, ib, ir) = rect;
9378 (bounds.0 + it, bounds.1 + il, bounds.0 + ib, bounds.1 + ir)
9379 }
9380
9381 fn rects_intersect(a: (i16, i16, i16, i16), b: (i16, i16, i16, i16)) -> bool {
9382 a.0 < b.2 && a.2 > b.0 && a.1 < b.3 && a.3 > b.1
9383 }
9384
9385 fn dialog_item_intersects_bounds(bounds: (i16, i16, i16, i16), item: &DialogItem) -> bool {
9386 Self::rects_intersect(Self::dialog_item_screen_rect(bounds, item.rect), bounds)
9387 }
9388
9389 fn dialog_is_game_managed(bounds: (i16, i16, i16, i16), items: &[DialogItem]) -> bool {
9390 let mut has_visible_item = false;
9391 for item in items {
9392 if !Self::dialog_item_intersects_bounds(bounds, item) {
9393 continue;
9394 }
9395 has_visible_item = true;
9396 if (item.item_type & 0x7F) != 0 {
9397 return false;
9398 }
9399 }
9400 has_visible_item
9401 }
9402
9403 fn start_dialog_button_flash(
9404 &mut self,
9405 bus: &mut MacMemoryBus,
9406 bounds: (i16, i16, i16, i16),
9407 item_no: i16,
9408 rect: (i16, i16, i16, i16),
9409 title: &str,
9410 is_default: bool,
9411 highlighted: bool,
9412 ) {
9413 if !highlighted {
9414 let screen_rect = Self::dialog_item_screen_rect(bounds, rect);
9415 self.draw_dialog_button_highlight_state(bus, screen_rect, title, is_default, true);
9416 }
9417 if let Some(t) = self.dialog_tracking.as_mut() {
9418 t.flash_remaining = 6;
9419 t.flash_delay = 3;
9420 t.flash_item = item_no;
9421 }
9422 }
9423
9424 fn dialog_control_handle_for_item(&self, dialog_ptr: u32, item_no: i16) -> Option<u32> {
9425 self.dialog_control_handles
9426 .iter()
9427 .find_map(|(&handle, &(dlg, item))| {
9428 if dlg == dialog_ptr && item == item_no {
9429 Some(handle)
9430 } else {
9431 None
9432 }
9433 })
9434 }
9435
9436 fn dialog_control_inactive(&self, bus: &MacMemoryBus, dialog_ptr: u32, item_no: i16) -> bool {
9437 self.dialog_control_handle_for_item(dialog_ptr, item_no)
9438 .map(|handle| bus.read_long(handle))
9439 .is_some_and(|ctrl_ptr| ctrl_ptr != 0 && bus.read_byte(ctrl_ptr + 17) == 255)
9440 }
9441
9442 fn begin_dialog_popup_tracking(
9443 &mut self,
9444 bus: &mut MacMemoryBus,
9445 dialog_ptr: u32,
9446 item_no: i16,
9447 ) -> bool {
9448 let Some(ctrl_handle) = self.dialog_control_handle_for_item(dialog_ptr, item_no) else {
9449 return false;
9450 };
9451 let ctrl_ptr = bus.read_long(ctrl_handle);
9452 if ctrl_ptr == 0 {
9453 return false;
9454 }
9455 let proc_id = self.control_proc_ids.get(&ctrl_ptr).copied().unwrap_or(0);
9456 if !Self::is_popup_menu_proc_id(proc_id) {
9457 return false;
9458 }
9459 let menu_id =
9460 self.popup_control_menu_id(bus, ctrl_ptr, bus.read_word(ctrl_ptr + 20) as i16);
9461 let Some(menu_idx) = self.menus.iter().rposition(|menu| menu.id == menu_id) else {
9462 return false;
9463 };
9464
9465 let dropdown_rect = self.popup_control_dropdown_rect(bus, ctrl_ptr, menu_idx);
9466 let saved_pixels = self.save_dropdown_pixels(bus, dropdown_rect);
9467 self.draw_menu_dropdown(bus, menu_idx, dropdown_rect);
9468
9469 if let Some(tracking) = self.dialog_tracking.as_mut() {
9470 tracking.active_popup = Some(DialogPopupTrackingState {
9471 item_no,
9472 ctrl_handle,
9473 ctrl_ptr,
9474 active_menu: menu_idx,
9475 highlighted_item: 0,
9476 saved_pixels,
9477 dropdown_rect,
9478 });
9479 true
9480 } else {
9481 false
9482 }
9483 }
9484
9485 fn dialog_popup_item_at_point(&self, bus: &MacMemoryBus, mouse_x: i16, mouse_y: i16) -> i16 {
9486 let Some(popup) = self
9487 .dialog_tracking
9488 .as_ref()
9489 .and_then(|tracking| tracking.active_popup.as_ref())
9490 else {
9491 return 0;
9492 };
9493 let (top, left, bottom, right) = popup.dropdown_rect;
9494 if mouse_x < left || mouse_x >= right || mouse_y < top || mouse_y >= bottom {
9495 return 0;
9496 }
9497 let Some(menu) = self.menus.get(popup.active_menu) else {
9498 return 0;
9499 };
9500 let mut item_top = top + 1;
9501 for (item_idx, item) in menu.items.iter().enumerate() {
9502 let item_bottom = item_top + self.menu_item_height(bus, item);
9503 if mouse_y >= item_top && mouse_y < item_bottom {
9504 if item.text == "-" || !item.enabled {
9505 return 0;
9506 }
9507 return item_idx as i16 + 1;
9508 }
9509 item_top = item_bottom;
9510 }
9511 0
9512 }
9513
9514 fn handle_dialog_popup_tracking<C: CpuOps>(&mut self, cpu: &mut C, bus: &mut MacMemoryBus) {
9515 if self.mouse_button {
9516 let (mv, mh) = self.mouse_pos;
9517 let new_item = self.dialog_popup_item_at_point(bus, mh, mv);
9518 let old_item = self
9519 .dialog_tracking
9520 .as_ref()
9521 .and_then(|tracking| tracking.active_popup.as_ref())
9522 .map(|popup| popup.highlighted_item)
9523 .unwrap_or(0);
9524 if new_item != old_item {
9525 let popup_state = self
9526 .dialog_tracking
9527 .as_ref()
9528 .and_then(|tracking| tracking.active_popup.as_ref())
9529 .map(|popup| (popup.active_menu, popup.dropdown_rect));
9530 if let Some((active_menu, dropdown_rect)) = popup_state {
9531 if old_item > 0 {
9532 self.invert_dropdown_item_rect(bus, active_menu, dropdown_rect, old_item);
9533 }
9534 if let Some(popup) = self
9535 .dialog_tracking
9536 .as_mut()
9537 .and_then(|tracking| tracking.active_popup.as_mut())
9538 {
9539 popup.highlighted_item = new_item;
9540 }
9541 if new_item > 0 {
9542 self.invert_dropdown_item_rect(bus, active_menu, dropdown_rect, new_item);
9543 }
9544 }
9545 }
9546 return;
9547 }
9548
9549 let Some(popup) = self
9550 .dialog_tracking
9551 .as_mut()
9552 .and_then(|tracking| tracking.active_popup.take())
9553 else {
9554 return;
9555 };
9556 self.restore_dropdown_pixels(bus, popup.dropdown_rect, &popup.saved_pixels);
9557 self.consume_dialog_mouse_up();
9558
9559 let selected_item = popup.highlighted_item;
9560 if selected_item <= 0 {
9561 return;
9562 }
9563
9564 self.write_control_value(bus, popup.ctrl_handle, selected_item);
9565 self.draw_control(cpu, bus, popup.ctrl_ptr);
9566
9567 if self.dialog_tracking.is_some() {
9568 let (dialog_ptr, edit_item, edit_text, items) = {
9569 let tracking = self.dialog_tracking.as_mut().unwrap();
9570 Self::sync_tracking_active_edit_item(tracking);
9571 (
9572 tracking.dialog_ptr,
9573 tracking.edit_item,
9574 tracking.edit_text.clone(),
9575 tracking.items.clone(),
9576 )
9577 };
9578 self.flush_dialog_edit_item_texts(bus, dialog_ptr, &items, edit_item, &edit_text);
9579 }
9580 if let Some((bounds, game_managed)) = self
9581 .dialog_tracking
9582 .as_ref()
9583 .map(|tracking| (tracking.bounds, tracking.game_managed))
9584 {
9585 if !game_managed {
9586 let rendered = self.save_dialog_pixels(bus, bounds);
9587 if let Some(tracking) = self.dialog_tracking.as_mut() {
9588 tracking.rendered_pixels = rendered;
9589 tracking.rendered_pixels_final = true;
9590 }
9591 }
9592 }
9593
9594 let Some(saved) = self.dialog_tracking.take() else {
9595 return;
9596 };
9597 self.persist_visible_dialog_snapshot(bus, &saved);
9598 self.dialog_saved_pixels
9599 .insert(saved.dialog_ptr, saved.saved_pixels);
9600 if saved.item_hit_ptr != 0 {
9601 bus.write_word(saved.item_hit_ptr, popup.item_no as u16);
9602 }
9603 cpu.write_reg(Register::A7, saved.stack_ptr + 8);
9604 }
9605
9606 fn handle_dialog_button_tracking(&mut self, bus: &mut MacMemoryBus) {
9607 let Some((dialog_ptr, bounds, item_no, rect, title, is_default, highlighted, item_type)) =
9608 self.dialog_tracking.as_ref().and_then(|tracking| {
9609 tracking.active_button.as_ref().map(|button| {
9610 (
9611 tracking.dialog_ptr,
9612 tracking.bounds,
9613 button.item_no,
9614 button.rect,
9615 button.title.clone(),
9616 button.is_default,
9617 button.highlighted,
9618 tracking
9619 .items
9620 .get(button.item_no.saturating_sub(1) as usize)
9621 .map(|item| item.item_type)
9622 .unwrap_or(4),
9623 )
9624 })
9625 })
9626 else {
9627 return;
9628 };
9629
9630 let (top, left, bottom, right) = Self::dialog_item_screen_rect(bounds, rect);
9631 let (mouse_v, mouse_h) = self.mouse_pos;
9632 let inside = mouse_v >= top && mouse_v < bottom && mouse_h >= left && mouse_h < right;
9633
9634 if self.mouse_button {
9635 if inside != highlighted {
9636 self.draw_dialog_button_highlight_state(
9637 bus,
9638 (top, left, bottom, right),
9639 &title,
9640 is_default,
9641 inside,
9642 );
9643 if let Some(button) = self
9644 .dialog_tracking
9645 .as_mut()
9646 .and_then(|tracking| tracking.active_button.as_mut())
9647 {
9648 button.highlighted = inside;
9649 }
9650 self.record_modal_dialog_input_trace(
9651 "tracking_update",
9652 dialog_ptr,
9653 bounds,
9654 item_no,
9655 Some(item_type),
9656 Some(inside),
9657 "pending",
9658 if inside {
9659 "button_highlighted"
9660 } else {
9661 "button_unhighlighted"
9662 },
9663 );
9664 }
9665 return;
9666 }
9667
9668 self.consume_dialog_mouse_up();
9669 if let Some(t) = self.dialog_tracking.as_mut() {
9670 t.active_button = None;
9671 }
9672
9673 if inside {
9674 self.record_modal_dialog_input_trace(
9675 "release",
9676 dialog_ptr,
9677 bounds,
9678 item_no,
9679 Some(item_type),
9680 Some(true),
9681 "pending",
9682 "start_flash",
9683 );
9684 self.start_dialog_button_flash(
9685 bus,
9686 bounds,
9687 item_no,
9688 rect,
9689 &title,
9690 is_default,
9691 highlighted,
9692 );
9693 } else if highlighted {
9694 self.draw_dialog_button_highlight_state(
9695 bus,
9696 (top, left, bottom, right),
9697 &title,
9698 is_default,
9699 false,
9700 );
9701 self.record_modal_dialog_input_trace(
9702 "release",
9703 dialog_ptr,
9704 bounds,
9705 item_no,
9706 Some(item_type),
9707 Some(false),
9708 "pending",
9709 "button_no_selection",
9710 );
9711 } else {
9712 self.record_modal_dialog_input_trace(
9713 "release",
9714 dialog_ptr,
9715 bounds,
9716 item_no,
9717 Some(item_type),
9718 Some(false),
9719 "pending",
9720 "button_no_selection",
9721 );
9722 }
9723 }
9724
9725 fn handle_dialog_user_item_tracking<C: CpuOps>(&mut self, cpu: &mut C, bus: &mut MacMemoryBus) {
9726 let Some((bounds, item_no, rect)) = self.dialog_tracking.as_ref().and_then(|tracking| {
9727 tracking
9728 .active_user_item
9729 .as_ref()
9730 .map(|user_item| (tracking.bounds, user_item.item_no, user_item.rect))
9731 }) else {
9732 return;
9733 };
9734
9735 if self.mouse_button {
9736 return;
9737 }
9738
9739 self.consume_dialog_mouse_up();
9740 if let Some(t) = self.dialog_tracking.as_mut() {
9741 t.active_user_item = None;
9742 }
9743
9744 let (top, left, bottom, right) = Self::dialog_item_screen_rect(bounds, rect);
9745 let (mouse_v, mouse_h) = self.mouse_pos;
9746 let inside = mouse_v >= top && mouse_v < bottom && mouse_h >= left && mouse_h < right;
9747 if !inside {
9748 return;
9749 }
9750
9751 let (dlg_ptr, edit_item, edit_text, items) = {
9752 let tracking = self.dialog_tracking.as_mut().unwrap();
9753 Self::sync_tracking_active_edit_item(tracking);
9754 (
9755 tracking.dialog_ptr,
9756 tracking.edit_item,
9757 tracking.edit_text.clone(),
9758 tracking.items.clone(),
9759 )
9760 };
9761 self.flush_dialog_edit_item_texts(bus, dlg_ptr, &items, edit_item, &edit_text);
9762 let saved = self.dialog_tracking.take().unwrap();
9763 self.persist_visible_dialog_snapshot(bus, &saved);
9764 self.dialog_saved_pixels
9765 .insert(saved.dialog_ptr, saved.saved_pixels);
9766 if saved.item_hit_ptr != 0 {
9767 bus.write_word(saved.item_hit_ptr, item_no as u16);
9768 }
9769 cpu.write_reg(Register::A7, saved.stack_ptr + 8);
9770 }
9771
9772 pub(crate) fn dispatch_dialog<C: CpuOps>(
9773 &mut self,
9774 is_tool: bool,
9775 trap_num: u16,
9776 cpu: &mut C,
9777 bus: &mut MacMemoryBus,
9778 ) -> Option<Result<()>> {
9779 Some(match (is_tool, trap_num) {
9780 (true, 0x17B) => {
9812 let sp = cpu.read_reg(Register::A7);
9813 let resume_proc = bus.read_long(sp);
9814 use crate::memory::globals::addr;
9815 bus.write_long(addr::RESUME_PROC, resume_proc);
9816 bus.write_long(addr::DA_BEEPER, 0);
9817 bus.write_word(addr::ALERT_STAGE, 0);
9818 for i in 0..4u32 {
9820 bus.write_long(addr::DA_STRINGS + i * 4, 0);
9821 }
9822 cpu.write_reg(Register::A7, sp + 4);
9823 Ok(())
9824 }
9825
9826 (true, 0x17C) => {
9838 let sp = cpu.read_reg(Register::A7);
9839 let behind = bus.read_long(sp);
9840 let storage_ptr = bus.read_long(sp + 4);
9841 let dialog_id = bus.read_word(sp + 8) as i16;
9842 eprintln!("[TRAP] GetNewDialog({})", dialog_id);
9843
9844 let dlog_ptr = self
9846 .find_resource_any(*b"DLOG", dialog_id)
9847 .map(|(_, ptr)| ptr);
9848
9849 if let Some(dlog_data) = dlog_ptr {
9850 let dlog_len = bus.get_alloc_size(dlog_data).unwrap_or(0);
9851 let (raw_bounds, proc_id, visible, items_id, title, position) =
9852 Self::parse_dlog(bus, dlog_data, dlog_len);
9853 let mut bounds = raw_bounds;
9854
9855 let ditl_info = self
9857 .find_resource_any(*b"DITL", items_id)
9858 .map(|(_, ptr)| ptr);
9859
9860 let Some(ditl_data) = ditl_info else {
9861 eprintln!(
9862 "[TRAP] GetNewDialog({}): DITL {} not found",
9863 dialog_id, items_id
9864 );
9865 bus.write_word(0x0A60, Self::RES_NOT_FOUND as u16);
9870 bus.write_long(sp + 10, 0);
9871 cpu.write_reg(Register::A7, sp + 10);
9872 return Some(Ok(()));
9873 };
9874 bus.write_word(0x0A60, 0);
9875 let ditl_len = bus.get_alloc_size(ditl_data).unwrap_or(0);
9876 let items = Self::parse_ditl(bus, ditl_data, ditl_len);
9877
9878 bounds = self.positioned_dialog_bounds(bounds, position);
9881
9882 eprintln!(
9883 "[TRAP] GetNewDialog({}) bounds=({},{},{},{}) raw_bounds=({},{},{},{}) position=${:04X} len={} procID={} items={} title=\"{}\"",
9884 dialog_id, bounds.0, bounds.1, bounds.2, bounds.3,
9885 raw_bounds.0, raw_bounds.1, raw_bounds.2, raw_bounds.3,
9886 position,
9887 dlog_len,
9888 proc_id, items.len(), title
9889 );
9890 for (i, item) in items.iter().enumerate() {
9891 let base_type = item.item_type & 0x7F;
9892 let type_name = match base_type {
9893 4 => "button",
9894 5 => "checkbox",
9895 6 => "radio",
9896 7 => "resCtrl",
9897 8 => "statText",
9898 16 => "editText",
9899 32 => "icon",
9900 64 => "picture",
9901 0 => "userItem",
9902 _ => "unknown",
9903 };
9904 if base_type == 32 || base_type == 64 {
9905 eprintln!(
9906 "[TRAP] item {}: type={}({}) rect=({},{},{},{}) resID={}",
9907 i + 1,
9908 type_name,
9909 item.item_type,
9910 item.rect.0,
9911 item.rect.1,
9912 item.rect.2,
9913 item.rect.3,
9914 item.resource_id
9915 );
9916 } else {
9917 eprintln!(
9918 "[TRAP] item {}: type={}({}) rect=({},{},{},{}) text=\"{}\"",
9919 i + 1,
9920 type_name,
9921 item.item_type,
9922 item.rect.0,
9923 item.rect.1,
9924 item.rect.2,
9925 item.rect.3,
9926 item.text
9927 );
9928 }
9929 }
9930
9931 let items_handle = if let Some((_, ditl_handle_ptr)) =
9932 self.find_resource_any(*b"DITL", items_id)
9933 {
9934 let handle = bus.alloc(4);
9935 bus.write_long(handle, ditl_handle_ptr);
9936 Self::duplicate_handle_data(bus, handle)
9937 } else {
9938 0
9939 };
9940 let dlg_ptr = self.finish_dialog_creation(
9942 bus,
9943 cpu,
9944 storage_ptr,
9945 bounds,
9946 &title,
9947 visible,
9948 proc_id,
9949 false,
9950 0,
9951 items_handle,
9952 items,
9953 );
9954 if dlg_ptr != 0 {
9968 let palette = self.copy_palette_resource(bus, dialog_id);
9969 if palette != 0 {
9970 self.set_window_palette_association(dlg_ptr, palette, -0x2000);
9971 self.activate_palette_for_window(bus, dlg_ptr);
9972 }
9973 self.apply_behind_parameter(bus, dlg_ptr, behind);
9974 }
9975 bus.write_long(sp + 10, dlg_ptr);
9976 } else {
9977 eprintln!("[TRAP] GetNewDialog({}): DLOG not found -> NIL", dialog_id);
9978 bus.write_word(0x0A60, Self::RES_NOT_FOUND as u16);
9982 bus.write_long(sp + 10, 0);
9983 }
9984 cpu.write_reg(Register::A7, sp + 10);
9985 Ok(())
9986 }
9987
9988 (true, 0x185) | (true, 0x186) | (true, 0x187) | (true, 0x188) => {
10045 const ALERT_MISSING_RESOURCE_RESULT: i16 = -1;
10046 let sp = cpu.read_reg(Register::A7);
10047 if self.dialog_tracking.is_some() {
10048 self.handle_interactive_alert_refire(cpu, bus);
10049 return Some(Ok(()));
10050 }
10051 let filter_proc = bus.read_long(sp);
10052 let alert_id = bus.read_word(sp + 4) as i16;
10053 let trap_name = match trap_num {
10054 0x185 => "Alert",
10055 0x186 => "StopAlert",
10056 0x187 => "NoteAlert",
10057 0x188 => "CautionAlert",
10058 _ => "Alert?",
10059 };
10060 let alert_stage_before = bus.read_word(crate::memory::globals::addr::ALERT_STAGE);
10061 let anumber_before = bus.read_word(crate::memory::globals::addr::ANUMBER);
10062 let mut alert_trace_stage: Option<(u16, u16, u32, u32)> = None;
10063 let alrt_ptr = self
10073 .find_resource_any(*b"ALRT", alert_id)
10074 .map(|(_, ptr)| ptr);
10075 let result: i16 = if let Some(alrt_data) = alrt_ptr {
10076 let alrt_len = bus.get_alloc_size(alrt_data).unwrap_or(0);
10077 let (bounds, items_id, stages, position) =
10078 Self::parse_alrt(bus, alrt_data, alrt_len);
10079 let stage_word = bus.read_word(crate::memory::globals::addr::ALERT_STAGE);
10088 let (stage_idx, nibble, default_item) =
10089 Self::alert_stage_default_item(stages, stage_word);
10090 alert_trace_stage = Some((stages, stage_word, stage_idx, nibble));
10091 let next_stage = ((stage_word as u32) + 1).min(3) as u16;
10099 bus.write_word(crate::memory::globals::addr::ALERT_STAGE, next_stage);
10100 bus.write_word(crate::memory::globals::addr::ANUMBER, alert_id as u16);
10106
10107 if let Some(default_item) = default_item {
10108 if filter_proc == 0
10113 && self.begin_interactive_alert(
10114 cpu,
10115 bus,
10116 sp,
10117 alert_id,
10118 filter_proc,
10119 bounds,
10120 items_id,
10121 position,
10122 default_item,
10123 )
10124 {
10125 if super::dispatch::trace_dialog_traps_enabled() {
10126 eprintln!(
10127 "[TRAP] {} id={} -> interactive dialog (PC=${:08X}) stages=${:04X} alertStage={} stageIdx={} nibble=${:X} defaultItem={}",
10128 trap_name,
10129 alert_id,
10130 cpu.read_reg(Register::PC),
10131 stages,
10132 stage_word,
10133 stage_idx,
10134 nibble,
10135 default_item
10136 );
10137 }
10138 return Some(Ok(()));
10139 }
10140 default_item
10141 } else {
10142 ALERT_MISSING_RESOURCE_RESULT
10143 }
10144 } else {
10145 bus.write_word(
10146 crate::memory::globals::addr::ALERT_STAGE,
10147 alert_stage_before,
10148 );
10149 bus.write_word(crate::memory::globals::addr::ANUMBER, anumber_before);
10150 ALERT_MISSING_RESOURCE_RESULT
10151 };
10152
10153 if super::dispatch::trace_dialog_traps_enabled() {
10154 let mut detail = format!(
10155 "[TRAP] {} id={} -> item {} (PC=${:08X})",
10156 trap_name,
10157 alert_id,
10158 result,
10159 cpu.read_reg(Register::PC)
10160 );
10161 if let Some((stages, stage_word, stage_idx, nibble)) = alert_trace_stage {
10162 detail.push_str(&format!(
10163 " stages=${stages:04X} alertStage={stage_word} stageIdx={stage_idx} nibble=${nibble:X}"
10164 ));
10165 }
10166 if let Some(alrt_data) = alrt_ptr {
10167 let alrt_len = bus.get_alloc_size(alrt_data).unwrap_or(0);
10178 let ((top, left, bottom, right), items_id, _, _) =
10179 Self::parse_alrt(bus, alrt_data, alrt_len);
10180 let bounds_plausible = (-32..1024).contains(&top)
10181 && (-32..2048).contains(&left)
10182 && bottom > top
10183 && right > left
10184 && (bottom - top) < 1024
10185 && (right - left) < 2048;
10186 if !bounds_plausible {
10187 detail.push_str(&format!(
10188 " bounds=({},{},{},{}) — implausible, skipping DITL lookup",
10189 top, left, bottom, right
10190 ));
10191 } else {
10192 let ditl_match = self.find_resource_any(*b"DITL", items_id);
10193 detail.push_str(&format!(
10194 " bounds=({},{},{},{}) itemsID={} ditl={}",
10195 top,
10196 left,
10197 bottom,
10198 right,
10199 items_id,
10200 if ditl_match.is_some() {
10201 "found"
10202 } else {
10203 "missing"
10204 }
10205 ));
10206 if let Some((_, ditl_data)) = ditl_match {
10207 let ditl_len = bus.get_alloc_size(ditl_data).unwrap_or(0);
10208 let items = Self::parse_ditl(bus, ditl_data, ditl_len);
10209 for (i, item) in items.iter().enumerate() {
10210 if !item.text.is_empty() {
10211 detail.push_str(&format!(
10212 "\n[TRAP] item {} (type=${:02X}): {:?}",
10213 i + 1,
10214 item.item_type,
10215 item.text
10216 ));
10217 }
10218 }
10219 }
10220 }
10221 }
10222 eprintln!("{}", detail);
10223 }
10224 bus.write_word(sp + 6, result as u16);
10225 cpu.write_reg(Register::A7, sp + 6);
10226 Ok(())
10227 }
10228
10229 (true, 0x17F) => {
10235 let sp = cpu.read_reg(Register::A7);
10236 let event_ptr = bus.read_long(sp);
10237 let (what, message, where_v, where_h, _modifiers) =
10238 Self::read_guest_event_record(bus, event_ptr);
10239 let target_dialog = self.dialog_from_window_event(what, message);
10240 let result = if let Some(dialog_ptr) = target_dialog {
10241 match what {
10242 6 | 8 => message == dialog_ptr,
10243 1 => Self::dialog_contains_screen_point(
10244 Self::dialog_screen_bounds(bus, dialog_ptr),
10245 where_v,
10246 where_h,
10247 ),
10248 _ => true,
10249 }
10250 } else {
10251 false
10252 };
10253 self.record_dialog_input_trace(
10254 "A97F",
10255 event_ptr,
10256 what,
10257 message,
10258 where_v,
10259 where_h,
10260 _modifiers,
10261 target_dialog,
10262 result,
10263 "outcome=is_dialog_event",
10264 );
10265 bus.write_word(sp + 4, if result { 0xFFFF } else { 0 });
10266 cpu.write_reg(Register::A7, sp + 4);
10267 Ok(())
10268 }
10269
10270 (true, 0x180) => {
10275 let sp = cpu.read_reg(Register::A7);
10276 let item_hit_ptr = bus.read_long(sp);
10277 let dialog_out_ptr = bus.read_long(sp + 4);
10278 let event_ptr = bus.read_long(sp + 8);
10279 let (what, message, where_v, where_h, _modifiers) =
10280 Self::read_guest_event_record(bus, event_ptr);
10281 let mut result = false;
10282 let mut trace_detail = String::from("outcome=no_dialog_target");
10283
10284 let target_dialog = self.dialog_from_window_event(what, message);
10285 if let Some(dialog_ptr) = target_dialog {
10286 let bounds = Self::dialog_screen_bounds(bus, dialog_ptr);
10287 trace_detail = format!(
10288 "bounds=({},{},{},{}) outcome=no_item",
10289 bounds.0, bounds.1, bounds.2, bounds.3
10290 );
10291 if let Some(mut items) = self.dialog_items.get(&dialog_ptr).cloned() {
10292 Self::refresh_ditl_proc_ptrs(bus, dialog_ptr, &mut items);
10293 match what {
10294 6 => {
10295 let update_rect =
10299 Self::region_handle_rect(bus, bus.read_long(dialog_ptr + 122));
10300 self.begin_update_window(bus, dialog_ptr);
10301 self.update_dialog_window_contents(
10302 bus,
10303 cpu,
10304 dialog_ptr,
10305 update_rect,
10306 );
10307 self.end_update_window(bus, dialog_ptr);
10308 trace_detail = format!(
10309 "bounds=({},{},{},{}) outcome=update_redraw",
10310 bounds.0, bounds.1, bounds.2, bounds.3
10311 );
10312 }
10313 1 if Self::dialog_contains_screen_point(bounds, where_v, where_h) => {
10314 let hit = self.dialog_item_hit_test(
10315 bus,
10316 &items,
10317 bounds,
10318 where_v,
10319 where_h,
10320 &self.dialog_popup_original_rects,
10321 dialog_ptr,
10322 );
10323 if hit > 0 {
10324 let item = &items[(hit - 1) as usize];
10325 let is_disabled = (item.item_type & 0x80) != 0;
10326 trace_detail = format!(
10327 "bounds=({},{},{},{}) item_hit={} item_type=${:02X} disabled={} outcome={}",
10328 bounds.0,
10329 bounds.1,
10330 bounds.2,
10331 bounds.3,
10332 hit,
10333 item.item_type,
10334 if is_disabled { "true" } else { "false" },
10335 if is_disabled {
10336 "disabled_item"
10337 } else {
10338 "enabled_item"
10339 },
10340 );
10341 if trace_dialog_items_enabled() {
10342 eprintln!(
10343 "[DIALOG-SELECT] mouseDown dialog=${:08X} where=({},{}) bounds=({},{},{},{}) hit={} type=${:02X} disabled={}",
10344 dialog_ptr,
10345 where_v,
10346 where_h,
10347 bounds.0,
10348 bounds.1,
10349 bounds.2,
10350 bounds.3,
10351 hit,
10352 item.item_type,
10353 is_disabled,
10354 );
10355 }
10356 if !is_disabled {
10357 self.cancel_app_owned_modal_dialog_button_tracking(
10358 bus, dialog_ptr,
10359 );
10360 if (item.item_type & 0x7F) == 16 {
10361 self.activate_dialog_edit_item(
10368 bus, cpu, dialog_ptr, &items, hit,
10369 );
10370 }
10371 if dialog_out_ptr != 0 {
10372 bus.write_long(dialog_out_ptr, dialog_ptr);
10373 }
10374 if item_hit_ptr != 0 {
10375 bus.write_word(item_hit_ptr, hit as u16);
10376 }
10377 result = true;
10378 }
10379 } else {
10380 trace_detail = format!(
10381 "bounds=({},{},{},{}) item_hit=0 outcome=no_item",
10382 bounds.0, bounds.1, bounds.2, bounds.3
10383 );
10384 if trace_dialog_items_enabled() {
10385 eprintln!(
10386 "[DIALOG-SELECT] mouseDown dialog=${:08X} where=({},{}) bounds=({},{},{},{}) hit=0",
10387 dialog_ptr,
10388 where_v,
10389 where_h,
10390 bounds.0,
10391 bounds.1,
10392 bounds.2,
10393 bounds.3,
10394 );
10395 }
10396 }
10397 }
10398 0 => {
10399 let (_edit_text, edit_item, _default_item) =
10400 Self::dialog_edit_state(bus, dialog_ptr, &items);
10401 trace_detail = format!(
10402 "bounds=({},{},{},{}) edit_item={} outcome=teidle",
10403 bounds.0, bounds.1, bounds.2, bounds.3, edit_item
10404 );
10405 if edit_item > 0 {
10406 let text_handle = bus.read_long(dialog_ptr + 160);
10412 self.textedit_idle(cpu, bus, text_handle);
10413 }
10414 }
10415 1 => {
10416 trace_detail = format!(
10417 "bounds=({},{},{},{}) item_hit=0 outcome=outside_dialog",
10418 bounds.0, bounds.1, bounds.2, bounds.3
10419 );
10420 if trace_dialog_items_enabled() {
10421 eprintln!(
10422 "[DIALOG-SELECT] mouseDown dialog=${:08X} where=({},{}) outside bounds=({},{},{},{})",
10423 dialog_ptr,
10424 where_v,
10425 where_h,
10426 bounds.0,
10427 bounds.1,
10428 bounds.2,
10429 bounds.3,
10430 );
10431 }
10432 }
10433 3 | 5 => {
10434 let (_edit_text, edit_item, _default_item) =
10435 Self::dialog_edit_state(bus, dialog_ptr, &items);
10436 trace_detail = format!(
10437 "bounds=({},{},{},{}) edit_item={} outcome=no_enabled_edittext",
10438 bounds.0, bounds.1, bounds.2, bounds.3, edit_item
10439 );
10440 if edit_item > 0 {
10441 if let Some(item) = items.get((edit_item - 1) as usize) {
10445 let item_type = item.item_type;
10446 let base_type = item_type & 0x7F;
10447 let is_disabled = (item_type & 0x80) != 0;
10448 trace_detail = format!(
10449 "bounds=({},{},{},{}) edit_item={} item_type=${:02X} disabled={} outcome={}",
10450 bounds.0,
10451 bounds.1,
10452 bounds.2,
10453 bounds.3,
10454 edit_item,
10455 item_type,
10456 if is_disabled { "true" } else { "false" },
10457 if base_type == 16 && !is_disabled {
10458 "enabled_edittext"
10459 } else {
10460 "no_enabled_edittext"
10461 },
10462 );
10463 if base_type == 16 && !is_disabled {
10464 let char_code = (message & 0xFF) as u8;
10465 self.apply_dialog_select_key_to_edit_item(
10469 bus, dialog_ptr, &mut items, edit_item, char_code,
10470 );
10471 if dialog_out_ptr != 0 {
10472 bus.write_long(dialog_out_ptr, dialog_ptr);
10473 }
10474 if item_hit_ptr != 0 {
10475 bus.write_word(item_hit_ptr, edit_item as u16);
10476 }
10477 result = true;
10478 }
10479 }
10480 }
10481 }
10482 _ => {}
10483 }
10484 self.dialog_items.insert(dialog_ptr, items);
10485 } else {
10486 trace_detail = format!(
10487 "bounds=({},{},{},{}) outcome=no_dialog_items",
10488 bounds.0, bounds.1, bounds.2, bounds.3
10489 );
10490 if trace_dialog_items_enabled() {
10491 eprintln!(
10492 "[DIALOG-SELECT] event what={} dialog=${:08X} has no dialog_items",
10493 what, dialog_ptr
10494 );
10495 }
10496 }
10497 } else if trace_dialog_items_enabled() {
10498 eprintln!(
10499 "[DIALOG-SELECT] event what={} message=${:08X} where=({},{}) has no dialog target front=${:08X}",
10500 what, message, where_v, where_h, self.front_window
10501 );
10502 }
10503 self.record_dialog_input_trace(
10504 "A980",
10505 event_ptr,
10506 what,
10507 message,
10508 where_v,
10509 where_h,
10510 _modifiers,
10511 target_dialog,
10512 result,
10513 &trace_detail,
10514 );
10515
10516 bus.write_word(sp + 12, if result { 0xFFFF } else { 0 });
10517 cpu.write_reg(Register::A7, sp + 12);
10518 Ok(())
10519 }
10520
10521 (true, 0x181) => {
10527 let sp = cpu.read_reg(Register::A7);
10528 let dialog_ptr = bus.read_long(sp);
10529 if let Some(mut items) = self.dialog_items.get(&dialog_ptr).cloned() {
10530 Self::refresh_ditl_proc_ptrs(bus, dialog_ptr, &mut items);
10531 let bounds = Self::dialog_screen_bounds(bus, dialog_ptr);
10532 let proc_id = self.dialog_window_proc_id(bus, dialog_ptr);
10533 let (edit_text, edit_item, default_item) =
10534 Self::dialog_edit_state(bus, dialog_ptr, &items);
10535 self.dialog_initial_draw_deferred.remove(&dialog_ptr);
10536 self.draw_dialog_preserving_user_items(
10537 bus,
10538 bounds,
10539 proc_id,
10540 "",
10541 &items,
10542 default_item,
10543 &edit_text,
10544 edit_item,
10545 false,
10546 dialog_ptr,
10547 true,
10548 false,
10549 false,
10550 );
10551 self.dialog_items.insert(dialog_ptr, items);
10552 self.queue_modeless_dialog_draw_procs_intersecting(bus, dialog_ptr, None);
10558 }
10559 cpu.write_reg(Register::A7, sp + 4);
10560 Ok(())
10561 }
10562
10563 (true, 0x183) => {
10592 let sp = cpu.read_reg(Register::A7);
10593 let requested_dialog_ptr = bus.read_long(sp);
10594 let dialog_ptr =
10595 self.resolve_dispos_dialog_ptr_after_modal_button_hit(requested_dialog_ptr);
10596 eprintln!("[TRAP] DisposDialog(${:08X})", requested_dialog_ptr);
10597 self.close_dialog_window(bus, cpu, dialog_ptr, true);
10598 self.capture_gui_frame(bus, &format!("dispos_dialog_{:08X}", dialog_ptr));
10599 cpu.write_reg(Register::A7, sp + 4);
10600 Ok(())
10601 }
10602
10603 (true, 0x18B) => {
10610 let sp = cpu.read_reg(Register::A7);
10611 let trap_pc = cpu.read_reg(Register::PC).wrapping_sub(2);
10612 let offsets: [u32; 4] = [12, 8, 4, 0];
10619 for (i, &off) in offsets.iter().enumerate() {
10620 let ptr = bus.read_long(sp + off);
10621 if ptr == 0 {
10622 continue;
10623 }
10624 let s = bus.read_pstring(ptr);
10625 self.param_text[i] = s.clone();
10626 use crate::memory::globals::addr;
10631 let data_len = 1u32 + s.len() as u32;
10632 let data_ptr = bus.alloc(data_len);
10633 if data_ptr != 0 {
10634 bus.write_byte(data_ptr, s.len() as u8);
10635 for (j, &b) in s.iter().enumerate() {
10636 bus.write_byte(data_ptr + 1 + j as u32, b);
10637 }
10638 let handle = bus.alloc(4);
10639 if handle != 0 {
10640 bus.write_long(handle, data_ptr);
10641 bus.write_long(addr::DA_STRINGS + i as u32 * 4, handle);
10642 }
10643 }
10644 }
10645 eprintln!(
10646 "[TRAP] ParamText pc=${:08X} ^0=\"{}\" ^1=\"{}\" ^2=\"{}\" ^3=\"{}\"",
10647 trap_pc,
10648 String::from_utf8_lossy(&self.param_text[0]),
10649 String::from_utf8_lossy(&self.param_text[1]),
10650 String::from_utf8_lossy(&self.param_text[2]),
10651 String::from_utf8_lossy(&self.param_text[3]),
10652 );
10653 cpu.write_reg(Register::A7, sp + 16);
10654 Ok(())
10655 }
10656
10657 (true, 0x18D) => {
10664 let sp = cpu.read_reg(Register::A7);
10665 let box_ptr = bus.read_long(sp);
10666 let item_handle_ptr = bus.read_long(sp + 4);
10667 let type_ptr = bus.read_long(sp + 8);
10668 let item_no = bus.read_word(sp + 12) as i16;
10669 let dialog_ptr = bus.read_long(sp + 14);
10670
10671 let found = self.dialog_items.get(&dialog_ptr).and_then(|items| {
10673 if item_no > 0 && (item_no as usize) <= items.len() {
10674 Some(&items[(item_no - 1) as usize])
10675 } else {
10676 None
10677 }
10678 });
10679
10680 if let Some(item) = found {
10681 if trace_dialog_items_enabled() && (item.item_type & 0x7F) == 0 {
10682 eprintln!(
10683 "[DIALOG-ITEM] GetDItem pc=${:08X} dialog=${:08X} item={} type={} proc=${:08X} out_type=${:08X} out_item=${:08X} out_box=${:08X} rect=({},{},{},{})",
10684 cpu.read_reg(Register::PC),
10685 dialog_ptr,
10686 item_no,
10687 item.item_type,
10688 item.proc_ptr,
10689 type_ptr,
10690 item_handle_ptr,
10691 box_ptr,
10692 item.rect.0,
10693 item.rect.1,
10694 item.rect.2,
10695 item.rect.3,
10696 );
10697 }
10698 if type_ptr != 0 {
10699 bus.write_word(type_ptr, item.item_type as u16);
10700 }
10701 if item_handle_ptr != 0 {
10702 let current_handle = Self::dialog_item_handle(bus, dialog_ptr, item_no);
10703 let base_type = item.item_type & 0x7F;
10704 if current_handle != 0 || !(4..=6).contains(&base_type) {
10705 bus.write_long(item_handle_ptr, current_handle);
10706 } else {
10707 let title_len = item.text.len().min(255);
10720 let ctrl_rec = bus.alloc(42 + title_len as u32);
10721 bus.write_long(ctrl_rec, 0); bus.write_long(ctrl_rec + 4, dialog_ptr); bus.write_word(ctrl_rec + 8, item.rect.0 as u16);
10726 bus.write_word(ctrl_rec + 10, item.rect.1 as u16);
10727 bus.write_word(ctrl_rec + 12, item.rect.2 as u16);
10728 bus.write_word(ctrl_rec + 14, item.rect.3 as u16);
10729 bus.write_byte(ctrl_rec + 16, 255); bus.write_byte(ctrl_rec + 17, 0); let value = self
10732 .dialog_control_values
10733 .get(&(dialog_ptr, item_no))
10734 .copied()
10735 .unwrap_or(0);
10736 bus.write_word(ctrl_rec + 18, value as u16);
10737 bus.write_word(ctrl_rec + 20, 0); bus.write_word(ctrl_rec + 22, 1); bus.write_byte(ctrl_rec + 40, title_len as u8);
10741 for (i, &ch) in item.text.as_bytes().iter().take(title_len).enumerate()
10742 {
10743 bus.write_byte(ctrl_rec + 41 + i as u32, ch);
10744 }
10745 let proc_id: i16 = match base_type {
10747 4 => 0, 5 => 1, 6 => 2, _ => 0,
10751 };
10752 self.control_proc_ids.insert(ctrl_rec, proc_id);
10753 let handle = bus.alloc(4);
10754 bus.write_long(handle, ctrl_rec);
10755 bus.write_long(item_handle_ptr, handle);
10756 self.dialog_control_handles
10757 .insert(handle, (dialog_ptr, item_no));
10758 Self::set_dialog_item_handle(bus, dialog_ptr, item_no, handle);
10760 }
10761 }
10762 if box_ptr != 0 {
10763 bus.write_word(box_ptr, item.rect.0 as u16);
10764 bus.write_word(box_ptr + 2, item.rect.1 as u16);
10765 bus.write_word(box_ptr + 4, item.rect.2 as u16);
10766 bus.write_word(box_ptr + 6, item.rect.3 as u16);
10767 }
10768 if let Some(menu_id) = self.last_inserted_menu_id.take() {
10774 let enabled_user_item =
10775 (item.item_type & 0x7F) == 0 && (item.item_type & 0x80) == 0;
10776 if enabled_user_item {
10777 if item.proc_ptr != 0 {
10778 self.dialog_item_popup_menus
10779 .insert((dialog_ptr, item_no), menu_id);
10780 self.dialog_popup_original_rects
10781 .insert((dialog_ptr, item_no), item.rect);
10782 self.pending_dialog_popup_menu = None;
10783 } else {
10784 self.pending_dialog_popup_menu = Some(PendingDialogPopupMenu {
10785 dialog_ptr,
10786 item_no,
10787 menu_id,
10788 rect: item.rect,
10789 });
10790 }
10791 } else {
10792 self.pending_dialog_popup_menu = None;
10793 }
10794 }
10795 } else {
10796 if type_ptr != 0 {
10797 bus.write_word(type_ptr, 0);
10798 }
10799 if item_handle_ptr != 0 {
10800 bus.write_long(item_handle_ptr, 0);
10801 }
10802 if box_ptr != 0 {
10803 bus.write_long(box_ptr, 0);
10804 bus.write_long(box_ptr + 4, 0);
10805 }
10806 }
10807 cpu.write_reg(Register::A7, sp + 18);
10808 Ok(())
10809 }
10810
10811 (true, 0x18E) => {
10826 let sp = cpu.read_reg(Register::A7);
10827 let box_ptr = bus.read_long(sp);
10828 let item_handle = bus.read_long(sp + 4);
10829 let item_type = bus.read_word(sp + 8) as u8;
10830 let item_no = bus.read_word(sp + 10) as i16;
10831 let dialog_ptr = bus.read_long(sp + 12);
10832 let (box_top, box_left, box_bottom, box_right) = if box_ptr != 0 {
10833 (
10834 bus.read_word(box_ptr) as i16,
10835 bus.read_word(box_ptr + 2) as i16,
10836 bus.read_word(box_ptr + 4) as i16,
10837 bus.read_word(box_ptr + 6) as i16,
10838 )
10839 } else {
10840 (0, 0, 0, 0)
10841 };
10842
10843 let base_type = item_type & 0x7F;
10844 let previous_handle = Self::dialog_item_handle(bus, dialog_ptr, item_no);
10845 let previous_item = self
10846 .dialog_items
10847 .get(&dialog_ptr)
10848 .and_then(|items| {
10849 (item_no > 0)
10850 .then(|| items.get((item_no - 1) as usize))
10851 .flatten()
10852 })
10853 .cloned();
10854 if trace_dialog_items_enabled() {
10855 eprintln!(
10856 "[DIALOG-ITEM] SetDItem pc=${:08X} sp=${:08X} rawbytes={:02X?} dialog=${:08X} item={} type={} proc=${:08X} rect=({},{},{},{})",
10857 cpu.read_reg(Register::PC),
10858 sp,
10859 (0..24u32).map(|i| bus.read_byte(sp + i)).collect::<Vec<u8>>(),
10860 dialog_ptr,
10861 item_no,
10862 item_type,
10863 item_handle,
10864 box_top,
10865 box_left,
10866 box_bottom,
10867 box_right,
10868 );
10869 }
10870
10871 if previous_handle != 0 {
10872 self.dialog_item_handles.remove(&previous_handle);
10873 self.dialog_control_handles.remove(&previous_handle);
10874 }
10875 if let Some(item_handle_addr) =
10876 Self::dialog_item_handle_addr(bus, dialog_ptr, item_no)
10877 {
10878 bus.write_long(item_handle_addr, item_handle);
10879 bus.write_word(item_handle_addr + 4, box_top as u16);
10880 bus.write_word(item_handle_addr + 6, box_left as u16);
10881 bus.write_word(item_handle_addr + 8, box_bottom as u16);
10882 bus.write_word(item_handle_addr + 10, box_right as u16);
10883 bus.write_byte(item_handle_addr + 12, item_type);
10884 }
10885 if let Some(items) = self.dialog_items.get_mut(&dialog_ptr) {
10886 if item_no > 0 && (item_no as usize) <= items.len() {
10887 let item = &mut items[(item_no - 1) as usize];
10888 item.item_type = item_type;
10889 item.rect = (box_top, box_left, box_bottom, box_right);
10890 if base_type == 0 {
10891 item.proc_ptr = item_handle;
10894 } else if base_type == 8 || base_type == 16 {
10895 item.text = Self::text_item_string_from_handle(bus, item_handle);
10896 }
10897 }
10898 }
10899
10900 if let Some(pending) = self.pending_dialog_popup_menu {
10901 if pending.dialog_ptr == dialog_ptr && pending.item_no == item_no {
10902 if base_type == 0 && (item_type & 0x80) == 0 && item_handle != 0 {
10903 self.dialog_item_popup_menus
10904 .insert((dialog_ptr, item_no), pending.menu_id);
10905 self.dialog_popup_original_rects
10906 .insert((dialog_ptr, item_no), pending.rect);
10907 }
10908 self.pending_dialog_popup_menu = None;
10909 }
10910 }
10911
10912 if let Some(previous_item) = previous_item {
10913 let key = (dialog_ptr, item_no);
10914 let previous_base_type = previous_item.item_type & 0x7F;
10915 let previous_enabled_user_item =
10916 previous_base_type == 0 && (previous_item.item_type & 0x80) == 0;
10917 let current_enabled_user_item = base_type == 0 && (item_type & 0x80) == 0;
10918 let old_width = previous_item.rect.3 - previous_item.rect.1;
10919 let old_height = previous_item.rect.2 - previous_item.rect.0;
10920 let new_width = box_right - box_left;
10921 let new_height = box_bottom - box_top;
10922 let narrowed_to_popup_indicator = previous_enabled_user_item
10923 && current_enabled_user_item
10924 && item_handle == 0
10925 && old_width >= 40
10926 && new_width > 0
10927 && new_width <= 24
10928 && old_height > 0
10929 && new_height > 0
10930 && (old_height - new_height).abs() <= 4;
10931 if narrowed_to_popup_indicator {
10932 self.dialog_popup_original_rects
10933 .entry(key)
10934 .or_insert(previous_item.rect);
10935 self.dialog_popup_candidate_items.insert(key);
10936 } else if !current_enabled_user_item {
10937 self.dialog_popup_candidate_items.remove(&key);
10938 if !self.dialog_item_popup_menus.contains_key(&key) {
10939 self.dialog_popup_original_rects.remove(&key);
10940 }
10941 }
10942 }
10943
10944 if base_type == 8 || base_type == 16 {
10945 if item_handle != 0 {
10946 self.dialog_item_handles
10947 .insert(item_handle, (dialog_ptr, (item_no - 1) as usize));
10948 }
10949 } else if (base_type == 4 || base_type == 5 || base_type == 6 || base_type == 7)
10950 && item_handle != 0
10951 {
10952 self.dialog_control_handles
10953 .insert(item_handle, (dialog_ptr, item_no));
10954 let ctrl_ptr = bus.read_long(item_handle);
10955 if ctrl_ptr != 0 {
10956 bus.write_word(ctrl_ptr + 8, box_top as u16);
10957 bus.write_word(ctrl_ptr + 10, box_left as u16);
10958 bus.write_word(ctrl_ptr + 12, box_bottom as u16);
10959 bus.write_word(ctrl_ptr + 14, box_right as u16);
10960 self.dialog_control_values
10961 .insert((dialog_ptr, item_no), bus.read_word(ctrl_ptr + 18) as i16);
10962 }
10963 }
10964
10965 if let Some(ref mut tracking) = self.dialog_tracking {
10967 if tracking.dialog_ptr == dialog_ptr
10968 && item_no > 0
10969 && (item_no as usize) <= tracking.items.len()
10970 {
10971 let item = &mut tracking.items[(item_no - 1) as usize];
10972 item.item_type = item_type;
10973 item.rect = (box_top, box_left, box_bottom, box_right);
10974 if base_type == 0 {
10975 item.proc_ptr = item_handle;
10976 } else if base_type == 8 || base_type == 16 {
10977 item.text = Self::text_item_string_from_handle(bus, item_handle);
10978 if tracking.edit_item == item_no {
10979 tracking.edit_text = item.text.clone();
10980 }
10981 }
10982 }
10983 }
10984
10985 cpu.write_reg(Register::A7, sp + 16);
10986 Ok(())
10987 }
10988
10989 (true, 0x191) => {
10995 if let Some(ref tracking) = self.dialog_tracking {
10997 if !tracking.draw_procs_done {
10998 return Some(Ok(()));
10999 }
11000 }
11001 if let Some(ref tracking) = self.dialog_tracking {
11005 if !tracking.rendered_pixels_final {
11006 let bounds = tracking.bounds;
11007 let items = tracking.items.clone();
11008 let default_item = tracking.default_item;
11009 let edit_text = tracking.edit_text.clone();
11010 let edit_item = tracking.edit_item;
11011 let dialog_ptr = tracking.dialog_ptr;
11012 let popup_draws = tracking.popup_draws.clone();
11013 if !tracking.game_managed {
11014 if self.front_window == dialog_ptr {
11015 self.blit_window_to_screen(bus);
11016 }
11017 self.redraw_standard_dialog_items(
11018 bus,
11019 bounds,
11020 &items,
11021 default_item,
11022 &edit_text,
11023 edit_item,
11024 dialog_ptr,
11025 );
11026 }
11027 self.redraw_dialog_popup_controls(bus, &popup_draws);
11028 let rendered = self.save_dialog_pixels(bus, bounds);
11029 let t = self.dialog_tracking.as_mut().unwrap();
11030 t.rendered_pixels = rendered;
11031 t.rendered_pixels_final = true;
11032 }
11033 }
11034
11035 if let Some(ref tracking) = self.dialog_tracking {
11036 if tracking.active_button.is_some() {
11037 self.handle_dialog_button_tracking(bus);
11038 return Some(Ok(()));
11039 }
11040
11041 if tracking.active_popup.is_some() {
11042 self.handle_dialog_popup_tracking(cpu, bus);
11043 return Some(Ok(()));
11044 }
11045
11046 if tracking.active_user_item.is_some() {
11047 self.handle_dialog_user_item_tracking(cpu, bus);
11048 return Some(Ok(()));
11049 }
11050
11051 if tracking.filter_proc == 0
11057 && tracking.flash_remaining == 0
11058 && tracking.active_button.is_none()
11059 && tracking.active_popup.is_none()
11060 && tracking.active_user_item.is_none()
11061 && self.event_queue.is_empty()
11062 {
11063 return Some(Ok(()));
11064 }
11065 let dialog_ptr = tracking.dialog_ptr;
11067 let bounds = tracking.bounds;
11068 let item_hit_ptr = tracking.item_hit_ptr;
11069 let stack_ptr = tracking.stack_ptr;
11070 let filter_proc = tracking.filter_proc;
11071 let flash_remaining = tracking.flash_remaining;
11072 let mut pending_event = None;
11075
11076 let mut waiting_for_filter_proc_event = false;
11081 if filter_proc != 0 {
11082 let result_addr = self.dialog_filter_result_addr;
11083 let tracking_dialog_ptr = tracking.dialog_ptr;
11084 let filter_result_word = if result_addr != 0 {
11085 bus.read_word(result_addr)
11086 } else {
11087 0
11088 };
11089 let filter_returned_true = if result_addr != 0 {
11090 (filter_result_word & 0x0100) != 0
11095 } else {
11096 false
11097 };
11098
11099 let mut hit = 0i16;
11100 if filter_returned_true && item_hit_ptr != 0 {
11101 hit = bus.read_word(item_hit_ptr) as i16;
11102 }
11103 let filter_event = self
11104 .dialog_tracking
11105 .as_mut()
11106 .and_then(|t| t.last_filter_event.take());
11107 if trace_dialog_filter_enabled() {
11108 eprintln!(
11109 "[DIALOG-FILTER] result dialog=${:08X} result_word=${:04X} returned_true={} item_hit={} item_hit_ptr=${:08X}",
11110 tracking_dialog_ptr,
11111 filter_result_word,
11112 filter_returned_true,
11113 hit,
11114 item_hit_ptr
11115 );
11116 }
11117
11118 if hit > 0 || filter_returned_true {
11119 let handled_mouse_down =
11120 filter_event.as_ref().is_some_and(|e| e.what == 1);
11121 let (dialog_ptr, edit_item, edit_text, items) = {
11122 let tracking = self.dialog_tracking.as_mut().unwrap();
11123 Self::sync_tracking_active_edit_item(tracking);
11124 (
11125 tracking.dialog_ptr,
11126 tracking.edit_item,
11127 tracking.edit_text.clone(),
11128 tracking.items.clone(),
11129 )
11130 };
11131 let keep_dialog_visible = items
11132 .get((hit - 1) as usize)
11133 .map(|item| (item.item_type & 0x7F) != 4)
11134 .unwrap_or(true);
11135 let item_type =
11136 items.get((hit - 1) as usize).map(|item| item.item_type);
11137 self.flush_dialog_edit_item_texts(
11138 bus, dialog_ptr, &items, edit_item, &edit_text,
11139 );
11140 let saved = self.dialog_tracking.take().unwrap();
11142 let saved_dialog_ptr = saved.dialog_ptr;
11143 let saved_bounds = saved.bounds;
11144 if keep_dialog_visible {
11145 self.persist_visible_dialog_snapshot(bus, &saved);
11146 }
11147 self.dialog_saved_pixels
11148 .insert(saved_dialog_ptr, saved.saved_pixels);
11149 if hit > 0
11150 && item_type.is_some_and(|ty| (ty & 0x7F) == 4 && (ty & 0x80) == 0)
11151 {
11152 self.pending_modal_button_dispose_dialog = Some(saved_dialog_ptr);
11153 }
11154 if handled_mouse_down {
11155 self.consume_dialog_mouse_up();
11156 }
11157 if trace_dialog_filter_enabled() {
11158 let actual_hit = bus.read_word(item_hit_ptr) as i16;
11159 eprintln!(
11160 "[DIALOG-FILTER] ModalDialog returning: filter hit={} actual_item_hit_ptr=${:08X} actual_hit={} handled_mouseDown={} stack_ptr=${:08X} queue_len={}",
11161 hit, item_hit_ptr, actual_hit, handled_mouse_down, stack_ptr, self.event_queue.len()
11162 );
11163 }
11164 let outcome = if hit > 0 {
11165 if keep_dialog_visible {
11166 "filter_item_hit_retained"
11167 } else {
11168 "filter_item_hit_dismissed"
11169 }
11170 } else {
11171 "filter_true_zero_hit"
11172 };
11173 self.record_modal_dialog_filter_input_trace(
11174 "filter_result",
11175 saved_dialog_ptr,
11176 saved_bounds,
11177 filter_proc,
11178 filter_event.as_ref(),
11179 hit,
11180 item_type,
11181 handled_mouse_down,
11182 keep_dialog_visible,
11183 "returned",
11184 outcome,
11185 );
11186 cpu.write_reg(Register::A7, stack_ptr + 8);
11187 return Some(Ok(()));
11188 }
11189 pending_event = filter_event;
11190 if let Some(ref event) = pending_event {
11191 self.record_modal_dialog_filter_input_trace(
11192 "filter_result",
11193 tracking_dialog_ptr,
11194 bounds,
11195 filter_proc,
11196 Some(event),
11197 0,
11198 None,
11199 false,
11200 true,
11201 "passed",
11202 "filter_declined",
11203 );
11204 }
11205 waiting_for_filter_proc_event = pending_event.is_none();
11206 }
11207
11208 if flash_remaining > 0 {
11209 let flash_item = self.dialog_tracking.as_ref().unwrap().flash_item;
11211 let (remaining, button_draw) = {
11212 let t = self.dialog_tracking.as_mut().unwrap();
11213 if t.flash_delay > 0 {
11214 t.flash_delay -= 1;
11215 return Some(Ok(()));
11216 }
11217 t.flash_remaining -= 1;
11218 t.flash_delay = 3;
11219 let remaining = t.flash_remaining;
11220 let button_draw = if remaining > 0
11221 && flash_item > 0
11222 && (flash_item as usize) <= t.items.len()
11223 {
11224 let item = &t.items[(flash_item - 1) as usize];
11225 Some((
11226 Self::dialog_item_screen_rect(bounds, item.rect),
11227 item.text.clone(),
11228 flash_item == t.default_item,
11229 remaining % 2 == 0,
11230 ))
11231 } else {
11232 None
11233 };
11234 (remaining, button_draw)
11235 };
11236
11237 if let Some((screen_rect, title, is_default, highlighted)) = button_draw {
11238 self.draw_dialog_button_highlight_state(
11240 bus,
11241 screen_rect,
11242 &title,
11243 is_default,
11244 highlighted,
11245 );
11246 }
11247
11248 if remaining == 0 {
11249 let (dialog_ptr, edit_item, edit_text, items) = {
11252 let tracking = self.dialog_tracking.as_mut().unwrap();
11253 Self::sync_tracking_active_edit_item(tracking);
11254 (
11255 tracking.dialog_ptr,
11256 tracking.edit_item,
11257 tracking.edit_text.clone(),
11258 tracking.items.clone(),
11259 )
11260 };
11261 self.flush_dialog_edit_item_texts(
11262 bus, dialog_ptr, &items, edit_item, &edit_text,
11263 );
11264
11265 let saved = self.dialog_tracking.take().unwrap();
11266 let saved_dialog_ptr = saved.dialog_ptr;
11267 let saved_bounds = saved.bounds;
11268 let item_type = saved
11269 .items
11270 .get(flash_item.saturating_sub(1) as usize)
11271 .map(|item| item.item_type);
11272 self.persist_visible_dialog_snapshot(bus, &saved);
11273 self.dialog_saved_pixels
11274 .insert(saved_dialog_ptr, saved.saved_pixels);
11275 self.pending_modal_button_dispose_dialog = Some(saved_dialog_ptr);
11276
11277 self.consume_dialog_mouse_up();
11282
11283 if item_hit_ptr != 0 {
11284 bus.write_word(item_hit_ptr, flash_item as u16);
11285 }
11286 self.record_modal_dialog_input_trace(
11287 "finish",
11288 saved_dialog_ptr,
11289 saved_bounds,
11290 flash_item,
11291 item_type,
11292 None,
11293 "returned",
11294 "button_item_hit",
11295 );
11296 cpu.write_reg(Register::A7, stack_ptr + 8);
11297 }
11298 return Some(Ok(()));
11299 }
11300
11301 if waiting_for_filter_proc_event {
11307 return Some(Ok(()));
11308 }
11309
11310 let event = if let Some(e) = pending_event {
11311 Some(e)
11312 } else if !self.event_queue.is_empty() {
11313 let mut event = None;
11315 while let Some(e) = self.event_queue.pop_front() {
11316 match e.what {
11317 1 | 2 | 3 | 6 => {
11318 event = Some(e);
11319 break;
11320 }
11321 _ => {} }
11323 }
11324 event
11325 } else {
11326 None
11327 };
11328
11329 if let Some(mut e) = event {
11330 if e.what == 0 && self.mouse_button {
11331 e.what = 1;
11332 e.where_v = self.mouse_pos.0;
11333 e.where_h = self.mouse_pos.1;
11334 }
11335 match e.what {
11336 6 => {
11341 self.begin_update_window(bus, dialog_ptr);
11347 self.set_current_port_state(bus, cpu, dialog_ptr, None);
11348 let previous_rendered = self
11349 .dialog_tracking
11350 .as_ref()
11351 .filter(|t| t.rendered_pixels_final)
11352 .map(|t| t.rendered_pixels.clone())
11353 .unwrap_or_default();
11354 if !previous_rendered.is_empty() {
11355 self.restore_dialog_pixels(bus, bounds, &previous_rendered);
11356 }
11357 if let Some(ref t) = self.dialog_tracking {
11358 if !t.game_managed {
11359 self.redraw_standard_dialog_items(
11360 bus,
11361 bounds,
11362 &t.items,
11363 t.default_item,
11364 &t.edit_text,
11365 t.edit_item,
11366 t.dialog_ptr,
11367 );
11368 }
11369 let popup_draws = t.popup_draws.clone();
11370 self.redraw_dialog_popup_controls(bus, &popup_draws);
11371 }
11372 let rendered = self.save_dialog_pixels(bus, bounds);
11373 let t = self.dialog_tracking.as_mut().unwrap();
11374 t.rendered_pixels = rendered;
11375 t.rendered_pixels_final = true;
11376 self.end_update_window(bus, dialog_ptr);
11377 }
11378 1 => {
11380 let items_clone: Vec<DialogItem> = self
11383 .dialog_tracking
11384 .as_ref()
11385 .map(|t| t.items.clone())
11386 .unwrap_or_default();
11387 let mut hit = self.dialog_item_hit_test(
11388 bus,
11389 &items_clone,
11390 bounds,
11391 e.where_v,
11392 e.where_h,
11393 &self.dialog_popup_original_rects,
11394 dialog_ptr,
11395 );
11396 if hit <= 0 {
11397 hit = Self::dialog_button_hit_test(
11398 &items_clone,
11399 bounds,
11400 e.where_v,
11401 e.where_h,
11402 );
11403 }
11404 if hit > 0 {
11405 let item = &items_clone[(hit - 1) as usize];
11406 let base_type = item.item_type & 0x7F;
11407 let is_disabled = (item.item_type & 0x80) != 0;
11408
11409 if !is_disabled {
11410 match base_type {
11411 4 => {
11413 let (abs_top, abs_left, abs_bottom, abs_right) =
11414 Self::dialog_item_screen_rect(
11415 bounds, item.rect,
11416 );
11417 let is_default = hit
11418 == self
11419 .dialog_tracking
11420 .as_ref()
11421 .map(|tracking| tracking.default_item)
11422 .unwrap_or(0);
11423 self.draw_dialog_button_highlight_state(
11424 bus,
11425 (abs_top, abs_left, abs_bottom, abs_right),
11426 &item.text,
11427 is_default,
11428 true,
11429 );
11430 if self.mouse_button {
11431 let t = self.dialog_tracking.as_mut().unwrap();
11432 t.active_button = Some(
11433 super::dispatch::DialogButtonTrackingState {
11434 item_no: hit,
11435 rect: item.rect,
11436 title: item.text.clone(),
11437 is_default,
11438 highlighted: true,
11439 },
11440 );
11441 self.record_modal_dialog_input_trace(
11442 "mouse_down",
11443 dialog_ptr,
11444 bounds,
11445 hit,
11446 Some(item.item_type),
11447 Some(true),
11448 "pending",
11449 "button_tracking_started",
11450 );
11451 } else {
11452 self.start_dialog_button_flash(
11453 bus, bounds, hit, item.rect, &item.text,
11454 is_default, true,
11455 );
11456 self.record_modal_dialog_input_trace(
11457 "mouse_down",
11458 dialog_ptr,
11459 bounds,
11460 hit,
11461 Some(item.item_type),
11462 Some(true),
11463 "pending",
11464 "start_flash",
11465 );
11466 }
11467 }
11468 5 => {
11473 let (dlg_ptr, edit_item, edit_text, items) = {
11474 let tracking =
11475 self.dialog_tracking.as_mut().unwrap();
11476 Self::sync_tracking_active_edit_item(tracking);
11477 (
11478 tracking.dialog_ptr,
11479 tracking.edit_item,
11480 tracking.edit_text.clone(),
11481 tracking.items.clone(),
11482 )
11483 };
11484 self.flush_dialog_edit_item_texts(
11485 bus, dlg_ptr, &items, edit_item, &edit_text,
11486 );
11487 let saved = self.dialog_tracking.take().unwrap();
11489 let saved_dialog_ptr = saved.dialog_ptr;
11490 let saved_bounds = saved.bounds;
11491 self.persist_visible_dialog_snapshot(bus, &saved);
11492 self.dialog_saved_pixels
11493 .insert(saved_dialog_ptr, saved.saved_pixels);
11494 self.consume_dialog_mouse_up();
11495 if item_hit_ptr != 0 {
11497 bus.write_word(item_hit_ptr, hit as u16);
11498 }
11499 self.record_modal_dialog_input_trace(
11500 "mouse_down",
11501 saved_dialog_ptr,
11502 saved_bounds,
11503 hit,
11504 Some(item.item_type),
11505 None,
11506 "returned",
11507 "checkbox_item_hit_retained",
11508 );
11509 cpu.write_reg(Register::A7, stack_ptr + 8);
11510 }
11511 16 => {
11513 let (dlg_ptr, edit_item, edit_text, items) = {
11514 let tracking =
11515 self.dialog_tracking.as_mut().unwrap();
11516 Self::sync_tracking_active_edit_item(tracking);
11517 tracking.edit_item = hit;
11518 tracking.edit_text = tracking
11519 .items
11520 .get((hit - 1) as usize)
11521 .map(|item| item.text.clone())
11522 .unwrap_or_default();
11523 tracking.edit_text_modified = false;
11524 (
11525 tracking.dialog_ptr,
11526 tracking.edit_item,
11527 tracking.edit_text.clone(),
11528 tracking.items.clone(),
11529 )
11530 };
11531 self.activate_dialog_edit_item(
11538 bus, cpu, dlg_ptr, &items, edit_item,
11539 );
11540 self.flush_dialog_edit_item_texts(
11541 bus, dlg_ptr, &items, edit_item, &edit_text,
11542 );
11543 let saved = self.dialog_tracking.take().unwrap();
11544 let saved_dialog_ptr = saved.dialog_ptr;
11545 let saved_bounds = saved.bounds;
11546 self.persist_visible_dialog_snapshot(bus, &saved);
11547 self.dialog_saved_pixels
11548 .insert(saved_dialog_ptr, saved.saved_pixels);
11549 self.consume_dialog_mouse_up();
11550 if item_hit_ptr != 0 {
11551 bus.write_word(item_hit_ptr, hit as u16);
11552 }
11553 self.record_modal_dialog_input_trace(
11554 "mouse_down",
11555 saved_dialog_ptr,
11556 saved_bounds,
11557 hit,
11558 Some(item.item_type),
11559 None,
11560 "returned",
11561 "edittext_item_hit_retained",
11562 );
11563 cpu.write_reg(Register::A7, stack_ptr + 8);
11564 }
11565 7 if self.begin_dialog_popup_tracking(
11569 bus, dialog_ptr, hit,
11570 ) => {}
11571 0 if self
11579 .dialog_item_popup_menus
11580 .contains_key(&(dialog_ptr, hit))
11581 || self
11582 .dialog_popup_candidate_items
11583 .contains(&(dialog_ptr, hit)) =>
11584 {
11585 let (dlg_ptr, edit_item, edit_text, items) = {
11586 let tracking =
11587 self.dialog_tracking.as_mut().unwrap();
11588 Self::sync_tracking_active_edit_item(tracking);
11589 (
11590 tracking.dialog_ptr,
11591 tracking.edit_item,
11592 tracking.edit_text.clone(),
11593 tracking.items.clone(),
11594 )
11595 };
11596 self.flush_dialog_edit_item_texts(
11597 bus, dlg_ptr, &items, edit_item, &edit_text,
11598 );
11599 let saved = self.dialog_tracking.take().unwrap();
11600 self.persist_visible_dialog_snapshot(bus, &saved);
11601 self.dialog_saved_pixels
11602 .insert(saved.dialog_ptr, saved.saved_pixels);
11603 self.consume_dialog_mouse_up();
11604 if item_hit_ptr != 0 {
11605 bus.write_word(item_hit_ptr, hit as u16);
11606 }
11607 cpu.write_reg(Register::A7, stack_ptr + 8);
11608 }
11609 0 if self.dialog_tracking.as_ref().is_some_and(
11623 |tracking| {
11624 self.is_plain_modal_user_item(
11625 tracking, hit, item,
11626 )
11627 },
11628 ) && self.mouse_button =>
11629 {
11630 let (dlg_ptr, edit_item, edit_text, items) = {
11631 let tracking =
11632 self.dialog_tracking.as_mut().unwrap();
11633 Self::sync_tracking_active_edit_item(tracking);
11634 (
11635 tracking.dialog_ptr,
11636 tracking.edit_item,
11637 tracking.edit_text.clone(),
11638 tracking.items.clone(),
11639 )
11640 };
11641 self.flush_dialog_edit_item_texts(
11642 bus, dlg_ptr, &items, edit_item, &edit_text,
11643 );
11644 let saved = self.dialog_tracking.take().unwrap();
11645 self.persist_visible_dialog_snapshot(bus, &saved);
11646 self.dialog_saved_pixels
11647 .insert(saved.dialog_ptr, saved.saved_pixels);
11648 if item_hit_ptr != 0 {
11649 bus.write_word(item_hit_ptr, hit as u16);
11650 }
11651 cpu.write_reg(Register::A7, stack_ptr + 8);
11652 }
11653 _ => {
11656 let (dlg_ptr, edit_item, edit_text, items) = {
11657 let tracking =
11658 self.dialog_tracking.as_mut().unwrap();
11659 Self::sync_tracking_active_edit_item(tracking);
11660 (
11661 tracking.dialog_ptr,
11662 tracking.edit_item,
11663 tracking.edit_text.clone(),
11664 tracking.items.clone(),
11665 )
11666 };
11667 self.flush_dialog_edit_item_texts(
11668 bus, dlg_ptr, &items, edit_item, &edit_text,
11669 );
11670 let saved = self.dialog_tracking.take().unwrap();
11671 self.persist_visible_dialog_snapshot(bus, &saved);
11672 self.dialog_saved_pixels
11673 .insert(saved.dialog_ptr, saved.saved_pixels);
11674 self.consume_dialog_mouse_up();
11675 if item_hit_ptr != 0 {
11676 bus.write_word(item_hit_ptr, hit as u16);
11677 }
11678 cpu.write_reg(Register::A7, stack_ptr + 8);
11679 }
11680 }
11681 }
11682 }
11683 }
11684 3 => {
11686 let char_code = (e.message & 0xFF) as u8;
11687 let key_code = ((e.message >> 8) & 0xFF) as u8;
11688 let command_period =
11689 char_code == b'.' && (e.modifiers & 0x0100) != 0;
11690 let command_select_all = char_code.eq_ignore_ascii_case(&b'a')
11691 && (e.modifiers & 0x0100) != 0;
11692 match char_code {
11693 0x0D | 0x03 => {
11695 let target =
11696 self.dialog_tracking.as_ref().and_then(|tracking| {
11697 let def = tracking.default_item;
11698 if def <= 0 {
11699 return None;
11700 }
11701 tracking
11702 .items
11703 .get(def.saturating_sub(1) as usize)
11704 .map(|item| {
11705 (def, item.rect, item.text.clone(), true)
11706 })
11707 });
11708 if let Some((def, rect, title, is_default)) = target {
11709 let screen_rect =
11710 Self::dialog_item_screen_rect(bounds, rect);
11711 self.draw_dialog_button_highlight_state(
11712 bus,
11713 screen_rect,
11714 &title,
11715 is_default,
11716 true,
11717 );
11718 let t = self.dialog_tracking.as_mut().unwrap();
11719 t.flash_remaining = 6;
11720 t.flash_delay = 3;
11721 t.flash_item = def;
11722 }
11723 }
11724 0x1B | b'.' if char_code == 0x1B || command_period => {
11728 let target =
11729 self.dialog_tracking.as_ref().and_then(|tracking| {
11730 let cancel = tracking.cancel_item;
11731 if cancel <= 0 {
11732 return None;
11733 }
11734 tracking
11735 .items
11736 .get(cancel.saturating_sub(1) as usize)
11737 .map(|item| {
11738 (
11739 cancel,
11740 item.rect,
11741 item.text.clone(),
11742 cancel == tracking.default_item,
11743 )
11744 })
11745 });
11746 if let Some((cancel, rect, title, is_default)) = target {
11747 let screen_rect =
11748 Self::dialog_item_screen_rect(bounds, rect);
11749 self.draw_dialog_button_highlight_state(
11750 bus,
11751 screen_rect,
11752 &title,
11753 is_default,
11754 true,
11755 );
11756 let t = self.dialog_tracking.as_mut().unwrap();
11757 t.flash_remaining = 6;
11758 t.flash_delay = 3;
11759 t.flash_item = cancel;
11760 }
11761 }
11762 0x09 => {
11764 let mut switched = false;
11765 if let Some(tracking) = self.dialog_tracking.as_mut() {
11766 if tracking.edit_item > 0 {
11767 Self::sync_tracking_active_edit_item(tracking);
11768 }
11769 if !tracking.items.is_empty() {
11770 let start = tracking.edit_item.max(0) as usize;
11771 let next =
11772 (0..tracking.items.len()).find_map(|offset| {
11773 let idx =
11774 (start + offset) % tracking.items.len();
11775 if (tracking.items[idx].item_type & 0x7F)
11776 == 16
11777 {
11778 Some(idx)
11779 } else {
11780 None
11781 }
11782 });
11783 if let Some(idx) = next {
11784 tracking.edit_item = (idx + 1) as i16;
11785 tracking.edit_text =
11786 tracking.items[idx].text.clone();
11787 tracking.edit_text_modified = false;
11788 bus.write_word(dialog_ptr + 164, idx as u16);
11789 switched = true;
11790 }
11791 }
11792 }
11793 if switched {
11794 self.refresh_dialog_tracking_snapshot(bus);
11795 }
11796 }
11797 _ if command_select_all => {
11799 let mut select_trace = None;
11800 let mut modified_key_to_clear = None;
11801 if let Some(tracking) = self.dialog_tracking.as_mut() {
11802 let text_before = tracking.edit_text.clone();
11803 if tracking.edit_item > 0 {
11804 let text_len = tracking.edit_text.len();
11805 tracking.edit_text_modified = false;
11806 Self::set_tracking_active_edit_selection(
11807 tracking, 0, text_len,
11808 );
11809 Self::sync_tracking_active_edit_item(tracking);
11810
11811 let edit_item = tracking.edit_item;
11812 let item_type = tracking
11813 .items
11814 .get((edit_item - 1) as usize)
11815 .map(|item| item.item_type);
11816 let enabled_edit_text = item_type
11817 .map(|ty| (ty & 0x7F) == 16 && (ty & 0x80) == 0)
11818 .unwrap_or(false);
11819 let text_after = tracking.edit_text.clone();
11820 select_trace = Some((
11821 edit_item,
11822 item_type,
11823 text_before,
11824 text_after,
11825 enabled_edit_text,
11826 ));
11827 modified_key_to_clear =
11828 Some((tracking.dialog_ptr, edit_item));
11829 }
11830 }
11831 if let Some(key) = modified_key_to_clear {
11832 self.dialog_edit_text_modified_items.remove(&key);
11833 }
11834 if let Some((
11835 edit_item,
11836 item_type,
11837 text_before,
11838 text_after,
11839 enabled_edit_text,
11840 )) = select_trace
11841 {
11842 self.refresh_dialog_tracking_snapshot(bus);
11843 let outcome = if enabled_edit_text {
11844 "enabled_edittext_select_all"
11845 } else {
11846 "edittext_select_all"
11847 };
11848 if enabled_edit_text {
11849 let (dlg_ptr, active_edit_item, edit_text, items) = {
11850 let tracking =
11851 self.dialog_tracking.as_mut().unwrap();
11852 Self::sync_tracking_active_edit_item(tracking);
11853 (
11854 tracking.dialog_ptr,
11855 tracking.edit_item,
11856 tracking.edit_text.clone(),
11857 tracking.items.clone(),
11858 )
11859 };
11860 self.flush_dialog_edit_item_texts(
11861 bus,
11862 dlg_ptr,
11863 &items,
11864 active_edit_item,
11865 &edit_text,
11866 );
11867 let saved = self.dialog_tracking.take().unwrap();
11868 let saved_dialog_ptr = saved.dialog_ptr;
11869 self.persist_visible_dialog_snapshot(bus, &saved);
11870 self.dialog_saved_pixels
11871 .insert(saved_dialog_ptr, saved.saved_pixels);
11872 if item_hit_ptr != 0 {
11873 bus.write_word(
11874 item_hit_ptr,
11875 active_edit_item as u16,
11876 );
11877 }
11878 cpu.write_reg(Register::A7, stack_ptr + 8);
11879 self.record_modal_dialog_text_input_trace(
11880 "key_down",
11881 dialog_ptr,
11882 bounds,
11883 edit_item,
11884 item_type,
11885 key_code,
11886 char_code,
11887 &text_before,
11888 &text_after,
11889 "returned",
11890 outcome,
11891 );
11892 } else {
11893 self.record_modal_dialog_text_input_trace(
11894 "key_down",
11895 dialog_ptr,
11896 bounds,
11897 edit_item,
11898 item_type,
11899 key_code,
11900 char_code,
11901 &text_before,
11902 &text_after,
11903 "pending",
11904 outcome,
11905 );
11906 }
11907 }
11908 }
11909 0x08 | 0x20..=0x7E => {
11910 let mut text_trace = None;
11911 let mut modified_key_to_set = None;
11912 if let Some(tracking) = self.dialog_tracking.as_mut() {
11913 let text_before = tracking.edit_text.clone();
11914 if tracking.edit_item > 0 {
11915 if char_code == 0x08 {
11916 if !tracking.edit_text_modified {
11917 tracking.edit_text.clear();
11919 tracking.edit_text_modified = true;
11920 } else if !tracking.edit_text.is_empty() {
11921 tracking.edit_text.pop();
11922 }
11923 } else {
11924 if !tracking.edit_text_modified {
11925 tracking.edit_text.clear();
11927 tracking.edit_text_modified = true;
11928 }
11929 tracking.edit_text.push(char_code as char);
11930 }
11931 let cursor = tracking.edit_text.len();
11932 Self::set_tracking_active_edit_selection(
11933 tracking, cursor, cursor,
11934 );
11935 Self::sync_tracking_active_edit_item(tracking);
11936 modified_key_to_set =
11937 Some((tracking.dialog_ptr, tracking.edit_item));
11938
11939 let edit_item = tracking.edit_item;
11940 let item_type = tracking
11941 .items
11942 .get((edit_item - 1) as usize)
11943 .map(|item| item.item_type);
11944 let enabled_edit_text = item_type
11945 .map(|ty| (ty & 0x7F) == 16 && (ty & 0x80) == 0)
11946 .unwrap_or(false);
11947 let text_after = tracking.edit_text.clone();
11948 text_trace = Some((
11949 edit_item,
11950 item_type,
11951 text_before,
11952 text_after,
11953 enabled_edit_text,
11954 ));
11955 }
11956 }
11957 if let Some(key) = modified_key_to_set {
11958 self.dialog_edit_text_modified_items.insert(key);
11959 }
11960 if let Some((
11961 edit_item,
11962 item_type,
11963 text_before,
11964 text_after,
11965 enabled_edit_text,
11966 )) = text_trace
11967 {
11968 self.refresh_dialog_tracking_snapshot(bus);
11969 let outcome = if enabled_edit_text {
11970 "enabled_edittext_item_hit"
11971 } else {
11972 "edittext_updated"
11973 };
11974 if enabled_edit_text {
11975 let (dlg_ptr, active_edit_item, edit_text, items) = {
11976 let tracking =
11977 self.dialog_tracking.as_mut().unwrap();
11978 Self::sync_tracking_active_edit_item(tracking);
11979 (
11980 tracking.dialog_ptr,
11981 tracking.edit_item,
11982 tracking.edit_text.clone(),
11983 tracking.items.clone(),
11984 )
11985 };
11986 self.flush_dialog_edit_item_texts(
11987 bus,
11988 dlg_ptr,
11989 &items,
11990 active_edit_item,
11991 &edit_text,
11992 );
11993 let saved = self.dialog_tracking.take().unwrap();
11994 let saved_dialog_ptr = saved.dialog_ptr;
11995 self.persist_visible_dialog_snapshot(bus, &saved);
11996 self.dialog_saved_pixels
11997 .insert(saved_dialog_ptr, saved.saved_pixels);
11998 if item_hit_ptr != 0 {
11999 bus.write_word(
12000 item_hit_ptr,
12001 active_edit_item as u16,
12002 );
12003 }
12004 cpu.write_reg(Register::A7, stack_ptr + 8);
12005 self.record_modal_dialog_text_input_trace(
12006 "key_down",
12007 dialog_ptr,
12008 bounds,
12009 edit_item,
12010 item_type,
12011 key_code,
12012 char_code,
12013 &text_before,
12014 &text_after,
12015 "returned",
12016 outcome,
12017 );
12018 } else {
12019 self.record_modal_dialog_text_input_trace(
12020 "key_down",
12021 dialog_ptr,
12022 bounds,
12023 edit_item,
12024 item_type,
12025 key_code,
12026 char_code,
12027 &text_before,
12028 &text_after,
12029 "pending",
12030 outcome,
12031 );
12032 }
12033 }
12034 }
12035 _ => {}
12036 }
12037 }
12038 _ => {}
12039 }
12040 }
12041 } else {
12042 let sp = cpu.read_reg(Register::A7);
12044 let item_hit_ptr = bus.read_long(sp);
12045 let filter_proc = bus.read_long(sp + 4);
12046 if item_hit_ptr != 0 {
12047 bus.write_word(item_hit_ptr, 0);
12048 }
12049
12050 let mut dialog_ptr = self.front_window;
12059 if !self.dialog_items.contains_key(&dialog_ptr) {
12060 if let Some((&retained_dialog_ptr, snapshot)) =
12061 self.dialog_visible_snapshots.iter().next()
12062 {
12063 dialog_ptr = retained_dialog_ptr;
12064 self.front_window = retained_dialog_ptr;
12065 self.window_bounds = snapshot.bounds;
12066 self.window_proc_id = self
12067 .window_proc_ids
12068 .get(&retained_dialog_ptr)
12069 .copied()
12070 .unwrap_or(self.window_proc_id);
12071 self.window_title.clear();
12072 }
12073 }
12074 if let Some(mut items) = self.dialog_items.get(&dialog_ptr).cloned() {
12075 if trace_dialog_filter_enabled() {
12076 eprintln!(
12077 "[DIALOG-FILTER] init dialog=${:08X} items={} filter_proc=${:08X} item_hit_ptr=${:08X}",
12078 dialog_ptr,
12079 items.len(),
12080 filter_proc,
12081 item_hit_ptr
12082 );
12083 }
12084 Self::refresh_ditl_proc_ptrs(bus, dialog_ptr, &mut items);
12088 let bounds = self.window_bounds;
12089 let proc_id = self.window_proc_id;
12090 let title = self.window_title.clone();
12091
12092 let (edit_text, edit_item, default_item) =
12093 Self::dialog_edit_state(bus, dialog_ptr, &items);
12094 let cancel_item = self
12095 .dialog_cancel_items
12096 .get(&dialog_ptr)
12097 .copied()
12098 .unwrap_or(2);
12099 let edit_text_modified = edit_item > 0
12100 && self
12101 .dialog_edit_text_modified_items
12102 .contains(&(dialog_ptr, edit_item));
12103
12104 let game_managed = Self::dialog_is_game_managed(bounds, &items);
12108
12109 if trace_dialog_procs_enabled() {
12110 for (i, item) in items.iter().enumerate() {
12111 eprintln!(
12112 "[DIALOG-PROC] dialog=${:08X} item={} type={} proc=${:08X} rect=({},{},{},{}) text={:?}",
12113 dialog_ptr, i + 1, item.item_type, item.proc_ptr,
12114 item.rect.0, item.rect.1, item.rect.2, item.rect.3,
12115 item.text,
12116 );
12117 if (item.item_type & 0x7F) == 0 {
12118 eprintln!(
12119 "[DIALOG-PROC] dialog=${:08X} item={} type={} proc=${:08X}",
12120 dialog_ptr,
12121 i + 1,
12122 item.item_type,
12123 item.proc_ptr,
12124 );
12125 }
12126 }
12127 }
12128
12129 let is_reentry = self.dialog_modal_entered.contains(&dialog_ptr);
12134 let preserved_visible_snapshot =
12135 self.dialog_visible_snapshots.remove(&dialog_ptr);
12136 let reused_retained_visible_snapshot =
12137 is_reentry && preserved_visible_snapshot.is_some();
12138 let preserved_saved_pixels =
12139 self.dialog_saved_pixels.get(&dialog_ptr).cloned();
12140 let restored_visible_snapshot = preserved_visible_snapshot.is_some();
12141 let saved_pixels = preserved_saved_pixels
12142 .unwrap_or_else(|| self.save_dialog_pixels(bus, bounds));
12143 if let Some(snapshot) = preserved_visible_snapshot {
12144 self.restore_dialog_pixels(bus, snapshot.bounds, &snapshot.pixels);
12145 }
12146 if !game_managed && !is_reentry {
12147 self.dialog_initial_draw_deferred.remove(&dialog_ptr);
12158 self.draw_dialog_preserving_user_items(
12159 bus,
12160 bounds,
12161 proc_id,
12162 &title,
12163 &items,
12164 default_item,
12165 &edit_text,
12166 edit_item,
12167 false,
12168 dialog_ptr,
12169 restored_visible_snapshot,
12170 true,
12171 true,
12172 );
12173 }
12174
12175 let implicit_popup_candidate_count = items
12182 .iter()
12183 .enumerate()
12184 .filter(|(i, item)| {
12185 let item_no = (*i + 1) as i16;
12186 (item.item_type & 0x7F) == 0
12187 && !self
12188 .dialog_item_popup_menus
12189 .contains_key(&(dialog_ptr, item_no))
12190 && self
12191 .dialog_popup_candidate_items
12192 .contains(&(dialog_ptr, item_no))
12193 })
12194 .count();
12195 let mut implicit_popup_menu_ids: Vec<i16> = self
12196 .menus
12197 .iter()
12198 .filter(|menu| {
12199 menu.in_menu_bar
12200 && !menu.visible_in_menu_bar
12201 && menu.items.iter().any(|item| item.mark == 0x12)
12202 })
12203 .map(|menu| menu.id)
12204 .collect();
12205 if implicit_popup_menu_ids.len() < implicit_popup_candidate_count {
12206 implicit_popup_menu_ids = self
12207 .menus
12208 .iter()
12209 .filter(|menu| menu.items.iter().any(|item| item.mark == 0x12))
12210 .map(|menu| menu.id)
12211 .collect();
12212 }
12213 let mut implicit_popup_menu_index = 0usize;
12214 let popup_draws: Vec<DialogPopupDraw> = items
12215 .iter()
12216 .enumerate()
12217 .filter_map(|(i, item)| {
12218 let item_no = (i + 1) as i16;
12219 if (item.item_type & 0x7F) != 0 {
12220 return None;
12221 }
12222 let key = (dialog_ptr, item_no);
12223 let (menu_id, original_rect) = if let Some(&menu_id) =
12224 self.dialog_item_popup_menus.get(&key)
12225 {
12226 let rect = self
12227 .dialog_popup_original_rects
12228 .get(&key)
12229 .copied()
12230 .unwrap_or(item.rect);
12231 (menu_id, rect)
12232 } else if self.dialog_popup_candidate_items.contains(&key) {
12233 let menu_id =
12234 *implicit_popup_menu_ids.get(implicit_popup_menu_index)?;
12235 implicit_popup_menu_index += 1;
12236 let rect = self
12237 .dialog_popup_original_rects
12238 .get(&key)
12239 .copied()
12240 .unwrap_or(item.rect);
12241 (menu_id, rect)
12242 } else {
12243 return None;
12244 };
12245 let checked_text = self
12246 .menus
12247 .iter()
12248 .find(|m| m.id == menu_id)
12249 .and_then(|m| {
12250 m.items
12251 .iter()
12252 .find(|mi| mi.mark == 0x12)
12253 .map(|mi| mi.text.clone())
12254 })
12255 .unwrap_or_default();
12256 let (it_t, it_l, it_b, it_r) = original_rect;
12258 Some(DialogPopupDraw {
12259 rect: (
12260 bounds.0 + it_t,
12261 bounds.1 + it_l,
12262 bounds.0 + it_b,
12263 bounds.1 + it_r,
12264 ),
12265 title: checked_text,
12266 enabled: (item.item_type & 0x80) == 0,
12267 pressed: false,
12268 })
12269 })
12270 .collect();
12271 self.redraw_dialog_popup_controls(bus, &popup_draws);
12272
12273 let rendered_pixels = self.save_dialog_pixels(bus, bounds);
12278
12279 let mut draw_proc_queue = VecDeque::new();
12291 if !reused_retained_visible_snapshot {
12292 for (i, item) in items.iter().enumerate() {
12293 let base_type = item.item_type & 0x7F;
12294 if base_type == 0
12295 && item.proc_ptr != 0
12296 && Self::dialog_item_intersects_bounds(bounds, item)
12297 {
12298 draw_proc_queue.push_back((item.proc_ptr, (i + 1) as i16));
12299 }
12300 }
12301 }
12302 let has_draw_procs = !draw_proc_queue.is_empty();
12303
12304 self.dialog_modal_entered.insert(dialog_ptr);
12305 self.dialog_tracking = Some(super::dispatch::DialogTrackingState {
12306 dialog_ptr,
12307 bounds,
12308 title,
12309 proc_id,
12310 items,
12311 default_item,
12312 cancel_item,
12313 edit_text,
12314 edit_item,
12315 saved_pixels,
12316 stack_ptr: sp,
12317 item_hit_ptr,
12318 rendered_pixels,
12319 flash_remaining: 0,
12320 flash_delay: 0,
12321 flash_item: 0,
12322 edit_text_modified,
12323 draw_proc_queue,
12324 draw_procs_done: !has_draw_procs,
12325 rendered_pixels_final: !has_draw_procs,
12326 filter_proc,
12327 game_managed,
12328 last_filter_event: None,
12329 popup_draws,
12330 active_popup: None,
12331 active_button: None,
12332 active_user_item: None,
12333 });
12334 self.record_modal_dialog_input_trace(
12335 "start",
12336 dialog_ptr,
12337 bounds,
12338 0,
12339 None,
12340 None,
12341 "pending",
12342 "open_modal_tracking",
12343 );
12344 } else {
12346 eprintln!("[TRAP] ModalDialog: no items found, returning 1");
12348 if item_hit_ptr != 0 {
12349 bus.write_word(item_hit_ptr, 1);
12350 }
12351 cpu.write_reg(Register::A7, sp + 8);
12352 }
12353 }
12354 Ok(())
12355 }
12356
12357 (true, 0x1CC) => {
12388 use crate::memory::globals::addr;
12389 let existing = bus.read_long(addr::TE_SCRP_HANDLE);
12393 if existing == 0 {
12394 let handle = Self::allocate_handle_with_data(bus, 0);
12404 bus.write_long(addr::TE_SCRP_HANDLE, handle);
12405 }
12406 bus.write_word(addr::TE_SCRP_LENGTH, 0);
12407 Ok(())
12408 }
12409
12410 (true, 0x012) => {
12460 let sp = cpu.read_reg(Register::A7);
12461 let te_handle = bus.read_long(sp);
12462 let mut dv = bus.read_word(sp + 4) as i16;
12463 let mut dh = bus.read_word(sp + 6) as i16;
12464 cpu.write_reg(Register::A7, sp + 8);
12465
12466 let te_ptr = Self::te_record_ptr(bus, te_handle);
12467 if te_ptr != 0 {
12468 let view_rect = Self::te_read_rect(bus, te_ptr + Self::TE_VIEW_RECT_OFFSET);
12469 let dest_rect = Self::te_read_rect(bus, te_ptr + Self::TE_DEST_RECT_OFFSET);
12470
12471 if dv > 0 {
12472 let max_down = view_rect.0.saturating_sub(dest_rect.0);
12473 if dv > max_down {
12474 dv = max_down;
12475 }
12476 } else if dv < 0 {
12477 let text_len = Self::te_text_length(bus, te_handle);
12488 let (end_top, _) = self.te_char_to_point(bus, te_handle, text_len);
12489 let end_line = Self::te_char_to_line_index(bus, te_handle, text_len);
12490 let text_bottom = end_top
12491 .saturating_add(Self::te_height_for_line(bus, te_handle, end_line));
12492 let max_up = view_rect.2.saturating_sub(text_bottom);
12493 dv = if max_up >= 0 {
12494 0
12495 } else if dv < max_up {
12496 max_up
12497 } else {
12498 dv
12499 };
12500 }
12501
12502 if dh > 0 {
12503 let max_right = view_rect.1.saturating_sub(dest_rect.1);
12504 dh = if max_right > 0 { dh.min(max_right) } else { 0 };
12505 } else if dh < 0 {
12506 let max_left = view_rect.1.saturating_sub(dest_rect.1);
12507 dh = if max_left < 0 { dh.max(max_left) } else { 0 };
12508 }
12509
12510 if trace_textedit_enabled() {
12511 let adjusted = (
12512 dest_rect.0.saturating_add(dv),
12513 dest_rect.1.saturating_add(dh),
12514 dest_rect.2.saturating_add(dv),
12515 dest_rect.3.saturating_add(dh),
12516 );
12517 eprintln!(
12518 "[TE] TEPinScroll hTE=${:08X} dh={} dv={} dest=({},{},{},{})",
12519 te_handle, dh, dv, adjusted.0, adjusted.1, adjusted.2, adjusted.3
12520 );
12521 }
12522 self.te_scroll_contents(cpu, bus, te_handle, dh, dv);
12523 }
12524 Ok(())
12525 }
12526
12527 (true, 0x013) => {
12536 let sp = cpu.read_reg(Register::A7);
12537 let te_handle = bus.read_long(sp);
12538 let enabled = bus.read_byte(sp + 4) != 0;
12540 self.set_te_feature_bit(te_handle, Self::TE_FEATURE_AUTO_SCROLL, enabled);
12541 if trace_textedit_enabled() {
12542 eprintln!("[TE] TEAutoView hTE=${:08X} enabled={}", te_handle, enabled);
12543 }
12544 cpu.write_reg(Register::A7, sp + 6);
12545 Ok(())
12546 }
12547
12548 (true, 0x011) => {
12589 let sp = cpu.read_reg(Register::A7);
12590 let te_handle = bus.read_long(sp);
12591 cpu.write_reg(Register::A7, sp + 4);
12592 if self.te_feature_bit(te_handle, Self::TE_FEATURE_AUTO_SCROLL) {
12593 let te_ptr = Self::te_record_ptr(bus, te_handle);
12594 if te_ptr != 0 {
12595 let view_rect = Self::te_read_rect(bus, te_ptr + Self::TE_VIEW_RECT_OFFSET);
12596 let dest_rect = Self::te_read_rect(bus, te_ptr + Self::TE_DEST_RECT_OFFSET);
12597 let sel_start = bus.read_word(te_ptr + Self::TE_SEL_START_OFFSET) as usize;
12598 let sel_end = bus.read_word(te_ptr + Self::TE_SEL_END_OFFSET) as usize;
12599 let (start_top, start_left) =
12600 self.te_char_to_point(bus, te_handle, sel_start);
12601 let (stop_top, stop_left) = self.te_char_to_point(bus, te_handle, sel_end);
12602 let stop_line = Self::te_char_to_line_index(bus, te_handle, sel_end);
12603 let stop_bottom = stop_top
12604 .saturating_add(Self::te_height_for_line(bus, te_handle, stop_line));
12605 let dv =
12606 Self::te_getdelta(start_top, stop_bottom, view_rect.0, view_rect.2);
12607 let dh = Self::te_getdelta(start_left, stop_left, view_rect.1, view_rect.3);
12608 if trace_textedit_enabled() {
12609 let adjusted = (
12610 dest_rect.0.saturating_add(dv),
12611 dest_rect.1.saturating_add(dh),
12612 dest_rect.2.saturating_add(dv),
12613 dest_rect.3.saturating_add(dh),
12614 );
12615 eprintln!(
12616 "[TE] TESelView hTE=${:08X} view=({},{},{},{}) dest=({},{},{},{}) adjusted=({},{},{},{})",
12617 te_handle,
12618 view_rect.0,
12619 view_rect.1,
12620 view_rect.2,
12621 view_rect.3,
12622 dest_rect.0,
12623 dest_rect.1,
12624 dest_rect.2,
12625 dest_rect.3,
12626 adjusted.0,
12627 adjusted.1,
12628 adjusted.2,
12629 adjusted.3
12630 );
12631 }
12632 self.te_scroll_contents(cpu, bus, te_handle, dh, dv);
12633 }
12634 }
12635 Ok(())
12636 }
12637
12638 (true, 0x050) => {
12645 self.cursor_data = Some(Self::default_arrow_cursor_image());
12646 self.cursor_level = 0;
12647 self.cursor_visible = true;
12648 Ok(())
12649 }
12650
12651 (true, 0x051) => {
12658 let sp = cpu.read_reg(Register::A7);
12659 let crsr_ptr = bus.read_long(sp);
12660 cpu.write_reg(Register::A7, sp + 4);
12661
12662 let mut data = [0u8; 32];
12664 for (i, byte) in data.iter_mut().enumerate() {
12665 *byte = bus.read_byte(crsr_ptr + i as u32);
12666 }
12667 let mut mask = [0u8; 32];
12669 for (i, byte) in mask.iter_mut().enumerate() {
12670 *byte = bus.read_byte(crsr_ptr + 32 + i as u32);
12671 }
12672 let hot_v = bus.read_word(crsr_ptr + 64) as i16;
12674 let hot_h = bus.read_word(crsr_ptr + 66) as i16;
12675
12676 self.cursor_data = Some(CursorImage::mono(data, mask, hot_v, hot_h));
12677 self.cursor_visible = self.cursor_level == 0;
12678 Ok(())
12679 }
12680
12681 (true, 0x052) => {
12685 self.cursor_level = self.cursor_level.saturating_sub(1);
12686 self.cursor_visible = self.cursor_level == 0;
12687 Ok(())
12688 }
12689
12690 (true, 0x053) => {
12694 if self.cursor_level < 0 {
12695 self.cursor_level += 1;
12696 }
12697 self.cursor_visible = self.cursor_level == 0;
12698 Ok(())
12699 }
12700
12701 (true, 0x056) => Ok(()),
12747
12748 (true, 0x1B9) => {
12778 let sp = cpu.read_reg(Register::A7);
12779 let cursor_id = bus.read_word(sp) as i16;
12780
12781 let handle = if let Some((refnum, ptr)) =
12782 self.find_or_load_resource_any(bus, *b"CURS", cursor_id)
12783 {
12784 self.get_or_create_resource_handle_in_file(
12785 bus, *b"CURS", cursor_id, ptr, refnum,
12786 )
12787 } else if let Some(ptr) = self.synthesize_system_cursor(bus, cursor_id) {
12788 self.get_or_create_resource_handle(bus, *b"CURS", cursor_id, ptr)
12792 } else {
12793 0
12794 };
12795
12796 bus.write_long(sp + 2, handle);
12797 cpu.write_reg(Register::A7, sp + 2);
12798 Ok(())
12799 }
12800
12801 (true, 0x1B8) => {
12824 let sp = cpu.read_reg(Register::A7);
12825 let pat_id = bus.read_word(sp) as i16;
12826
12827 let handle = if let Some((refnum, ptr)) =
12828 self.find_or_load_resource_any(bus, *b"PAT ", pat_id)
12829 {
12830 self.get_or_create_resource_handle_in_file(bus, *b"PAT ", pat_id, ptr, refnum)
12831 } else {
12832 0
12833 };
12834
12835 bus.write_long(sp + 2, handle);
12836 cpu.write_reg(Register::A7, sp + 2);
12837 Ok(())
12838 }
12839
12840 (true, 0x1BB) => {
12869 let sp = cpu.read_reg(Register::A7);
12870 let icon_id = bus.read_word(sp) as i16;
12871
12872 let handle = if let Some((refnum, ptr)) =
12873 self.find_or_load_resource_any(bus, *b"ICON", icon_id)
12874 {
12875 let h = self
12876 .get_or_create_resource_handle_in_file(bus, *b"ICON", icon_id, ptr, refnum);
12877 eprintln!(
12878 "[TRAP] GetIcon({}) -> handle=${:08X} ptr=${:08X}",
12879 icon_id, h, ptr
12880 );
12881 h
12882 } else {
12883 eprintln!("[TRAP] GetIcon({}) -> NIL (not found)", icon_id);
12884 0
12885 };
12886
12887 bus.write_long(sp + 2, handle);
12888 cpu.write_reg(Register::A7, sp + 2);
12889 Ok(())
12890 }
12891
12892 (true, 0x1BC) => {
12899 let sp = cpu.read_reg(Register::A7);
12900 let pic_id = bus.read_word(sp) as i16;
12901
12902 let handle = if let Some((refnum, ptr)) =
12903 self.find_or_load_resource_any(bus, *b"PICT", pic_id)
12904 {
12905 let h = self
12906 .get_or_create_resource_handle_in_file(bus, *b"PICT", pic_id, ptr, refnum);
12907 eprintln!(
12908 "[TRAP] GetPicture({}) -> handle=${:08X} ptr=${:08X}",
12909 pic_id, h, ptr
12910 );
12911 h
12912 } else {
12913 eprintln!("[TRAP] GetPicture({}) -> NIL (not found)", pic_id);
12914 0
12915 };
12916
12917 bus.write_long(sp + 2, handle);
12918 cpu.write_reg(Register::A7, sp + 2);
12919 Ok(())
12920 }
12921
12922 (true, 0x1BA) => {
12928 let sp = cpu.read_reg(Register::A7);
12929 let string_id = bus.read_word(sp) as i16;
12930 let handle = if let Some((refnum, ptr)) =
12931 self.find_or_load_resource_any(bus, *b"STR ", string_id)
12932 {
12933 self.get_or_create_resource_handle_in_file(
12934 bus, *b"STR ", string_id, ptr, refnum,
12935 )
12936 } else if let Some(ptr) = self.synthesize_system_str(bus, string_id) {
12937 self.get_or_create_resource_handle(bus, *b"STR ", string_id, ptr)
12938 } else {
12939 0
12940 };
12941 bus.write_long(sp + 2, handle);
12942 cpu.write_reg(Register::A7, sp + 2);
12943 Ok(())
12944 }
12945
12946 (true, 0x1D2) => {
12987 let sp = cpu.read_reg(Register::A7);
12988 let handle = Self::allocate_te_handle(bus);
12989 let (dest_rect, view_rect, stack_pop) = Self::te_new_rect_args(bus, sp);
12990 self.initialize_te_record(bus, handle, dest_rect, view_rect);
12991 bus.write_long(sp + stack_pop, handle);
12992 cpu.write_reg(Register::A7, sp + stack_pop);
12993 Ok(())
12994 }
12995
12996 (true, 0x03E) => {
13040 let sp = cpu.read_reg(Register::A7);
13041 let handle = Self::allocate_te_handle(bus);
13042 let (dest_rect, view_rect, stack_pop) = Self::te_new_rect_args(bus, sp);
13043 self.initialize_styled_te_record(bus, handle, dest_rect, view_rect);
13044 bus.write_long(sp + stack_pop, handle);
13045 cpu.write_reg(Register::A7, sp + stack_pop);
13046 Ok(())
13047 }
13048
13049 (true, 0x03C) => {
13096 let sp = cpu.read_reg(Register::A7);
13097 let te_handle = bus.read_long(sp);
13098 let point_v = bus.read_word(sp + 4) as i16;
13099 let point_h = bus.read_word(sp + 6) as i16;
13100 let offset = self.te_point_to_char(bus, te_handle, (point_v, point_h));
13101 bus.write_word(sp + 8, offset as u16);
13102 cpu.write_reg(Register::A7, sp + 8);
13103 Ok(())
13104 }
13105
13106 (false, 0x0FE) => {
13112 let current_pos = (cpu.read_reg(Register::D0) as u16) as usize;
13113 let _caller = cpu.read_reg(Register::D2);
13114 let _p_te = cpu.read_reg(Register::A3);
13115 let h_te = cpu.read_reg(Register::A4);
13116 let (word_start, word_end) = self.te_find_word_bounds(bus, h_te, current_pos);
13117 cpu.write_reg(Register::D0, u32::from(word_start));
13118 cpu.write_reg(Register::D1, u32::from(word_end));
13119 Ok(())
13120 }
13121
13122 (true, 0x03D) => {
13185 let sp = cpu.read_reg(Register::A7);
13186 let selector = bus.read_word(sp);
13187 if trace_textedit_enabled() {
13188 eprintln!("[TE] TEDispatch selector=${:04X} sp=${:08X}", selector, sp);
13189 }
13190 match selector {
13191 0x0000 => {
13192 cpu.write_reg(Register::A7, sp + 6);
13195 }
13196 0x0001 => {
13197 let te_handle = bus.read_long(sp + 2);
13202 let redraw = bus.read_byte(sp + 6) != 0;
13204 let style_ptr = bus.read_long(sp + 8);
13205 let mode = bus.read_word(sp + 12);
13206 if trace_textedit_enabled() {
13207 eprintln!(
13208 "[TE] TESetStyle hTE=${:08X} mode=${:04X} redraw={} style_ptr=${:08X}",
13209 te_handle, mode, redraw, style_ptr
13210 );
13211 if style_ptr != 0 {
13212 let te_ptr = Self::te_record_ptr(bus, te_handle);
13213 eprintln!(
13214 "[TE] TESetStyle values sel={}..{} font={} face=${:04X} size={} color=(${:04X},${:04X},${:04X})",
13215 if te_ptr != 0 { bus.read_word(te_ptr + Self::TE_SEL_START_OFFSET) } else { 0 },
13216 if te_ptr != 0 { bus.read_word(te_ptr + Self::TE_SEL_END_OFFSET) } else { 0 },
13217 bus.read_word(style_ptr) as i16,
13218 bus.read_byte(style_ptr + 2),
13219 bus.read_word(style_ptr + 4) as i16,
13220 bus.read_word(style_ptr + 6),
13221 bus.read_word(style_ptr + 8),
13222 bus.read_word(style_ptr + 10),
13223 );
13224 }
13225 }
13226 if style_ptr != 0 {
13227 let te_ptr = Self::te_record_ptr(bus, te_handle);
13228 if te_ptr != 0 {
13229 let insertion_point = bus
13230 .read_word(te_ptr + Self::TE_SEL_START_OFFSET)
13231 == bus.read_word(te_ptr + Self::TE_SEL_END_OFFSET);
13232 if insertion_point && Self::te_is_styled_record(bus, te_ptr) {
13233 Self::te_set_null_style(bus, te_handle, mode, style_ptr);
13234 } else {
13235 let style_handle = Self::te_style_handle(bus, te_handle);
13236 if style_handle != 0 {
13237 let style_ptr_record = bus.read_long(style_handle);
13238 if style_ptr_record != 0 {
13239 let table_handle = bus.read_long(
13240 style_ptr_record
13241 + Self::TE_STYLE_STYLE_TABLE_OFFSET,
13242 );
13243 let table_ptr = if table_handle != 0 {
13244 bus.read_long(table_handle)
13245 } else {
13246 0
13247 };
13248 if table_ptr != 0 {
13249 if (mode & 0x0001) != 0 {
13250 bus.write_word(
13251 table_ptr + Self::ST_ELEMENT_FONT_OFFSET,
13252 bus.read_word(style_ptr),
13253 );
13254 }
13255 if (mode & 0x0002) != 0 {
13256 bus.write_byte(
13257 table_ptr + Self::ST_ELEMENT_FACE_OFFSET,
13258 bus.read_byte(style_ptr + 2),
13259 );
13260 }
13261 if (mode & 0x0004) != 0 {
13262 bus.write_word(
13263 table_ptr + Self::ST_ELEMENT_SIZE_OFFSET,
13264 bus.read_word(style_ptr + 4),
13265 );
13266 }
13267 if (mode & 0x0008) != 0 {
13268 bus.write_word(
13269 table_ptr + Self::ST_ELEMENT_COLOR_OFFSET,
13270 bus.read_word(style_ptr + 6),
13271 );
13272 bus.write_word(
13273 table_ptr
13274 + Self::ST_ELEMENT_COLOR_OFFSET
13275 + 2,
13276 bus.read_word(style_ptr + 8),
13277 );
13278 bus.write_word(
13279 table_ptr
13280 + Self::ST_ELEMENT_COLOR_OFFSET
13281 + 4,
13282 bus.read_word(style_ptr + 10),
13283 );
13284 }
13285
13286 let resolved_font = bus.read_word(
13287 table_ptr + Self::ST_ELEMENT_FONT_OFFSET,
13288 )
13289 as i16;
13290 let resolved_size = bus.read_word(
13291 table_ptr + Self::ST_ELEMENT_SIZE_OFFSET,
13292 )
13293 as i16;
13294 let metrics = get_font_metrics(
13295 resolved_font,
13296 Self::font_lookup_size(resolved_size),
13297 );
13298 let line_height = metrics.ascent
13299 + metrics.descent
13300 + metrics.leading;
13301 bus.write_word(
13302 table_ptr + Self::ST_ELEMENT_HEIGHT_OFFSET,
13303 line_height as u16,
13304 );
13305 bus.write_word(
13306 table_ptr + Self::ST_ELEMENT_ASCENT_OFFSET,
13307 metrics.ascent as u16,
13308 );
13309
13310 let lh_handle = bus.read_long(
13311 style_ptr_record
13312 + Self::TE_STYLE_LH_TABLE_OFFSET,
13313 );
13314 let lh_ptr = if lh_handle != 0 {
13315 bus.read_long(lh_handle)
13316 } else {
13317 0
13318 };
13319 if lh_ptr != 0 {
13320 bus.write_word(
13321 lh_ptr + Self::LH_ELEMENT_HEIGHT_OFFSET,
13322 line_height as u16,
13323 );
13324 bus.write_word(
13325 lh_ptr + Self::LH_ELEMENT_ASCENT_OFFSET,
13326 metrics.ascent as u16,
13327 );
13328 }
13329 }
13330 }
13331 } else {
13332 if (mode & 0x0001) != 0 {
13333 bus.write_word(
13334 te_ptr + Self::TE_TX_FONT_OFFSET,
13335 bus.read_word(style_ptr),
13336 );
13337 }
13338 if (mode & 0x0002) != 0 {
13339 bus.write_byte(
13340 te_ptr + Self::TE_TX_FACE_OFFSET,
13341 bus.read_byte(style_ptr + 2),
13342 );
13343 }
13344 if (mode & 0x0004) != 0 {
13345 bus.write_word(
13346 te_ptr + Self::TE_TX_SIZE_OFFSET,
13347 bus.read_word(style_ptr + 4),
13348 );
13349 }
13350
13351 let resolved_font =
13352 bus.read_word(te_ptr + Self::TE_TX_FONT_OFFSET) as i16;
13353 let resolved_size =
13354 bus.read_word(te_ptr + Self::TE_TX_SIZE_OFFSET) as i16;
13355 let metrics = get_font_metrics(
13356 resolved_font,
13357 Self::font_lookup_size(resolved_size),
13358 );
13359 bus.write_word(
13360 te_ptr + Self::TE_LINE_HEIGHT_OFFSET,
13361 (metrics.ascent + metrics.descent + metrics.leading)
13362 as u16,
13363 );
13364 bus.write_word(
13365 te_ptr + Self::TE_FONT_ASCENT_OFFSET,
13366 metrics.ascent as u16,
13367 );
13368 }
13369 }
13370 }
13371 }
13372 let _ = redraw;
13373 cpu.write_reg(Register::A7, sp + 14);
13374 }
13375 0x0002 => {
13376 let te_handle = bus.read_long(sp + 2);
13386 let _redraw = bus.read_byte(sp + 6) != 0;
13387 let new_style_ptr = bus.read_long(sp + 8);
13388 let old_style_ptr = bus.read_long(sp + 12);
13389 let mode = bus.read_word(sp + 16);
13390 if old_style_ptr != 0 && new_style_ptr != 0 {
13391 let style_handle = Self::te_style_handle(bus, te_handle);
13392 if style_handle != 0 {
13393 let style_ptr_record = bus.read_long(style_handle);
13394 if style_ptr_record != 0 {
13395 let table_handle = bus.read_long(
13396 style_ptr_record + Self::TE_STYLE_STYLE_TABLE_OFFSET,
13397 );
13398 let table_ptr = if table_handle != 0 {
13399 bus.read_long(table_handle)
13400 } else {
13401 0
13402 };
13403 if table_ptr != 0 {
13404 let mut matches_old = true;
13410 if (mode & 0x0001) != 0
13411 && bus
13412 .read_word(table_ptr + Self::ST_ELEMENT_FONT_OFFSET)
13413 != bus.read_word(old_style_ptr)
13414 {
13415 matches_old = false;
13416 }
13417 if (mode & 0x0002) != 0
13418 && bus
13419 .read_byte(table_ptr + Self::ST_ELEMENT_FACE_OFFSET)
13420 != bus.read_byte(old_style_ptr + 2)
13421 {
13422 matches_old = false;
13423 }
13424 if (mode & 0x0004) != 0
13425 && bus
13426 .read_word(table_ptr + Self::ST_ELEMENT_SIZE_OFFSET)
13427 != bus.read_word(old_style_ptr + 4)
13428 {
13429 matches_old = false;
13430 }
13431 if (mode & 0x0008) != 0
13432 && (bus.read_word(
13433 table_ptr + Self::ST_ELEMENT_COLOR_OFFSET,
13434 ) != bus.read_word(old_style_ptr + 6)
13435 || bus.read_word(
13436 table_ptr + Self::ST_ELEMENT_COLOR_OFFSET + 2,
13437 ) != bus.read_word(old_style_ptr + 8)
13438 || bus.read_word(
13439 table_ptr + Self::ST_ELEMENT_COLOR_OFFSET + 4,
13440 ) != bus.read_word(old_style_ptr + 10))
13441 {
13442 matches_old = false;
13443 }
13444 if matches_old {
13445 if (mode & 0x0001) != 0 {
13446 bus.write_word(
13447 table_ptr + Self::ST_ELEMENT_FONT_OFFSET,
13448 bus.read_word(new_style_ptr),
13449 );
13450 }
13451 if (mode & 0x0002) != 0 {
13452 bus.write_byte(
13453 table_ptr + Self::ST_ELEMENT_FACE_OFFSET,
13454 bus.read_byte(new_style_ptr + 2),
13455 );
13456 }
13457 if (mode & 0x0004) != 0 {
13458 bus.write_word(
13459 table_ptr + Self::ST_ELEMENT_SIZE_OFFSET,
13460 bus.read_word(new_style_ptr + 4),
13461 );
13462 }
13463 if (mode & 0x0008) != 0 {
13464 bus.write_word(
13465 table_ptr + Self::ST_ELEMENT_COLOR_OFFSET,
13466 bus.read_word(new_style_ptr + 6),
13467 );
13468 bus.write_word(
13469 table_ptr + Self::ST_ELEMENT_COLOR_OFFSET + 2,
13470 bus.read_word(new_style_ptr + 8),
13471 );
13472 bus.write_word(
13473 table_ptr + Self::ST_ELEMENT_COLOR_OFFSET + 4,
13474 bus.read_word(new_style_ptr + 10),
13475 );
13476 }
13477 }
13478 }
13479 }
13480 }
13481 }
13482 let _ = mode;
13483 cpu.write_reg(Register::A7, sp + 18);
13484 }
13485 0x0003 => {
13486 let te_handle = bus.read_long(sp + 2);
13490 let font_ascent_ptr = bus.read_long(sp + 6);
13491 let line_height_ptr = bus.read_long(sp + 10);
13492 let attrs_ptr = bus.read_long(sp + 14);
13493 let _sel = bus.read_word(sp + 18) as i16;
13494 let (font, face, size, color, line_height, font_ascent) =
13495 self.te_primary_style(bus, te_handle);
13496 if attrs_ptr != 0 {
13497 bus.write_word(attrs_ptr, font as u16);
13498 bus.write_word(attrs_ptr + 2, face as u16);
13499 bus.write_word(attrs_ptr + 4, size as u16);
13500 bus.write_word(attrs_ptr + 6, color.0);
13501 bus.write_word(attrs_ptr + 8, color.1);
13502 bus.write_word(attrs_ptr + 10, color.2);
13503 }
13504 if line_height_ptr != 0 {
13505 bus.write_word(line_height_ptr, line_height as u16);
13506 }
13507 if font_ascent_ptr != 0 {
13508 bus.write_word(font_ascent_ptr, font_ascent as u16);
13509 }
13510 cpu.write_reg(Register::A7, sp + 20);
13511 }
13512 0x0004 => {
13513 let te_handle = bus.read_long(sp + 2);
13516 bus.write_long(sp + 6, Self::te_style_handle(bus, te_handle));
13517 cpu.write_reg(Register::A7, sp + 6);
13518 }
13519 0x0005 => {
13520 let te_handle = bus.read_long(sp + 2);
13523 let style_handle = bus.read_long(sp + 6);
13524 Self::te_write_style_handle(bus, te_handle, style_handle);
13525 cpu.write_reg(Register::A7, sp + 10);
13526 }
13527 0x0006 => {
13528 let te_handle = bus.read_long(sp + 2);
13531 let style_handle = Self::te_style_handle(bus, te_handle);
13532 let mut result = 0;
13533 if style_handle != 0 {
13534 let style_ptr = bus.read_long(style_handle);
13535 if style_ptr != 0 {
13536 let null_style_handle =
13537 bus.read_long(style_ptr + Self::TE_STYLE_NULL_STYLE_OFFSET);
13538 if null_style_handle != 0 {
13539 let null_style_ptr = bus.read_long(null_style_handle);
13540 if null_style_ptr != 0 {
13541 let scrap_handle = bus.read_long(
13542 null_style_ptr + Self::NULL_STYLE_SCRAP_OFFSET,
13543 );
13544 result = Self::duplicate_handle_data(bus, scrap_handle);
13545 }
13546 }
13547 }
13548 }
13549 bus.write_long(sp + 6, result);
13550 cpu.write_reg(Register::A7, sp + 6);
13551 }
13552 0x0007 => {
13553 let te_handle = bus.read_long(sp + 2);
13556 let style_scrap = bus.read_long(sp + 6);
13557 let length = bus.read_long(sp + 10) as usize;
13558 let text_ptr = bus.read_long(sp + 14);
13559 if text_ptr != 0 && length != 0 {
13560 let text = bus.read_bytes(text_ptr, length);
13561 let insert_start = {
13562 let te_ptr = Self::te_record_ptr(bus, te_handle);
13563 let text_len = Self::te_text_length(bus, te_handle);
13564 if te_ptr != 0 {
13565 let mut sel_start =
13566 bus.read_word(te_ptr + Self::TE_SEL_START_OFFSET) as usize;
13567 let mut sel_end =
13568 bus.read_word(te_ptr + Self::TE_SEL_END_OFFSET) as usize;
13569 sel_start = sel_start.min(text_len);
13570 sel_end = sel_end.min(text_len);
13571 if sel_end < sel_start {
13572 std::mem::swap(&mut sel_start, &mut sel_end);
13573 }
13574 sel_start
13575 } else {
13576 0
13577 }
13578 };
13579 let preview_len = text.len().min(64);
13580 if trace_textedit_enabled() {
13581 eprintln!(
13582 "[TE] TEStyleInsert hTE=${:08X} hST=${:08X} len={} text_ptr=${:08X} preview={:?}",
13583 te_handle,
13584 style_scrap,
13585 length,
13586 text_ptr,
13587 String::from_utf8_lossy(&text[..preview_len])
13588 );
13589 }
13590 self.te_insert_text(bus, te_handle, &text);
13591 let effective_style_scrap = if style_scrap != 0 {
13597 style_scrap
13598 } else {
13599 Self::te_null_style_scrap_handle(bus, te_handle)
13600 };
13601 if self.te_apply_style_scrap_to_range(
13602 bus,
13603 te_handle,
13604 effective_style_scrap,
13605 insert_start,
13606 text.len(),
13607 ) {
13608 self.te_recalculate_layout(bus, te_handle);
13609 }
13610 self.draw_te_contents(cpu, bus, te_handle);
13611 } else if text_ptr != 0 && bus.read_byte(text_ptr) == 0 {
13612 cpu.write_reg(Register::A3, 0);
13618 }
13619 cpu.write_reg(Register::A7, sp + 18);
13620 }
13621 0x0008 => {
13622 let te_handle = bus.read_long(sp + 2);
13627 let offset = bus.read_word(sp + 6) as i16;
13628 let te_ptr = Self::te_record_ptr(bus, te_handle);
13629 let result_addr = sp + 8;
13630 if te_ptr != 0 {
13631 let text_len = Self::te_text_length(bus, te_handle);
13632 let clamped = i32::from(offset).clamp(0, text_len as i32) as usize;
13633 let line_index = Self::te_char_to_line_index(bus, te_handle, clamped);
13634 let line_start = Self::te_line_starts(bus, te_handle)
13635 .get(line_index)
13636 .copied()
13637 .unwrap_or(0);
13638 let (top, x) = self.te_char_to_point(bus, te_handle, clamped);
13639 let y = top.saturating_add(Self::te_ascent_for_line(
13640 bus, te_handle, line_index,
13641 ));
13642 if trace_textedit_enabled() {
13643 let starts = Self::te_line_starts(bus, te_handle);
13644 let pc = cpu.read_reg(Register::PC);
13645 eprintln!(
13646 "[TE] TEGetPoint hTE=${:08X} offset={} line={} start={} point_offset={} point=({}, {}) pc=${:08X} next=[{:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X}]",
13647 te_handle,
13648 clamped,
13649 line_index,
13650 line_start,
13651 clamped,
13652 y,
13653 x,
13654 pc,
13655 bus.read_word(pc),
13656 bus.read_word(pc + 2),
13657 bus.read_word(pc + 4),
13658 bus.read_word(pc + 6),
13659 bus.read_word(pc + 8),
13660 bus.read_word(pc + 10),
13661 bus.read_word(pc + 12),
13662 bus.read_word(pc + 14),
13663 bus.read_word(pc + 16),
13664 bus.read_word(pc + 18),
13665 bus.read_word(pc + 20),
13666 bus.read_word(pc + 22),
13667 bus.read_word(pc + 24),
13668 bus.read_word(pc + 26),
13669 bus.read_word(pc + 28),
13670 bus.read_word(pc + 30),
13671 bus.read_word(pc + 32),
13672 bus.read_word(pc + 34),
13673 bus.read_word(pc + 36),
13674 bus.read_word(pc + 38)
13675 );
13676 eprintln!(
13677 "[TE] TEGetPoint layout nLines={} teLength={} lineStarts={:?}",
13678 starts.len().saturating_sub(1),
13679 text_len,
13680 starts
13681 );
13682 eprintln!(
13683 "[TE] TEGetPoint helper@35614=[{:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X}]",
13684 bus.read_word(0x0003_5614),
13685 bus.read_word(0x0003_5616),
13686 bus.read_word(0x0003_5618),
13687 bus.read_word(0x0003_561A),
13688 bus.read_word(0x0003_561C),
13689 bus.read_word(0x0003_561E),
13690 bus.read_word(0x0003_5620),
13691 bus.read_word(0x0003_5622),
13692 bus.read_word(0x0003_5624),
13693 bus.read_word(0x0003_5626),
13694 bus.read_word(0x0003_5628),
13695 bus.read_word(0x0003_562A)
13696 );
13697 let table_base = cpu.read_reg(Register::A5).wrapping_sub(0x37B4);
13698 let rect_table = bus.read_long(table_base);
13699 let rect_ptr = rect_table.wrapping_add(21 * 8);
13700 let a6 = cpu.read_reg(Register::A6);
13701 let ret = bus.read_long(a6 + 4);
13702 eprintln!(
13703 "[TE] TEGetPoint rect21 table=${:08X} rect_ptr=${:08X} rect=({},{},{},{}) a4=${:08X} d5={} d6={} d7={} a6=${:08X} ret=${:08X}",
13704 rect_table,
13705 rect_ptr,
13706 bus.read_word(rect_ptr) as i16,
13707 bus.read_word(rect_ptr + 2) as i16,
13708 bus.read_word(rect_ptr + 4) as i16,
13709 bus.read_word(rect_ptr + 6) as i16,
13710 cpu.read_reg(Register::A4),
13711 cpu.read_reg(Register::D5) as i32,
13712 cpu.read_reg(Register::D6) as i32,
13713 cpu.read_reg(Register::D7) as i32,
13714 a6,
13715 ret
13716 );
13717 eprintln!(
13718 "[TE] TEGetPoint caller@{:08X}=[{:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X}]",
13719 ret,
13720 bus.read_word(ret),
13721 bus.read_word(ret + 2),
13722 bus.read_word(ret + 4),
13723 bus.read_word(ret + 6),
13724 bus.read_word(ret + 8),
13725 bus.read_word(ret + 10),
13726 bus.read_word(ret + 12),
13727 bus.read_word(ret + 14),
13728 bus.read_word(ret + 16),
13729 bus.read_word(ret + 18),
13730 bus.read_word(ret + 20),
13731 bus.read_word(ret + 22)
13732 );
13733 let caller_start = ret.wrapping_sub(0x20);
13734 eprintln!(
13735 "[TE] TEGetPoint caller_pre@{:08X}=[{:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X}]",
13736 caller_start,
13737 bus.read_word(caller_start),
13738 bus.read_word(caller_start + 2),
13739 bus.read_word(caller_start + 4),
13740 bus.read_word(caller_start + 6),
13741 bus.read_word(caller_start + 8),
13742 bus.read_word(caller_start + 10),
13743 bus.read_word(caller_start + 12),
13744 bus.read_word(caller_start + 14),
13745 bus.read_word(caller_start + 16),
13746 bus.read_word(caller_start + 18),
13747 bus.read_word(caller_start + 20),
13748 bus.read_word(caller_start + 22),
13749 bus.read_word(caller_start + 24),
13750 bus.read_word(caller_start + 26),
13751 bus.read_word(caller_start + 28),
13752 bus.read_word(caller_start + 30)
13753 );
13754 eprintln!(
13755 "[TE] TEGetPoint caller_block@00035F7C=[{:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X}]",
13756 bus.read_word(0x0003_5F7C),
13757 bus.read_word(0x0003_5F7E),
13758 bus.read_word(0x0003_5F80),
13759 bus.read_word(0x0003_5F82),
13760 bus.read_word(0x0003_5F84),
13761 bus.read_word(0x0003_5F86),
13762 bus.read_word(0x0003_5F88),
13763 bus.read_word(0x0003_5F8A),
13764 bus.read_word(0x0003_5F8C),
13765 bus.read_word(0x0003_5F8E),
13766 bus.read_word(0x0003_5F90),
13767 bus.read_word(0x0003_5F92),
13768 bus.read_word(0x0003_5F94),
13769 bus.read_word(0x0003_5F96),
13770 bus.read_word(0x0003_5F98),
13771 bus.read_word(0x0003_5F9A),
13772 bus.read_word(0x0003_5F9C),
13773 bus.read_word(0x0003_5F9E),
13774 bus.read_word(0x0003_5FA0),
13775 bus.read_word(0x0003_5FA2),
13776 bus.read_word(0x0003_5FA4),
13777 bus.read_word(0x0003_5FA6),
13778 bus.read_word(0x0003_5FA8),
13779 bus.read_word(0x0003_5FAA),
13780 bus.read_word(0x0003_5FAC),
13781 bus.read_word(0x0003_5FAE),
13782 bus.read_word(0x0003_5FB0),
13783 bus.read_word(0x0003_5FB2),
13784 bus.read_word(0x0003_5FB4),
13785 bus.read_word(0x0003_5FB6),
13786 bus.read_word(0x0003_5FB8),
13787 bus.read_word(0x0003_5FBA)
13788 );
13789 eprintln!(
13790 "[TE] TEGetPoint branch_c6@00035FC6=[{:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X}] branch_e2@00035FE2=[{:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X}]",
13791 bus.read_word(0x0003_5FC6),
13792 bus.read_word(0x0003_5FC8),
13793 bus.read_word(0x0003_5FCA),
13794 bus.read_word(0x0003_5FCC),
13795 bus.read_word(0x0003_5FCE),
13796 bus.read_word(0x0003_5FD0),
13797 bus.read_word(0x0003_5FD2),
13798 bus.read_word(0x0003_5FD4),
13799 bus.read_word(0x0003_5FE2),
13800 bus.read_word(0x0003_5FE4),
13801 bus.read_word(0x0003_5FE6),
13802 bus.read_word(0x0003_5FE8),
13803 bus.read_word(0x0003_5FEA),
13804 bus.read_word(0x0003_5FEC),
13805 bus.read_word(0x0003_5FEE),
13806 bus.read_word(0x0003_5FF0)
13807 );
13808 eprintln!(
13809 "[TE] TEGetPoint helper_fn@00036B9A=[{:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X}]",
13810 bus.read_word(0x0003_6B9A),
13811 bus.read_word(0x0003_6B9C),
13812 bus.read_word(0x0003_6B9E),
13813 bus.read_word(0x0003_6BA0),
13814 bus.read_word(0x0003_6BA2),
13815 bus.read_word(0x0003_6BA4),
13816 bus.read_word(0x0003_6BA6),
13817 bus.read_word(0x0003_6BA8),
13818 bus.read_word(0x0003_6BAA),
13819 bus.read_word(0x0003_6BAC),
13820 bus.read_word(0x0003_6BAE),
13821 bus.read_word(0x0003_6BB0),
13822 bus.read_word(0x0003_6BB2),
13823 bus.read_word(0x0003_6BB4),
13824 bus.read_word(0x0003_6BB6),
13825 bus.read_word(0x0003_6BB8),
13826 bus.read_word(0x0003_6BBA),
13827 bus.read_word(0x0003_6BBC),
13828 bus.read_word(0x0003_6BBE),
13829 bus.read_word(0x0003_6BC0),
13830 bus.read_word(0x0003_6BC2),
13831 bus.read_word(0x0003_6BC4),
13832 bus.read_word(0x0003_6BC6),
13833 bus.read_word(0x0003_6BC8)
13834 );
13835 }
13836 bus.write_word(result_addr, y as u16);
13837 bus.write_word(result_addr + 2, x as u16);
13838 } else {
13839 bus.write_word(result_addr, 0);
13840 bus.write_word(result_addr + 2, 0);
13841 }
13842 cpu.write_reg(Register::A7, result_addr);
13843 }
13844 0x0009 => {
13845 let te_handle = bus.read_long(sp + 2);
13850 let mut start_line = bus.read_long(sp + 6) as i32;
13851 let mut end_line = bus.read_long(sp + 10) as i32;
13852 let te_ptr = Self::te_record_ptr(bus, te_handle);
13853 let n_lines = if te_ptr != 0 {
13854 bus.read_word(te_ptr + Self::TE_N_LINES_OFFSET) as i32
13855 } else {
13856 0
13857 };
13858 if start_line > 0 {
13859 start_line -= 1;
13860 } else {
13861 start_line = 0;
13862 }
13863 end_line = end_line.min(n_lines);
13864 if end_line < 0 {
13865 end_line = 0;
13866 } else if end_line > 0 {
13867 end_line -= 1;
13868 }
13869 if start_line > end_line {
13870 std::mem::swap(&mut start_line, &mut end_line);
13871 }
13872
13873 let text_bytes = Self::te_text_bytes(bus, te_handle);
13874 let line_starts = Self::te_line_starts(bus, te_handle);
13875 if !text_bytes.is_empty() {
13876 while end_line >= start_line {
13877 let current = end_line as usize;
13878 let Some(&line_start) = line_starts.get(current) else {
13879 break;
13880 };
13881 let line_end = line_starts
13882 .get(current + 1)
13883 .copied()
13884 .unwrap_or(text_bytes.len());
13885 let blank_trailing_line = current + 1 == line_starts.len() - 1
13886 && line_start < line_end
13887 && text_bytes[line_start..line_end]
13888 .iter()
13889 .all(|&b| matches!(b, b'\r' | b'\n'));
13890 if blank_trailing_line {
13891 end_line -= 1;
13892 } else {
13893 break;
13894 }
13895 }
13896 }
13897
13898 let mut total_height = 0i32;
13899 if end_line >= start_line {
13900 for current_line in
13901 start_line.max(0) as usize..=end_line.max(0) as usize
13902 {
13903 total_height += i32::from(Self::te_height_for_line(
13904 bus,
13905 te_handle,
13906 current_line,
13907 ));
13908 }
13909 }
13910 if trace_textedit_enabled() {
13911 eprintln!(
13912 "[TE] TEGetHeight hTE=${:08X} start_line={} end_line={} result={}",
13913 te_handle, start_line, end_line, total_height
13914 );
13915 }
13916 bus.write_long(sp + 14, total_height as u32);
13917 cpu.write_reg(Register::A7, sp + 14);
13918 }
13919 0x000A => {
13920 let te_handle = bus.read_long(sp + 2);
13925 let style_ptr = bus.read_long(sp + 6);
13926 let mode_ptr = bus.read_long(sp + 10);
13927 let result_addr = sp + 14;
13928 let requested_mode = if mode_ptr != 0 {
13929 bus.read_word(mode_ptr)
13930 } else {
13931 0
13932 };
13933 if style_ptr != 0 {
13934 let (tx_font, tx_face, tx_size, color, _, _) =
13935 self.te_primary_style(bus, te_handle);
13936 if (requested_mode & 0x0001) != 0 {
13937 bus.write_word(style_ptr, tx_font as u16);
13938 }
13939 if (requested_mode & 0x0002) != 0 {
13940 bus.write_byte(style_ptr + 2, tx_face as u8);
13941 }
13942 if (requested_mode & 0x0004) != 0 {
13943 bus.write_word(style_ptr + 4, tx_size as u16);
13944 }
13945 if (requested_mode & 0x0008) != 0 {
13946 bus.write_word(style_ptr + 6, color.0);
13947 bus.write_word(style_ptr + 8, color.1);
13948 bus.write_word(style_ptr + 10, color.2);
13949 }
13950 }
13951 bus.write_word(result_addr, 0xFFFF);
13952 cpu.write_reg(Register::A7, result_addr);
13953 }
13954 0x000B => {
13955 let te_handle = bus.read_long(sp + 2);
13961 let redraw = bus.read_byte(sp + 6) != 0;
13963 let new_styles = bus.read_long(sp + 8);
13964 let range_end = bus.read_long(sp + 12) as usize;
13965 let range_start = bus.read_long(sp + 16) as usize;
13966 let (range_start, range_end) = if range_end < range_start {
13967 (range_end, range_start)
13968 } else {
13969 (range_start, range_end)
13970 };
13971 if self.te_apply_style_scrap_to_range(
13972 bus,
13973 te_handle,
13974 new_styles,
13975 range_start,
13976 range_end.saturating_sub(range_start),
13977 ) {
13978 self.te_recalculate_layout(bus, te_handle);
13979 if redraw {
13980 self.draw_te_contents(cpu, bus, te_handle);
13981 }
13982 }
13983 cpu.write_reg(Register::A7, sp + 20);
13984 }
13985 0x000C => {
13986 let addr_ptr = bus.read_long(sp + 6);
13996 if addr_ptr != 0 {
13997 bus.write_long(addr_ptr, 0);
13998 }
13999 cpu.write_reg(Register::A7, sp + 12);
14000 }
14001 0x000D => {
14002 let te_handle = bus.read_long(sp + 2);
14013 let style_handle = Self::te_style_handle(bus, te_handle);
14014 let mut count: u32 = 1;
14015 if style_handle != 0 {
14016 let style_ptr_record = bus.read_long(style_handle);
14017 if style_ptr_record != 0 {
14018 let n_runs = bus
14019 .read_word(style_ptr_record + Self::TE_STYLE_N_RUNS_OFFSET)
14020 as u32;
14021 count = n_runs.max(1);
14022 }
14023 }
14024 bus.write_long(sp + 14, count);
14025 cpu.write_reg(Register::A7, sp + 14);
14026 }
14027 0x000E => {
14028 let te_handle = bus.read_long(sp + 2);
14033 let action = bus.read_word(sp + 6) as i16;
14034 let feature = bus.read_word(sp + 8);
14035 let was_set = self.te_feature_bit(te_handle, feature);
14036 if matches!(
14037 feature,
14038 Self::TE_FEATURE_AUTO_SCROLL
14039 | Self::TE_FEATURE_TEXT_BUFFERING
14040 | Self::TE_FEATURE_OUTLINE_HILITE
14041 | Self::TE_FEATURE_INLINE_INPUT
14042 | Self::TE_FEATURE_USE_TEXT_SERVICES
14043 ) {
14044 match action {
14045 Self::TE_BIT_CLEAR => {
14046 self.set_te_feature_bit(te_handle, feature, false)
14047 }
14048 Self::TE_BIT_SET => {
14049 self.set_te_feature_bit(te_handle, feature, true)
14050 }
14051 Self::TE_BIT_TEST => {}
14052 _ => {}
14053 }
14054 }
14055 bus.write_word(sp + 10, if was_set { 1 } else { 0 });
14056 cpu.write_reg(Register::A7, sp + 10);
14057 }
14058 _ => return None,
14059 }
14060 Ok(())
14061 }
14062
14063 (true, 0x1CD) => {
14073 let sp = cpu.read_reg(Register::A7);
14074 let te_handle = bus.read_long(sp);
14075 if te_handle != 0 {
14076 let te_ptr = bus.read_long(te_handle);
14077 if te_ptr != 0 {
14078 let h_text = bus.read_long(te_ptr + Self::TE_HTEXT_OFFSET);
14080 if h_text != 0 {
14081 let text_ptr = bus.read_long(h_text);
14082 if text_ptr != 0 {
14083 bus.free(text_ptr);
14084 }
14085 bus.free(h_text);
14086 }
14087 if Self::te_is_styled_record(bus, te_ptr) {
14089 let style_handle = bus.read_long(te_ptr + Self::TE_TX_FONT_OFFSET);
14090 if style_handle != 0 {
14091 let style_ptr = bus.read_long(style_handle);
14092 if style_ptr != 0 {
14093 bus.free(style_ptr);
14094 }
14095 bus.free(style_handle);
14096 }
14097 }
14098 bus.free(te_ptr);
14099 }
14100 bus.free(te_handle);
14101 self.textedit_states.remove(&te_handle);
14102 }
14103 cpu.write_reg(Register::A7, sp + 4);
14104 Ok(())
14105 }
14106
14107 (true, 0x1CF) => {
14139 let sp = cpu.read_reg(Register::A7);
14140 let te_handle = bus.read_long(sp);
14141 let length = bus.read_long(sp + 4) as usize;
14142 let text_ptr = bus.read_long(sp + 8);
14143 if text_ptr != 0 && length != 0 {
14144 let text = bus.read_bytes(text_ptr, length);
14145 self.te_set_text_contents(bus, te_handle, &text);
14146 } else {
14147 self.te_set_text_contents(bus, te_handle, &[]);
14148 }
14149 cpu.write_reg(Register::A7, sp + 12);
14150 Ok(())
14151 }
14152
14153 (true, 0x1CB) => {
14182 let sp = cpu.read_reg(Register::A7);
14183 let te_handle = bus.read_long(sp);
14184 bus.write_long(sp + 4, Self::te_text_handle(bus, te_handle));
14185 cpu.write_reg(Register::A7, sp + 4);
14186 Ok(())
14187 }
14188
14189 (true, 0x1CE) => {
14245 let sp = cpu.read_reg(Register::A7);
14246 let align = bus.read_word(sp) as i16;
14247 let box_ptr = bus.read_long(sp + 2);
14248 let box_top = bus.read_word(box_ptr) as i16;
14249 let box_left = bus.read_word(box_ptr + 2) as i16;
14250 let box_bottom = bus.read_word(box_ptr + 4) as i16;
14251 let box_right = bus.read_word(box_ptr + 6) as i16;
14252 let length = bus.read_long(sp + 6) as usize;
14253 let text_ptr = bus.read_long(sp + 10);
14254 cpu.write_reg(Register::A7, sp + 14);
14255
14256 if trace_dialog_text_inline_enabled() {
14257 let mut stack_bytes = [0u8; 24];
14258 for (i, byte) in stack_bytes.iter_mut().enumerate() {
14259 *byte = bus.read_byte(sp + i as u32);
14260 }
14261 let preview_len = length.min(64);
14262 let preview = if text_ptr != 0 {
14263 bus.read_bytes(text_ptr, preview_len)
14264 } else {
14265 Vec::new()
14266 };
14267 eprintln!(
14268 "[DIALOG-TEXT] TETextBox params current_port=${:08X} sp=${:08X} stack={:02X?} text_ptr=${:08X} len={} box=({},{}..{},{} ) align={} preview={:02X?}",
14269 self.current_port,
14270 sp,
14271 stack_bytes,
14272 text_ptr,
14273 length,
14274 box_top,
14275 box_left,
14276 box_bottom,
14277 box_right,
14278 align,
14279 preview,
14280 );
14281 }
14282
14283 if box_right > box_left && box_bottom > box_top {
14284 self.draw_rect(
14289 cpu,
14290 bus,
14291 &Rect {
14292 top: box_top,
14293 left: box_left,
14294 bottom: box_bottom,
14295 right: box_right,
14296 },
14297 ShapeOp::Erase,
14298 );
14299
14300 if text_ptr != 0 && length > 0 {
14301 let text_bytes = bus.read_bytes(text_ptr, length);
14303
14304 if trace_dialog_text_inline_enabled() {
14305 let preview_len = text_bytes.len().min(160);
14306 let first_char =
14307 text_bytes.first().copied().map(char::from).unwrap_or('\0');
14308 let first_glyph = crate::quickdraw::text::get_glyph(
14309 self.tx_font,
14310 self.tx_size,
14311 first_char,
14312 )
14313 .is_some();
14314 eprintln!(
14315 "[DIALOG-TEXT] TETextBox current_port=${:08X} box=({},{}..{},{} ) align={} len={} txFont={} txFace=${:04X} txMode={} txSize={} firstChar={:?} firstGlyph={} text=\"{}\"",
14316 self.current_port,
14317 box_top,
14318 box_left,
14319 box_bottom,
14320 box_right,
14321 align,
14322 length,
14323 self.tx_font,
14324 self.tx_face,
14325 self.tx_mode,
14326 self.tx_size,
14327 first_char,
14328 first_glyph,
14329 String::from_utf8_lossy(&text_bytes[..preview_len]),
14330 );
14331 }
14332
14333 let font_id = self.tx_font;
14335 let font_size = self.tx_size;
14336 let advance_extra = self.advance_extra();
14337 let missing_advance = self.missing_glyph_advance();
14338
14339 let metrics = crate::quickdraw::text::get_font_metrics(font_id, font_size);
14340 let line_height = metrics.ascent + metrics.descent + metrics.leading.max(2);
14341 let box_width = box_right - box_left;
14342
14343 let measure = |start: usize, end: usize| -> i16 {
14345 let mut w = 0i16;
14346 for &b in &text_bytes[start..end] {
14347 let ch = b as char;
14348 if let Some((g, _)) =
14349 crate::quickdraw::text::get_glyph(font_id, font_size, ch)
14350 {
14351 w += g.advance as i16 + advance_extra;
14352 } else {
14353 w += missing_advance;
14354 }
14355 }
14356 w
14357 };
14358
14359 let mut lines: Vec<(usize, usize)> = Vec::new();
14362 let mut line_start = 0usize;
14363 let mut last_break = 0usize;
14364 let mut line_width = 0i16;
14365
14366 for (i, &b) in text_bytes.iter().enumerate() {
14367 if b == b'\r' || b == b'\n' {
14368 lines.push((line_start, i));
14369 line_start = i + 1;
14370 last_break = line_start;
14371 line_width = 0;
14372 continue;
14373 }
14374 let ch = b as char;
14375 let char_w = if let Some((g, _)) =
14376 crate::quickdraw::text::get_glyph(font_id, font_size, ch)
14377 {
14378 g.advance as i16 + advance_extra
14379 } else {
14380 missing_advance
14381 };
14382 line_width += char_w;
14383 if b == b' ' {
14384 last_break = i + 1;
14385 }
14386 if line_width > box_width && i > line_start {
14387 if last_break > line_start {
14388 lines.push((line_start, last_break - 1));
14389 line_start = last_break;
14390 } else {
14391 lines.push((line_start, i));
14392 line_start = i;
14393 }
14394 last_break = line_start;
14395 line_width = measure(line_start, i + 1);
14396 }
14397 }
14398 if line_start < text_bytes.len() {
14399 lines.push((line_start, text_bytes.len()));
14400 }
14401
14402 let mut y = box_top + metrics.ascent;
14404 for (start, end) in &lines {
14405 if y + metrics.descent > box_bottom {
14406 break;
14407 }
14408 let mut trimmed_end = *end;
14409 while trimmed_end > *start && text_bytes[trimmed_end - 1] == b' ' {
14410 trimmed_end -= 1;
14411 }
14412 let lw = measure(*start, trimmed_end);
14413 let x = Self::te_line_origin_x(align, box_left, box_right, lw);
14419 self.pn_loc = (y, x);
14420 for &byte in &text_bytes[*start..trimmed_end] {
14421 self.draw_char(cpu, bus, byte as char);
14422 }
14423 y += line_height;
14424 }
14425 }
14426 }
14427 self.refresh_visible_dialog_snapshot_for_port(bus, self.current_port);
14428 Ok(())
14429 }
14430
14431 (true, 0x1D0) => {
14435 let sp = cpu.read_reg(Register::A7);
14436 let te_handle = bus.read_long(sp);
14437 let te_ptr = Self::te_record_ptr(bus, te_handle);
14438 if te_ptr != 0 {
14439 let text_len =
14440 Self::te_text_length(bus, te_handle).min(u16::MAX as usize) as u16;
14441 bus.write_word(te_ptr + Self::TE_LENGTH_OFFSET, text_len);
14442 self.te_recalculate_layout(bus, te_handle);
14443 }
14444 cpu.write_reg(Register::A7, sp + 4);
14445 Ok(())
14446 }
14447
14448 (true, 0x1D1) => {
14453 let sp = cpu.read_reg(Register::A7);
14454 let te_handle = bus.read_long(sp);
14455 let te_ptr = Self::te_record_ptr(bus, te_handle);
14456 if te_ptr != 0 {
14457 let te_length = u32::from(bus.read_word(te_ptr + Self::TE_LENGTH_OFFSET));
14458 let sel_end = bus
14462 .read_long(sp + 4)
14463 .min(u32::from(u16::MAX))
14464 .min(te_length);
14465 let sel_start = bus.read_long(sp + 8).min(u32::from(u16::MAX));
14466 bus.write_word(te_ptr + Self::TE_SEL_START_OFFSET, sel_start as u16);
14467 bus.write_word(te_ptr + Self::TE_SEL_END_OFFSET, sel_end as u16);
14468 }
14469 cpu.write_reg(Register::A7, sp + 12);
14470 Ok(())
14471 }
14472
14473 (true, 0x1D3) => {
14519 let sp = cpu.read_reg(Register::A7);
14520 let hte_at_sp = bus.read_long(sp);
14558 let ptr_candidate = bus.read_long(sp + 4);
14559 let looks_like_c_form = ptr_candidate != 0
14560 && (ptr_candidate & 1) == 0
14561 && (0x0000_0010..0x0400_0000).contains(&ptr_candidate)
14562 && {
14563 let top = bus.read_word(ptr_candidate) as i16;
14564 let left = bus.read_word(ptr_candidate + 2) as i16;
14565 let bottom = bus.read_word(ptr_candidate + 4) as i16;
14566 let right = bus.read_word(ptr_candidate + 6) as i16;
14567 top <= bottom
14568 && left <= right
14569 && (-32000..=32000).contains(&top)
14570 && (-32000..=32000).contains(&left)
14571 && (-32000..=32000).contains(&bottom)
14572 && (-32000..=32000).contains(&right)
14573 && !(top == 0 && left == 0 && bottom == 0 && right == 0)
14574 };
14575 let (te_handle, stack_pop) = if looks_like_c_form {
14576 (hte_at_sp, 8)
14577 } else {
14578 (hte_at_sp, 12)
14579 };
14580 let te_ptr = Self::te_record_ptr(bus, te_handle);
14581 if te_ptr != 0 {
14582 if trace_textedit_enabled() {
14583 let dest_rect = Self::te_read_rect(bus, te_ptr + Self::TE_DEST_RECT_OFFSET);
14584 let view_rect = Self::te_read_rect(bus, te_ptr + Self::TE_VIEW_RECT_OFFSET);
14585 eprintln!(
14586 "[TE] TEUpdate hTE=${:08X} dest=({},{},{},{}) view=({},{},{},{})",
14587 te_handle,
14588 dest_rect.0,
14589 dest_rect.1,
14590 dest_rect.2,
14591 dest_rect.3,
14592 view_rect.0,
14593 view_rect.1,
14594 view_rect.2,
14595 view_rect.3
14596 );
14597 }
14598 self.draw_te_contents(cpu, bus, te_handle);
14599 }
14600 cpu.write_reg(Register::A7, sp + stack_pop);
14601 Ok(())
14602 }
14603
14604 (true, 0x1D4) => {
14673 let sp = cpu.read_reg(Register::A7);
14674 if trace_textedit_enabled() {
14675 let te_handle = bus.read_long(sp);
14676 let extend = bus.read_byte(sp + 4) != 0;
14678 let pt_v = bus.read_word(sp + 6) as i16;
14679 let pt_h = bus.read_word(sp + 8) as i16;
14680 eprintln!(
14681 "[TE] TEClick hTE=${:08X} extend={} pt=({}, {})",
14682 te_handle, extend, pt_v, pt_h
14683 );
14684 }
14685 cpu.write_reg(Register::A7, sp + 10);
14686 Ok(())
14687 }
14688
14689 (true, 0x1D5) => {
14698 let sp = cpu.read_reg(Register::A7);
14699 let te_handle = bus.read_long(sp);
14700 let te_ptr = Self::te_record_ptr(bus, te_handle);
14701 if te_ptr != 0 {
14702 let sel_start = bus.read_word(te_ptr + Self::TE_SEL_START_OFFSET) as usize;
14703 let sel_end = bus.read_word(te_ptr + Self::TE_SEL_END_OFFSET) as usize;
14704 let (s, e) = if sel_start <= sel_end {
14705 (sel_start, sel_end)
14706 } else {
14707 (sel_end, sel_start)
14708 };
14709 if s != e {
14710 use crate::memory::globals::addr;
14711 let existing = Self::te_text_bytes(bus, te_handle);
14712 let s = s.min(existing.len());
14713 let e = e.min(existing.len());
14714 let selected = &existing[s..e];
14715 let mut scrap_handle = bus.read_long(addr::TE_SCRP_HANDLE);
14716 if scrap_handle == 0 {
14717 scrap_handle = Self::allocate_handle_with_data(bus, 0);
14718 bus.write_long(addr::TE_SCRP_HANDLE, scrap_handle);
14719 }
14720 let text_ptr =
14721 Self::ensure_text_handle_size(bus, scrap_handle, selected.len());
14722 if text_ptr != 0 && !selected.is_empty() {
14723 bus.write_bytes(text_ptr, selected);
14724 }
14725 bus.write_word(
14726 addr::TE_SCRP_LENGTH,
14727 selected.len().min(u16::MAX as usize) as u16,
14728 );
14729 }
14730 }
14731 cpu.write_reg(Register::A7, sp + 4);
14732 Ok(())
14733 }
14734
14735 (true, 0x1D6) => {
14744 let sp = cpu.read_reg(Register::A7);
14745 let te_handle = bus.read_long(sp);
14746 let te_ptr = Self::te_record_ptr(bus, te_handle);
14747 if te_ptr != 0 {
14748 let sel_start = bus.read_word(te_ptr + Self::TE_SEL_START_OFFSET) as usize;
14749 let sel_end = bus.read_word(te_ptr + Self::TE_SEL_END_OFFSET) as usize;
14750 let (s, e) = if sel_start <= sel_end {
14751 (sel_start, sel_end)
14752 } else {
14753 (sel_end, sel_start)
14754 };
14755 if s != e {
14756 let existing = Self::te_text_bytes(bus, te_handle);
14757 let s = s.min(existing.len());
14758 let e = e.min(existing.len());
14759 {
14762 use crate::memory::globals::addr;
14763 let selected = &existing[s..e];
14764 let mut scrap_handle = bus.read_long(addr::TE_SCRP_HANDLE);
14765 if scrap_handle == 0 {
14766 scrap_handle = Self::allocate_handle_with_data(bus, 0);
14767 bus.write_long(addr::TE_SCRP_HANDLE, scrap_handle);
14768 }
14769 let text_ptr =
14770 Self::ensure_text_handle_size(bus, scrap_handle, selected.len());
14771 if text_ptr != 0 && !selected.is_empty() {
14772 bus.write_bytes(text_ptr, selected);
14773 }
14774 bus.write_word(
14775 addr::TE_SCRP_LENGTH,
14776 selected.len().min(u16::MAX as usize) as u16,
14777 );
14778 }
14779 let mut merged = Vec::with_capacity(existing.len() - (e - s));
14781 merged.extend_from_slice(&existing[..s]);
14782 merged.extend_from_slice(&existing[e..]);
14783 self.te_set_text_contents(bus, te_handle, &merged);
14784 let new_pos = s.min(u16::MAX as usize) as u16;
14785 bus.write_word(te_ptr + Self::TE_SEL_START_OFFSET, new_pos);
14786 bus.write_word(te_ptr + Self::TE_SEL_END_OFFSET, new_pos);
14787 }
14788 }
14789 cpu.write_reg(Register::A7, sp + 4);
14790 Ok(())
14791 }
14792
14793 (true, 0x1D7) => {
14805 let sp = cpu.read_reg(Register::A7);
14806 let te_handle = bus.read_long(sp);
14807 if trace_textedit_enabled() {
14808 eprintln!("[TE] TEDelete hTE=${:08X}", te_handle);
14809 }
14810 let te_ptr = Self::te_record_ptr(bus, te_handle);
14811 if te_ptr != 0 {
14812 let sel_start = bus.read_word(te_ptr + Self::TE_SEL_START_OFFSET) as usize;
14813 let sel_end = bus.read_word(te_ptr + Self::TE_SEL_END_OFFSET) as usize;
14814 if sel_start != sel_end {
14815 let existing = Self::te_text_bytes(bus, te_handle);
14816 let s = sel_start.min(existing.len());
14817 let e = sel_end.min(existing.len());
14818 let (s, e) = if s > e { (e, s) } else { (s, e) };
14819 let mut merged = Vec::with_capacity(existing.len() - (e - s));
14820 merged.extend_from_slice(&existing[..s]);
14821 merged.extend_from_slice(&existing[e..]);
14822 self.te_set_text_contents(bus, te_handle, &merged);
14823 let new_pos = s.min(u16::MAX as usize) as u16;
14824 bus.write_word(te_ptr + Self::TE_SEL_START_OFFSET, new_pos);
14825 bus.write_word(te_ptr + Self::TE_SEL_END_OFFSET, new_pos);
14826 }
14827 }
14828 cpu.write_reg(Register::A7, sp + 4);
14829 Ok(())
14830 }
14831
14832 (true, 0x1D8) => {
14842 let sp = cpu.read_reg(Register::A7);
14843 let te_handle = bus.read_long(sp);
14844 if trace_textedit_enabled() {
14845 eprintln!("[TE] TEActivate hTE=${:08X}", te_handle);
14846 }
14847 let te_ptr = Self::te_record_ptr(bus, te_handle);
14848 if te_ptr != 0 {
14849 bus.write_word(te_ptr + Self::TE_ACTIVE_OFFSET, 1);
14850 bus.write_long(te_ptr + Self::TE_CARET_TIME_OFFSET, self.tick_count);
14851 bus.write_word(te_ptr + Self::TE_CARET_STATE_OFFSET, 0);
14852 self.draw_te_contents(cpu, bus, te_handle);
14853 }
14854 cpu.write_reg(Register::A7, sp + 4);
14855 Ok(())
14856 }
14857
14858 (true, 0x1D9) => {
14868 let sp = cpu.read_reg(Register::A7);
14869 let te_handle = bus.read_long(sp);
14870 if trace_textedit_enabled() {
14871 eprintln!("[TE] TEDeactivate hTE=${:08X}", te_handle);
14872 }
14873 let te_ptr = Self::te_record_ptr(bus, te_handle);
14874 if te_ptr != 0 {
14875 bus.write_word(te_ptr + Self::TE_ACTIVE_OFFSET, 0);
14876 self.draw_te_contents(cpu, bus, te_handle);
14877 }
14878 cpu.write_reg(Register::A7, sp + 4);
14879 Ok(())
14880 }
14881
14882 (true, 0x1DA) => {
14926 let sp = cpu.read_reg(Register::A7);
14927 let te_handle = bus.read_long(sp);
14928 self.textedit_idle(cpu, bus, te_handle);
14929 cpu.write_reg(Register::A7, sp + 4);
14930 Ok(())
14931 }
14932
14933 (true, 0x1DB) => {
14942 let sp = cpu.read_reg(Register::A7);
14943 let te_handle = bus.read_long(sp);
14944 if trace_textedit_enabled() {
14945 eprintln!("[TE] TEPaste hTE=${:08X}", te_handle);
14946 }
14947 use crate::memory::globals::addr;
14948 let scrap_len = bus.read_word(addr::TE_SCRP_LENGTH) as usize;
14949 let scrap_handle = bus.read_long(addr::TE_SCRP_HANDLE);
14950 if scrap_handle != 0 && scrap_len > 0 {
14951 let scrap_ptr = bus.read_long(scrap_handle);
14952 if scrap_ptr != 0 {
14953 let scrap = bus.read_bytes(scrap_ptr, scrap_len);
14954 self.te_insert_text(bus, te_handle, &scrap);
14955 }
14956 }
14957 cpu.write_reg(Register::A7, sp + 4);
14958 Ok(())
14959 }
14960
14961 (true, 0x1DC) => {
14970 let sp = cpu.read_reg(Register::A7);
14971 let te_handle = bus.read_long(sp);
14972 let key = bus.read_word(sp + 4) as u8;
14973 if trace_textedit_enabled() {
14974 eprintln!(
14975 "[TE] TEKey hTE=${:08X} key=${:02X} {:?}",
14976 te_handle,
14977 key,
14978 char::from(key).escape_default().to_string()
14979 );
14980 }
14981 let te_ptr = Self::te_record_ptr(bus, te_handle);
14982 if te_ptr != 0 {
14983 let sel_start = bus.read_word(te_ptr + Self::TE_SEL_START_OFFSET) as usize;
14984 let sel_end = bus.read_word(te_ptr + Self::TE_SEL_END_OFFSET) as usize;
14985 let existing = Self::te_text_bytes(bus, te_handle);
14986 let text_len = existing.len();
14987 let s = sel_start.min(text_len);
14988 let e = sel_end.min(text_len);
14989 let (s, e) = if s > e { (e, s) } else { (s, e) };
14990
14991 if key == 0x08 {
14992 if s != e {
14994 let mut merged = Vec::with_capacity(text_len - (e - s));
14996 merged.extend_from_slice(&existing[..s]);
14997 merged.extend_from_slice(&existing[e..]);
14998 self.te_set_text_contents(bus, te_handle, &merged);
14999 let new_pos = s.min(u16::MAX as usize) as u16;
15000 bus.write_word(te_ptr + Self::TE_SEL_START_OFFSET, new_pos);
15001 bus.write_word(te_ptr + Self::TE_SEL_END_OFFSET, new_pos);
15002 } else if s > 0 {
15003 let mut merged = Vec::with_capacity(text_len - 1);
15005 merged.extend_from_slice(&existing[..s - 1]);
15006 merged.extend_from_slice(&existing[s..]);
15007 self.te_set_text_contents(bus, te_handle, &merged);
15008 let new_pos = (s - 1).min(u16::MAX as usize) as u16;
15009 bus.write_word(te_ptr + Self::TE_SEL_START_OFFSET, new_pos);
15010 bus.write_word(te_ptr + Self::TE_SEL_END_OFFSET, new_pos);
15011 }
15012 } else {
15014 self.te_insert_text(bus, te_handle, &[key]);
15016 }
15017 }
15018 cpu.write_reg(Register::A7, sp + 6);
15019 Ok(())
15020 }
15021
15022 (true, 0x1DD) => {
15026 let sp = cpu.read_reg(Register::A7);
15027 let te_handle = bus.read_long(sp);
15028 let dv = bus.read_word(sp + 4) as i16;
15029 let dh = bus.read_word(sp + 6) as i16;
15030 if trace_textedit_enabled() {
15031 eprintln!("[TE] TEScroll hTE=${:08X} dh={} dv={}", te_handle, dh, dv);
15032 }
15033 self.te_scroll_contents(cpu, bus, te_handle, dh, dv);
15034 cpu.write_reg(Register::A7, sp + 8);
15035 Ok(())
15036 }
15037
15038 (true, 0x1DE) => {
15048 let sp = cpu.read_reg(Register::A7);
15049 let te_handle = bus.read_long(sp);
15050 let length = bus.read_long(sp + 4) as usize;
15051 let text_ptr = bus.read_long(sp + 8);
15052 if text_ptr != 0 && length != 0 {
15053 let te_ptr = Self::te_record_ptr(bus, te_handle);
15054 if te_ptr != 0 {
15055 let existing = Self::te_text_bytes(bus, te_handle);
15056 let sel_start = (bus.read_word(te_ptr + Self::TE_SEL_START_OFFSET)
15057 as usize)
15058 .min(existing.len());
15059 let sel_end = (bus.read_word(te_ptr + Self::TE_SEL_END_OFFSET) as usize)
15060 .min(existing.len());
15061 let text = bus.read_bytes(text_ptr, length);
15062 let mut merged = Vec::with_capacity(existing.len() + text.len());
15063 merged.extend_from_slice(&existing[..sel_start]);
15064 merged.extend_from_slice(&text);
15065 merged.extend_from_slice(&existing[sel_start..]);
15066 self.te_set_text_contents(bus, te_handle, &merged);
15067 let shifted_start = (sel_start + text.len()).min(u16::MAX as usize) as u16;
15068 let shifted_end = (sel_end + text.len()).min(u16::MAX as usize) as u16;
15069 bus.write_word(te_ptr + Self::TE_SEL_START_OFFSET, shifted_start);
15070 bus.write_word(te_ptr + Self::TE_SEL_END_OFFSET, shifted_end);
15071 }
15072 }
15073 cpu.write_reg(Register::A7, sp + 12);
15074 Ok(())
15075 }
15076
15077 (true, 0x1DF) => {
15084 let sp = cpu.read_reg(Register::A7);
15085 let te_handle = bus.read_long(sp);
15086 let just = bus.read_word(sp + 4) as i16;
15087 let te_ptr = Self::te_record_ptr(bus, te_handle);
15088 if te_ptr != 0 {
15089 bus.write_word(te_ptr + Self::TE_JUST_OFFSET, just as u16);
15090 }
15091 cpu.write_reg(Register::A7, sp + 6);
15092 Ok(())
15093 }
15094
15095 (true, 0x17E) => {
15126 let sp = cpu.read_reg(Register::A7);
15127 let end_sel = bus.read_word(sp) as i16;
15128 let start_sel = bus.read_word(sp + 2) as i16;
15129 let item_no = bus.read_word(sp + 4) as i16;
15130 let dialog_ptr = bus.read_long(sp + 6);
15131 let mut redraw_item = None;
15132 if dialog_ptr != 0 && item_no > 0 {
15133 if let Some(items) = self.dialog_items.get_mut(&dialog_ptr) {
15134 if let Some(item) = items.get_mut((item_no - 1) as usize) {
15135 if item.item_type & 0x7F == 16 {
15136 let text_len = item.text.len() as i16;
15140 let (s, e) = if start_sel == 0 && end_sel == -1 {
15141 (0, text_len)
15142 } else {
15143 let s = start_sel.max(0).min(text_len);
15145 let e = end_sel.max(0).min(text_len);
15146 if s <= e {
15149 (s, e)
15150 } else {
15151 (e, s)
15152 }
15153 };
15154 item.sel_start = s;
15155 item.sel_end = e;
15156 bus.write_word(dialog_ptr + 164, (item_no - 1) as u16);
15157 let text_h_handle = bus.read_long(dialog_ptr + 160);
15164 if text_h_handle != 0 {
15165 let te_ptr = bus.read_long(text_h_handle);
15166 if te_ptr != 0 {
15167 bus.write_word(
15168 te_ptr + Self::TE_SEL_START_OFFSET,
15169 s as u16,
15170 );
15171 bus.write_word(te_ptr + Self::TE_SEL_END_OFFSET, e as u16);
15172 }
15173 }
15174 redraw_item = Some((dialog_ptr, item_no));
15175 }
15176 }
15177 }
15178 }
15179 if let Some((dialog_ptr, item_no)) = redraw_item {
15180 self.redraw_dialog_text_item(bus, dialog_ptr, item_no);
15181 }
15182 cpu.write_reg(Register::A7, sp + 10);
15183 Ok(())
15184 }
15185
15186 (true, 0x17D) | (true, 0x24B) => {
15206 let sp = cpu.read_reg(Register::A7);
15207 let items_handle = bus.read_long(sp);
15219 let ref_con = bus.read_long(sp + 4);
15220 let go_away_flag = bus.read_byte(sp + 8) != 0;
15223 let behind = bus.read_long(sp + 10);
15224 let proc_id = bus.read_word(sp + 14) as i16;
15225 let visible = bus.read_byte(sp + 16) != 0;
15226 let title_ptr = bus.read_long(sp + 18);
15227 let bounds_rect_ptr = bus.read_long(sp + 22);
15228 let storage_ptr = bus.read_long(sp + 26);
15229
15230 let bounds = if bounds_rect_ptr != 0 {
15231 (
15232 bus.read_word(bounds_rect_ptr) as i16,
15233 bus.read_word(bounds_rect_ptr + 2) as i16,
15234 bus.read_word(bounds_rect_ptr + 4) as i16,
15235 bus.read_word(bounds_rect_ptr + 6) as i16,
15236 )
15237 } else {
15238 (0, 0, 0, 0)
15239 };
15240 let title = if title_ptr != 0 {
15241 decode_mac_roman_for_render(&bus.read_pstring(title_ptr))
15242 } else {
15243 String::new()
15244 };
15245
15246 let items_ptr = if items_handle != 0 {
15247 bus.read_long(items_handle)
15248 } else {
15249 0
15250 };
15251 let items_len = if items_ptr != 0 {
15252 bus.get_alloc_size(items_ptr).unwrap_or(0)
15253 } else {
15254 0
15255 };
15256 let items = if items_ptr != 0 && items_len != 0 {
15257 Self::parse_ditl(bus, items_ptr, items_len)
15258 } else {
15259 Vec::new()
15260 };
15261
15262 let dlg_ptr = self.finish_dialog_creation(
15263 bus,
15264 cpu,
15265 storage_ptr,
15266 bounds,
15267 &title,
15268 visible,
15269 proc_id,
15270 go_away_flag,
15271 ref_con,
15272 items_handle,
15273 items,
15274 );
15275 self.apply_behind_parameter(bus, dlg_ptr, behind);
15277 bus.write_long(sp + 30, dlg_ptr);
15278 cpu.write_reg(Register::A7, sp + 30);
15279 Ok(())
15280 }
15281
15282 (true, 0x182) => {
15292 let sp = cpu.read_reg(Register::A7);
15293 let dialog_ptr = bus.read_long(sp);
15294 self.close_dialog_window(bus, cpu, dialog_ptr, false);
15295 cpu.write_reg(Register::A7, sp + 4);
15296 Ok(())
15297 }
15298
15299 (true, 0x178) => {
15304 let sp = cpu.read_reg(Register::A7);
15305 let update_rgn = bus.read_long(sp);
15306 let dialog_ptr = bus.read_long(sp + 4);
15307 cpu.write_reg(Register::A7, sp + 8);
15308 if update_rgn == 0 {
15309 return Some(Ok(()));
15310 }
15311 let update_rect = Self::region_handle_rect(bus, update_rgn);
15312 self.update_dialog_window_contents(bus, cpu, dialog_ptr, update_rect);
15313 Ok(())
15314 }
15315
15316 (true, 0x179) | (true, 0x17A) => {
15331 let sp = cpu.read_reg(Register::A7);
15332 let dialog_id = bus.read_word(sp) as i16;
15333 let load_if_missing = trap_num == 0x179;
15334 let purgeable = trap_num == 0x17A;
15335 self.cascade_dialog_resource_purgeability(
15336 bus,
15337 *b"DLOG",
15338 dialog_id,
15339 purgeable,
15340 load_if_missing,
15341 );
15342 let res_err = self.dialog_template_res_err(dialog_id);
15343 bus.write_word(0x0A60, res_err as u16);
15344 cpu.write_reg(Register::A7, sp + 2);
15345 Ok(())
15346 }
15347
15348 (true, 0x027) => {
15362 let sp = cpu.read_reg(Register::A7);
15363 let item_no = bus.read_word(sp) as i16;
15364 let dialog_ptr = bus.read_long(sp + 2);
15365 cpu.write_reg(Register::A7, sp + 6);
15366 let mut updated_control_rect = None;
15367 let mut redraw_local_rect = None;
15368 if dialog_ptr != 0 && item_no > 0 {
15369 let key = (dialog_ptr, item_no);
15370 if let Some(items) = self.dialog_items.get_mut(&dialog_ptr) {
15371 let idx = (item_no as usize).wrapping_sub(1);
15372 if idx < items.len() {
15373 let rect = items[idx].rect;
15374 if rect.1 < 8192 {
15376 let enclosing = Self::dialog_item_enclosing_local_rect(
15377 items[idx].item_type,
15378 rect,
15379 );
15380 self.hidden_dialog_item_rects.entry(key).or_insert(rect);
15381 items[idx].rect.1 = rect.1.wrapping_add(16384);
15382 items[idx].rect.3 = rect.3.wrapping_add(16384);
15383 updated_control_rect = Some(items[idx].rect);
15384 redraw_local_rect = Some(enclosing);
15385 }
15386 }
15387 }
15388 if let Some(rect) = redraw_local_rect {
15389 self.erase_dialog_item_enclosing_rect(bus, dialog_ptr, rect);
15390 self.invalidate_window_rect(bus, dialog_ptr, rect);
15391 }
15392 if let Some(rect) = updated_control_rect {
15393 if let Some(ctrl_handle) =
15394 self.dialog_control_handle_for_item(dialog_ptr, item_no)
15395 {
15396 let ctrl_ptr = bus.read_long(ctrl_handle);
15397 if ctrl_ptr != 0 {
15398 bus.write_word(ctrl_ptr + 8, rect.0 as u16);
15399 bus.write_word(ctrl_ptr + 10, rect.1 as u16);
15400 bus.write_word(ctrl_ptr + 12, rect.2 as u16);
15401 bus.write_word(ctrl_ptr + 14, rect.3 as u16);
15402 }
15403 }
15404 if let Some(tracking) = self.dialog_tracking.as_mut() {
15405 if tracking.dialog_ptr == dialog_ptr {
15406 let idx = (item_no as usize).wrapping_sub(1);
15407 if idx < tracking.items.len() {
15408 tracking.items[idx].rect = rect;
15409 }
15410 }
15411 }
15412 }
15413 }
15414 Ok(())
15415 }
15416
15417 (true, 0x028) => {
15426 let sp = cpu.read_reg(Register::A7);
15427 let item_no = bus.read_word(sp) as i16;
15428 let dialog_ptr = bus.read_long(sp + 2);
15429 cpu.write_reg(Register::A7, sp + 6);
15430 let mut updated_control_rect = None;
15431 let mut redraw_local_rect = None;
15432 if dialog_ptr != 0 && item_no > 0 {
15433 let key = (dialog_ptr, item_no);
15434 if let Some(items) = self.dialog_items.get_mut(&dialog_ptr) {
15435 let idx = (item_no as usize).wrapping_sub(1);
15436 if idx < items.len() {
15437 let rect = items[idx].rect;
15438 if rect.1 > 8192 {
15440 let restored_rect = if let Some(orig_rect) =
15441 self.hidden_dialog_item_rects.remove(&key)
15442 {
15443 orig_rect
15444 } else {
15445 (
15446 rect.0,
15447 rect.1.wrapping_sub(16384),
15448 rect.2,
15449 rect.3.wrapping_sub(16384),
15450 )
15451 };
15452 let enclosing = Self::dialog_item_enclosing_local_rect(
15453 items[idx].item_type,
15454 restored_rect,
15455 );
15456 items[idx].rect = restored_rect;
15457 updated_control_rect = Some(restored_rect);
15458 redraw_local_rect = Some(enclosing);
15459 }
15460 }
15461 }
15462 if let Some(rect) = redraw_local_rect {
15463 self.invalidate_window_rect(bus, dialog_ptr, rect);
15464 }
15465 if let Some(rect) = updated_control_rect {
15466 if let Some(ctrl_handle) =
15467 self.dialog_control_handle_for_item(dialog_ptr, item_no)
15468 {
15469 let ctrl_ptr = bus.read_long(ctrl_handle);
15470 if ctrl_ptr != 0 {
15471 bus.write_word(ctrl_ptr + 8, rect.0 as u16);
15472 bus.write_word(ctrl_ptr + 10, rect.1 as u16);
15473 bus.write_word(ctrl_ptr + 12, rect.2 as u16);
15474 bus.write_word(ctrl_ptr + 14, rect.3 as u16);
15475 }
15476 }
15477 if let Some(tracking) = self.dialog_tracking.as_mut() {
15478 if tracking.dialog_ptr == dialog_ptr {
15479 let idx = (item_no as usize).wrapping_sub(1);
15480 if idx < tracking.items.len() {
15481 tracking.items[idx].rect = rect;
15482 }
15483 }
15484 }
15485 }
15486 }
15487 Ok(())
15488 }
15489
15490 (true, 0x184) => {
15511 let sp = cpu.read_reg(Register::A7);
15512 let pt_v = bus.read_word(sp) as i16;
15513 let pt_h = bus.read_word(sp + 2) as i16;
15514 let dialog_ptr = bus.read_long(sp + 4);
15515 let result: i16 = if dialog_ptr == 0 {
15516 -1
15517 } else if let Some(items) = self.dialog_items.get(&dialog_ptr) {
15518 let mut hit: i16 = -1;
15519 for (idx, item) in items.iter().enumerate() {
15520 let (top, left, bottom, right) = item.rect;
15521 if pt_v >= top && pt_v < bottom && pt_h >= left && pt_h < right {
15522 hit = idx as i16;
15523 break;
15524 }
15525 }
15526 hit
15527 } else {
15528 -1
15529 };
15530 bus.write_word(sp + 8, result as u16);
15531 cpu.write_reg(Register::A7, sp + 8);
15532 Ok(())
15533 }
15534
15535 (true, 0x189) | (true, 0x18A) => {
15550 let sp = cpu.read_reg(Register::A7);
15551 let alert_id = bus.read_word(sp) as i16;
15552 let load_if_missing = trap_num == 0x189;
15553 let purgeable = trap_num == 0x18A;
15554 self.cascade_dialog_resource_purgeability(
15555 bus,
15556 *b"ALRT",
15557 alert_id,
15558 purgeable,
15559 load_if_missing,
15560 );
15561 bus.write_word(0x0A60, 0);
15562 cpu.write_reg(Register::A7, sp + 2);
15563 Ok(())
15564 }
15565
15566 (true, 0x18C) => {
15592 let sp = cpu.read_reg(Register::A7);
15593 let sound_proc = bus.read_long(sp);
15594 bus.write_long(crate::memory::globals::addr::DA_BEEPER, sound_proc);
15595 cpu.write_reg(Register::A7, sp + 4);
15596 Ok(())
15597 }
15598
15599 (true, 0x18F) => {
15615 let sp = cpu.read_reg(Register::A7);
15616 let pc = cpu.read_reg(Register::PC);
15617 let text_str_ptr = bus.read_long(sp);
15618 let item_handle = bus.read_long(sp + 4);
15619 let mut redraw_text_item = None;
15620 if text_str_ptr != 0 {
15621 let bytes = bus.read_pstring(text_str_ptr);
15622 let len = bytes.len();
15623 let text = decode_mac_roman_for_render(&bytes);
15624
15625 if item_handle != 0 {
15626 let data_ptr = Self::ensure_text_handle_size(bus, item_handle, len);
15627 if data_ptr != 0 {
15628 bus.write_bytes(data_ptr, &bytes);
15629 }
15630 }
15631
15632 if let Some((dlg_ptr, idx)) =
15634 self.dialog_item_handles.get(&item_handle).copied()
15635 {
15636 let item_no = (idx + 1) as i16;
15637 if trace_dialog_items_enabled() {
15638 eprintln!(
15639 "[DIALOG-ITEM] SetDialogItemText pc=${:08X} dialog=${:08X} item={} handle=${:08X} text={:?}",
15640 pc,
15641 dlg_ptr,
15642 item_no,
15643 item_handle,
15644 text
15645 );
15646 }
15647 if let Some(items) = self.dialog_items.get_mut(&dlg_ptr) {
15648 if idx < items.len() {
15649 items[idx].text = text.clone();
15650 }
15651 }
15652 let mut refresh_tracking = false;
15653 if let Some(tracking) = self.dialog_tracking.as_mut() {
15654 if tracking.dialog_ptr == dlg_ptr && idx < tracking.items.len() {
15655 tracking.items[idx].text = text.clone();
15656 if tracking.edit_item == item_no {
15657 tracking.edit_text = text;
15658 tracking.edit_text_modified = false;
15659 }
15660 refresh_tracking = !tracking.game_managed;
15661 }
15662 }
15663 if refresh_tracking {
15664 self.refresh_dialog_tracking_snapshot(bus);
15665 } else {
15666 redraw_text_item = Some((dlg_ptr, item_no));
15667 }
15668 } else if trace_dialog_items_enabled() {
15669 eprintln!(
15670 "[DIALOG-ITEM] SetDialogItemText pc=${:08X} dialog=<unknown> handle=${:08X} text={:?}",
15671 pc,
15672 item_handle,
15673 text
15674 );
15675 }
15676 }
15677
15678 if let Some((dlg_ptr, item_no)) = redraw_text_item {
15679 self.redraw_dialog_text_item(bus, dlg_ptr, item_no);
15680 }
15681
15682 cpu.write_reg(Register::A7, sp + 8);
15683 Ok(())
15684 }
15685
15686 (true, 0x190) => {
15691 let sp = cpu.read_reg(Register::A7);
15692 let text_ptr = bus.read_long(sp);
15693 let item_handle = bus.read_long(sp + 4);
15694
15695 if text_ptr != 0 {
15696 let mut wrote = false;
15698 if let Some(ref tracking) = self.dialog_tracking {
15699 let current_edit_handle =
15700 self.dialog_item_handles.get(&item_handle).copied().filter(
15701 |(dlg_ptr, idx)| {
15702 *dlg_ptr == tracking.dialog_ptr
15703 && (*idx as i16 + 1) == tracking.edit_item
15704 },
15705 );
15706 if current_edit_handle.is_some() {
15707 let bytes = tracking.edit_text.as_bytes();
15708 let len = bytes.len().min(255);
15709 bus.write_byte(text_ptr, len as u8);
15710 for (i, byte) in bytes.iter().take(len).enumerate() {
15711 bus.write_byte(text_ptr + 1 + i as u32, *byte);
15712 }
15713 wrote = true;
15714 }
15715 }
15716 if !wrote {
15717 if item_handle != 0 {
15720 let master = bus.read_long(item_handle);
15721 if master != 0 {
15722 let len = bus.get_alloc_size(master).unwrap_or(0).min(255) as usize;
15723 bus.write_byte(text_ptr, len as u8);
15724 for i in 0..len {
15725 bus.write_byte(
15726 text_ptr + 1 + i as u32,
15727 bus.read_byte(master + i as u32),
15728 );
15729 }
15730 wrote = true;
15731 }
15732 }
15733 if !wrote {
15734 bus.write_byte(text_ptr, 0);
15735 }
15736 }
15737 }
15738 cpu.write_reg(Register::A7, sp + 8);
15739 Ok(())
15740 }
15741
15742 (true, 0x268) => {
15751 let sp = cpu.read_reg(Register::A7);
15752 let d0 = cpu.read_reg(Register::D0) as u16;
15753 let param_bytes = (((d0 >> 8) & 0xFF) as u32) * 2;
15754 let routine = d0 & 0xFF;
15755 match routine {
15756 0x0C => {
15774 let _features = bus.read_long(sp);
15787 let items_handle = bus.read_long(sp + 4);
15788 let ref_con = bus.read_long(sp + 8);
15789 let go_away_flag = bus.read_byte(sp + 12) != 0;
15790 let behind = bus.read_long(sp + 14);
15791 let proc_id = bus.read_word(sp + 18) as i16;
15792 let visible = bus.read_byte(sp + 20) != 0;
15793 let title_ptr = bus.read_long(sp + 22);
15794 let bounds_rect_ptr = bus.read_long(sp + 26);
15795 let storage_ptr = bus.read_long(sp + 30);
15796
15797 let bounds = if bounds_rect_ptr != 0 {
15798 (
15799 bus.read_word(bounds_rect_ptr) as i16,
15800 bus.read_word(bounds_rect_ptr + 2) as i16,
15801 bus.read_word(bounds_rect_ptr + 4) as i16,
15802 bus.read_word(bounds_rect_ptr + 6) as i16,
15803 )
15804 } else {
15805 (0, 0, 0, 0)
15806 };
15807 let title = if title_ptr != 0 {
15808 decode_mac_roman_for_render(&bus.read_pstring(title_ptr))
15809 } else {
15810 String::new()
15811 };
15812 let items_ptr = if items_handle != 0 {
15813 bus.read_long(items_handle)
15814 } else {
15815 0
15816 };
15817 let items_len = if items_ptr != 0 {
15818 bus.get_alloc_size(items_ptr).unwrap_or(0)
15819 } else {
15820 0
15821 };
15822 let items = if items_ptr != 0 && items_len != 0 {
15823 Self::parse_ditl(bus, items_ptr, items_len)
15824 } else {
15825 Vec::new()
15826 };
15827
15828 let dlg_ptr = self.finish_dialog_creation(
15829 bus,
15830 cpu,
15831 storage_ptr,
15832 bounds,
15833 &title,
15834 visible,
15835 proc_id,
15836 go_away_flag,
15837 ref_con,
15838 items_handle,
15839 items,
15840 );
15841 self.apply_behind_parameter(bus, dlg_ptr, behind);
15842 bus.write_long(sp + param_bytes, dlg_ptr);
15843 cpu.write_reg(Register::A7, sp + param_bytes);
15844 }
15845 0x03 => {
15877 let proc_ptr = bus.read_long(sp);
15878 let shim = if self.dialog_std_filter_proc != 0 {
15879 self.dialog_std_filter_proc
15880 } else {
15881 let shim = bus.alloc(16);
15882 bus.write_word(shim, 0x4EF9); bus.write_long(shim + 2, shim + 6);
15884 bus.write_word(shim + 6, 0x7000); bus.write_word(shim + 8, 0x426F); bus.write_word(shim + 10, 0x0010);
15894 bus.write_word(shim + 12, 0x4E74); bus.write_word(shim + 14, 0x000C);
15896 self.dialog_std_filter_proc = shim;
15897 shim
15898 };
15899 if proc_ptr != 0 {
15900 bus.write_long(proc_ptr, shim);
15901 }
15902 bus.write_word(sp + param_bytes, 0); cpu.write_reg(Register::A7, sp + param_bytes);
15905 }
15906 0x04 => {
15924 let new_item = bus.read_word(sp) as i16;
15925 let dialog_ptr = bus.read_long(sp + 2);
15926 if dialog_ptr != 0 {
15927 bus.write_word(dialog_ptr + 168, new_item as u16);
15931 if let Some(tracking) = self.dialog_tracking.as_mut() {
15934 if tracking.dialog_ptr == dialog_ptr {
15935 tracking.default_item = new_item;
15936 }
15937 }
15938 }
15939 bus.write_word(sp + param_bytes, 0); cpu.write_reg(Register::A7, sp + param_bytes);
15941 }
15942 0x05 => {
15956 let new_item = bus.read_word(sp) as i16;
15957 let dialog_ptr = bus.read_long(sp + 2);
15958 if dialog_ptr != 0 {
15959 self.dialog_cancel_items.insert(dialog_ptr, new_item);
15960 if let Some(tracking) = self.dialog_tracking.as_mut() {
15961 if tracking.dialog_ptr == dialog_ptr {
15962 tracking.cancel_item = new_item;
15963 }
15964 }
15965 }
15966 bus.write_word(sp + param_bytes, 0); cpu.write_reg(Register::A7, sp + param_bytes);
15968 }
15969 0x06 => {
15984 bus.write_word(sp + param_bytes, 0); cpu.write_reg(Register::A7, sp + param_bytes);
15986 }
15987 _ => {
15988 eprintln!(
15989 "[TRAP] DialogDispatch unknown routine=${:02X} d0=${:04X}",
15990 routine, d0
15991 );
15992 cpu.write_reg(Register::A7, sp + param_bytes);
15993 }
15994 }
15995 Ok(())
15996 }
15997
15998 _ => return None,
15999 })
16000 }
16001}
16002
16003#[cfg(test)]
16004mod tests {
16005 use super::super::test_helpers::{setup, setup_with_port, MockCpu, TEST_SP};
16006 use crate::cpu::{CpuOps, Register};
16007 use crate::memory::{MacMemoryBus, MemoryBus};
16008 use crate::trap::dispatch::{
16009 DialogButtonTrackingState, DialogItem, DialogPopupDraw, DialogTrackingState,
16010 PendingDialogPopupMenu, PersistentDialogSnapshot, RetainedModalDialogClickState,
16011 };
16012 use crate::trap::menu::{Menu, MenuItem};
16013 use crate::trap::TrapDispatcher;
16014 use crate::ui_theme::UiThemeId;
16015 use std::collections::VecDeque;
16016
16017 fn screen_pixel_is_set(bus: &MacMemoryBus, base: u32, row_bytes: u32, x: i16, y: i16) -> bool {
16018 let byte = bus.read_byte(base + (y as u32 * row_bytes) + ((x as u32) / 8));
16019 byte & (0x80u8 >> ((x as u8) & 7)) != 0
16020 }
16021
16022 fn count_set_pixels(
16023 bus: &MacMemoryBus,
16024 base: u32,
16025 row_bytes: u32,
16026 top: i16,
16027 left: i16,
16028 bottom: i16,
16029 right: i16,
16030 ) -> u32 {
16031 let mut count = 0;
16032 for y in top..bottom {
16033 for x in left..right {
16034 if screen_pixel_is_set(bus, base, row_bytes, x, y) {
16035 count += 1;
16036 }
16037 }
16038 }
16039 count
16040 }
16041
16042 fn count_pixel_index(
16043 bus: &MacMemoryBus,
16044 base: u32,
16045 row_bytes: u32,
16046 top: i16,
16047 left: i16,
16048 bottom: i16,
16049 right: i16,
16050 pixel_index: u8,
16051 ) -> u32 {
16052 let mut count = 0;
16053 for y in top..bottom {
16054 for x in left..right {
16055 if bus.read_byte(base + y as u32 * row_bytes + x as u32) == pixel_index {
16056 count += 1;
16057 }
16058 }
16059 }
16060 count
16061 }
16062
16063 fn sum_screen_bytes(
16064 bus: &MacMemoryBus,
16065 base: u32,
16066 row_bytes: u32,
16067 top: i16,
16068 left: i16,
16069 bottom: i16,
16070 right: i16,
16071 ) -> u64 {
16072 let mut sum = 0;
16073 for y in top..bottom {
16074 let row = base + (y as u32 * row_bytes);
16075 for x in left..right {
16076 sum += u64::from(bus.read_byte(row + x as u32));
16077 }
16078 }
16079 sum
16080 }
16081
16082 fn make_style_scrap(
16083 bus: &mut MacMemoryBus,
16084 styles: &[(u32, i16, i16, i16, i16, i16, (u16, u16, u16))],
16085 ) -> u32 {
16086 let handle = TrapDispatcher::allocate_handle_with_data(
16087 bus,
16088 TrapDispatcher::SCRAP_STYLE_TAB_OFFSET
16089 + (styles.len() as u32 * TrapDispatcher::SCRAP_STYLE_ELEMENT_SIZE),
16090 );
16091 let ptr = bus.read_long(handle);
16092 bus.write_word(
16093 ptr + TrapDispatcher::SCRAP_N_STYLES_OFFSET,
16094 styles.len() as u16,
16095 );
16096 for (index, (start, height, ascent, font, face, size, color)) in styles.iter().enumerate() {
16097 let base = ptr
16098 + TrapDispatcher::SCRAP_STYLE_TAB_OFFSET
16099 + (index as u32 * TrapDispatcher::SCRAP_STYLE_ELEMENT_SIZE);
16100 bus.write_long(base + TrapDispatcher::SCRAP_STYLE_START_CHAR_OFFSET, *start);
16101 bus.write_word(
16102 base + TrapDispatcher::SCRAP_STYLE_HEIGHT_OFFSET,
16103 *height as u16,
16104 );
16105 bus.write_word(
16106 base + TrapDispatcher::SCRAP_STYLE_ASCENT_OFFSET,
16107 *ascent as u16,
16108 );
16109 bus.write_word(base + TrapDispatcher::SCRAP_STYLE_FONT_OFFSET, *font as u16);
16110 bus.write_byte(base + TrapDispatcher::SCRAP_STYLE_FACE_OFFSET, *face as u8);
16111 bus.write_word(base + TrapDispatcher::SCRAP_STYLE_SIZE_OFFSET, *size as u16);
16112 bus.write_word(base + TrapDispatcher::SCRAP_STYLE_COLOR_OFFSET, color.0);
16113 bus.write_word(base + TrapDispatcher::SCRAP_STYLE_COLOR_OFFSET + 2, color.1);
16114 bus.write_word(base + TrapDispatcher::SCRAP_STYLE_COLOR_OFFSET + 4, color.2);
16115 }
16116 handle
16117 }
16118
16119 #[test]
16122 fn init_dialogs_pops_4_bytes() {
16123 let (mut disp, mut cpu, mut bus) = setup();
16124 bus.write_long(TEST_SP, 0x00000000);
16126
16127 let result = disp.dispatch_dialog(true, 0x17B, &mut cpu, &mut bus);
16128 assert!(result.unwrap().is_ok());
16129 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 4);
16130 }
16131
16132 #[test]
16133 fn dialog_edit_state_falls_back_to_first_valid_edit_text_item() {
16134 let (_disp, _cpu, mut bus) = setup();
16135 let dialog_ptr = bus.alloc(170);
16136 bus.write_word(dialog_ptr + 164, 0); bus.write_word(dialog_ptr + 168, 1); let items = vec![
16139 DialogItem {
16140 item_type: 4,
16141 text: "OK".to_string(),
16142 ..Default::default()
16143 },
16144 DialogItem {
16145 item_type: 4,
16146 text: "Cancel".to_string(),
16147 ..Default::default()
16148 },
16149 DialogItem {
16150 item_type: 16,
16151 text: "answer".to_string(),
16152 ..Default::default()
16153 },
16154 ];
16155
16156 let (text, edit_item, default_item) =
16157 TrapDispatcher::dialog_edit_state(&bus, dialog_ptr, &items);
16158
16159 assert_eq!(text, "answer");
16160 assert_eq!(edit_item, 3);
16161 assert_eq!(default_item, 1);
16162 }
16163
16164 #[test]
16165 fn dialog_edit_state_empty_live_handle_does_not_fallback_to_template_text() {
16166 let (_disp, _cpu, mut bus) = setup();
16167 let dialog_ptr = bus.alloc(170);
16168 let items_handle = bus.alloc(4);
16169 let ditl_ptr = bus.alloc(16);
16170 let text_handle = bus.alloc(4);
16171
16172 bus.write_long(text_handle, 0); bus.write_long(items_handle, ditl_ptr);
16174 bus.write_long(dialog_ptr + 156, items_handle);
16175 bus.write_word(dialog_ptr + 164, 0); bus.write_word(dialog_ptr + 168, 1);
16177 bus.write_word(ditl_ptr, 0);
16178 bus.write_long(ditl_ptr + 2, text_handle);
16179 bus.write_word(ditl_ptr + 6, 10);
16180 bus.write_word(ditl_ptr + 8, 10);
16181 bus.write_word(ditl_ptr + 10, 26);
16182 bus.write_word(ditl_ptr + 12, 120);
16183 bus.write_byte(ditl_ptr + 14, 16);
16184 bus.write_byte(ditl_ptr + 15, 0);
16185
16186 let items = vec![DialogItem {
16187 item_type: 16,
16188 rect: (10, 10, 26, 120),
16189 text: "Edit Text".to_string(),
16190 resource_id: 0,
16191 proc_ptr: 0,
16192 sel_start: 0,
16193 sel_end: 0,
16194 }];
16195
16196 let (text, edit_item, default_item) =
16197 TrapDispatcher::dialog_edit_state(&bus, dialog_ptr, &items);
16198
16199 assert_eq!(text, "");
16200 assert_eq!(edit_item, 1);
16201 assert_eq!(default_item, 1);
16202 }
16203
16204 #[test]
16205 fn parse_dlog_ignores_position_word_when_resource_is_classic_length() {
16206 let (_disp, _cpu, mut bus) = setup();
16207 let ptr = bus.alloc(24);
16208 bus.write_word(ptr, 228); bus.write_word(ptr + 2, 198); bus.write_word(ptr + 4, 372); bus.write_word(ptr + 6, 455); bus.write_word(ptr + 8, 2); bus.write_byte(ptr + 10, 1); bus.write_byte(ptr + 12, 0); bus.write_long(ptr + 14, 0); bus.write_word(ptr + 18, 1013); bus.write_byte(ptr + 20, 0); bus.write_byte(ptr + 21, 0); bus.write_word(ptr + 22, 0x280A); let (bounds, proc_id, visible, items_id, title, position) =
16222 TrapDispatcher::parse_dlog(&bus, ptr, 22);
16223
16224 assert_eq!(bounds, (228, 198, 372, 455));
16225 assert_eq!(proc_id, 2);
16226 assert!(visible);
16227 assert_eq!(items_id, 1013);
16228 assert_eq!(title, "");
16229 assert_eq!(position, 0);
16230 }
16231
16232 #[test]
16233 fn parse_dlog_reads_position_word_when_resource_contains_it() {
16234 let (_disp, _cpu, mut bus) = setup();
16235 let ptr = bus.alloc(24);
16236 bus.write_word(ptr, 10);
16237 bus.write_word(ptr + 2, 20);
16238 bus.write_word(ptr + 4, 110);
16239 bus.write_word(ptr + 6, 220);
16240 bus.write_word(ptr + 8, 2);
16241 bus.write_byte(ptr + 10, 1);
16242 bus.write_long(ptr + 14, 0);
16243 bus.write_word(ptr + 18, 42);
16244 bus.write_byte(ptr + 20, 0);
16245 bus.write_byte(ptr + 21, 0);
16246 bus.write_word(ptr + 22, 0x280A);
16247
16248 let (_, _, _, _, _, position) = TrapDispatcher::parse_dlog(&bus, ptr, 24);
16249
16250 assert_eq!(position, 0x280A);
16251 }
16252
16253 #[test]
16254 fn dlog_parse_title_alignment_even_and_odd() {
16255 let (_disp, _cpu, mut bus) = setup();
16256 let odd = build_test_dlog_with_title((10, 20, 110, 220), 701, "Odd", 0x300A);
16257 let even = build_test_dlog_with_title((11, 21, 111, 221), 702, "Even", 0x700A);
16258
16259 let odd_ptr = bus.alloc(odd.len() as u32);
16260 bus.write_bytes(odd_ptr, &odd);
16261 let even_ptr = bus.alloc(even.len() as u32);
16262 bus.write_bytes(even_ptr, &even);
16263
16264 let (odd_bounds, _, _, odd_items, odd_title, odd_position) =
16267 TrapDispatcher::parse_dlog(&bus, odd_ptr, odd.len() as u32);
16268 let (even_bounds, _, _, even_items, even_title, even_position) =
16269 TrapDispatcher::parse_dlog(&bus, even_ptr, even.len() as u32);
16270
16271 assert_eq!(odd_bounds, (10, 20, 110, 220));
16272 assert_eq!(odd_items, 701);
16273 assert_eq!(odd_title, "Odd");
16274 assert_eq!(odd_position, 0x300A);
16275 assert_eq!(odd.len(), 26);
16276
16277 assert_eq!(even_bounds, (11, 21, 111, 221));
16278 assert_eq!(even_items, 702);
16279 assert_eq!(even_title, "Even");
16280 assert_eq!(even_position, 0x700A);
16281 assert_eq!(even.len(), 28);
16282 }
16283
16284 #[test]
16285 fn dlog_parse_position_constants() {
16286 let (_disp, _cpu, mut bus) = setup();
16287 for position in [0x0000u16, 0xB00A, 0x300A, 0x700A] {
16290 let dlog = build_test_dlog_with_title((10, 20, 110, 220), 711, "P", position);
16291 let ptr = bus.alloc(dlog.len() as u32);
16292 bus.write_bytes(ptr, &dlog);
16293
16294 let (_, _, _, _, _, parsed_position) =
16295 TrapDispatcher::parse_dlog(&bus, ptr, dlog.len() as u32);
16296
16297 assert_eq!(parsed_position, position);
16298 }
16299 }
16300
16301 #[test]
16302 fn alrt_parse_stage_nibbles_and_position() {
16303 let (_disp, _cpu, mut bus) = setup();
16304 let mut alrt = build_alrt_template(-321, 0xF721);
16305 alrt.extend_from_slice(&0xB00Au16.to_be_bytes());
16306 let ptr = bus.alloc(alrt.len() as u32);
16307 bus.write_bytes(ptr, &alrt);
16308
16309 let (bounds, items_id, stages, position) =
16310 TrapDispatcher::parse_alrt(&bus, ptr, alrt.len() as u32);
16311
16312 assert_eq!(bounds, (0, 0, 80, 200));
16313 assert_eq!(items_id, -321);
16314 assert_eq!(stages, 0xF721);
16315 assert_eq!(stages & 0x000F, 0x0001);
16317 assert_eq!((stages >> 4) & 0x000F, 0x0002);
16318 assert_eq!((stages >> 8) & 0x000F, 0x0007);
16319 assert_eq!((stages >> 12) & 0x000F, 0x000F);
16320 assert_eq!(position, 0xB00A);
16321
16322 let classic_alrt = build_alrt_template(123, 0x0008);
16323 let classic_ptr = bus.alloc(classic_alrt.len() as u32);
16324 bus.write_bytes(classic_ptr, &classic_alrt);
16325 let (_, classic_items_id, classic_stages, classic_position) =
16326 TrapDispatcher::parse_alrt(&bus, classic_ptr, classic_alrt.len() as u32);
16327 assert_eq!(classic_items_id, 123);
16328 assert_eq!(classic_stages, 0x0008);
16329 assert_eq!(classic_position, 0);
16330 }
16331
16332 fn push_ditl_count(data: &mut Vec<u8>, count_minus_one: i16) {
16333 data.extend_from_slice(&count_minus_one.to_be_bytes());
16334 }
16335
16336 fn push_ditl_item(
16337 data: &mut Vec<u8>,
16338 item_handle: u32,
16339 rect: (i16, i16, i16, i16),
16340 item_type: u8,
16341 length_or_reserved: u8,
16342 payload: &[u8],
16343 ) {
16344 data.extend_from_slice(&item_handle.to_be_bytes());
16345 data.extend_from_slice(&rect.0.to_be_bytes());
16346 data.extend_from_slice(&rect.1.to_be_bytes());
16347 data.extend_from_slice(&rect.2.to_be_bytes());
16348 data.extend_from_slice(&rect.3.to_be_bytes());
16349 data.push(item_type);
16350 data.push(length_or_reserved);
16351 data.extend_from_slice(payload);
16352 if payload.len() % 2 != 0 {
16353 data.push(0);
16354 }
16355 }
16356
16357 fn parse_ditl_bytes(bus: &mut MacMemoryBus, data: &[u8]) -> Vec<DialogItem> {
16358 let ptr = bus.alloc(data.len() as u32);
16359 bus.write_bytes(ptr, data);
16360 TrapDispatcher::parse_ditl(bus, ptr, data.len() as u32)
16361 }
16362
16363 #[test]
16364 fn ditl_iter_empty_count_minus_one() {
16365 let (_disp, _cpu, mut bus) = setup();
16366 let mut ditl = Vec::new();
16367 push_ditl_count(&mut ditl, -1);
16369
16370 let items = parse_ditl_bytes(&mut bus, &ditl);
16371
16372 assert!(items.is_empty());
16373 }
16374
16375 #[test]
16376 fn ditl_iter_button_checkbox_radio_static_edit_text() {
16377 let (_disp, _cpu, mut bus) = setup();
16378 let mut ditl = Vec::new();
16379 push_ditl_count(&mut ditl, 4);
16380 for (item_type, rect, text) in [
16381 (4, (1, 2, 11, 42), "OK"),
16382 (5, (12, 2, 22, 72), "Check"),
16383 (6, (23, 2, 33, 72), "Radio"),
16384 (8, (34, 2, 44, 92), "Static"),
16385 (16, (45, 2, 57, 122), "Edit"),
16386 ] {
16387 push_ditl_item(
16388 &mut ditl,
16389 0,
16390 rect,
16391 item_type,
16392 text.len() as u8,
16393 text.as_bytes(),
16394 );
16395 }
16396
16397 let items = parse_ditl_bytes(&mut bus, &ditl);
16398
16399 assert_eq!(items.len(), 5);
16400 assert_eq!(items[0].item_type, 4);
16401 assert_eq!(items[0].rect, (1, 2, 11, 42));
16402 assert_eq!(items[0].text, "OK");
16403 assert_eq!(items[1].item_type, 5);
16404 assert_eq!(items[1].text, "Check");
16405 assert_eq!(items[2].item_type, 6);
16406 assert_eq!(items[2].text, "Radio");
16407 assert_eq!(items[3].item_type, 8);
16408 assert_eq!(items[3].text, "Static");
16409 assert_eq!(items[4].item_type, 16);
16410 assert_eq!(items[4].rect, (45, 2, 57, 122));
16411 assert_eq!(items[4].text, "Edit");
16412 }
16413
16414 #[test]
16415 fn ditl_iter_control_icon_picture_resource_id() {
16416 let (_disp, _cpu, mut bus) = setup();
16417 let mut ditl = Vec::new();
16418 push_ditl_count(&mut ditl, 2);
16419 push_ditl_item(&mut ditl, 0, (1, 2, 11, 42), 7, 0, &128i16.to_be_bytes());
16422 push_ditl_item(
16424 &mut ditl,
16425 0,
16426 (12, 2, 28, 34),
16427 32,
16428 2,
16429 &(-42i16).to_be_bytes(),
16430 );
16431 push_ditl_item(&mut ditl, 0, (29, 2, 70, 90), 64, 0, &300i16.to_be_bytes());
16432
16433 let items = parse_ditl_bytes(&mut bus, &ditl);
16434
16435 assert_eq!(items.len(), 3);
16436 assert_eq!(items[0].item_type, 7);
16437 assert_eq!(items[0].resource_id, 128);
16438 assert_eq!(items[1].item_type, 32);
16439 assert_eq!(items[1].resource_id, -42);
16440 assert_eq!(items[2].item_type, 64);
16441 assert_eq!(items[2].resource_id, 300);
16442 }
16443
16444 #[test]
16445 fn ditl_iter_user_item_without_data_bytes() {
16446 let (_disp, _cpu, mut bus) = setup();
16447 let mut ditl = Vec::new();
16448 push_ditl_count(&mut ditl, 0);
16449 push_ditl_item(&mut ditl, 0x12345678, (10, 20, 30, 40), 0, 0, &[]);
16450
16451 let items = parse_ditl_bytes(&mut bus, &ditl);
16452
16453 assert_eq!(items.len(), 1);
16454 assert_eq!(items[0].item_type, 0);
16455 assert_eq!(items[0].rect, (10, 20, 30, 40));
16456 assert_eq!(items[0].proc_ptr, 0x12345678);
16457 }
16458
16459 #[test]
16460 fn ditl_iter_help_item_sized_payload() {
16461 let (_disp, _cpu, mut bus) = setup();
16462 let mut ditl = Vec::new();
16463 push_ditl_count(&mut ditl, 1);
16464 push_ditl_item(
16466 &mut ditl,
16467 0,
16468 (0, 0, 0, 0),
16469 1,
16470 6,
16471 &[0x00, 0x08, 0x01, 0x2C, 0x00, 0x02],
16472 );
16473 push_ditl_item(&mut ditl, 0, (5, 6, 17, 50), 4, 2, b"OK");
16474
16475 let items = parse_ditl_bytes(&mut bus, &ditl);
16476
16477 assert_eq!(items.len(), 2);
16478 assert_eq!(items[0].item_type, 1);
16479 assert_eq!(items[1].item_type, 4);
16480 assert_eq!(items[1].text, "OK");
16481 }
16482
16483 #[test]
16484 fn ditl_iter_odd_pascal_text_alignment() {
16485 let (_disp, _cpu, mut bus) = setup();
16486 let mut ditl = Vec::new();
16487 push_ditl_count(&mut ditl, 1);
16488 push_ditl_item(&mut ditl, 0, (1, 2, 11, 42), 8, 3, b"Odd");
16489 push_ditl_item(&mut ditl, 0, (12, 2, 22, 42), 4, 2, b"OK");
16490
16491 let items = parse_ditl_bytes(&mut bus, &ditl);
16492
16493 assert_eq!(items.len(), 2);
16494 assert_eq!(items[0].text, "Odd");
16495 assert_eq!(items[1].rect, (12, 2, 22, 42));
16496 assert_eq!(items[1].text, "OK");
16497 }
16498
16499 #[test]
16500 fn ditl_iter_even_pascal_text_alignment() {
16501 let (_disp, _cpu, mut bus) = setup();
16502 let mut ditl = Vec::new();
16503 push_ditl_count(&mut ditl, 1);
16504 push_ditl_item(&mut ditl, 0, (1, 2, 11, 42), 8, 4, b"Even");
16505 push_ditl_item(&mut ditl, 0, (12, 2, 22, 42), 4, 2, b"OK");
16506
16507 let items = parse_ditl_bytes(&mut bus, &ditl);
16508
16509 assert_eq!(items.len(), 2);
16510 assert_eq!(items[0].text, "Even");
16511 assert_eq!(items[1].rect, (12, 2, 22, 42));
16512 assert_eq!(items[1].text, "OK");
16513 }
16514
16515 #[test]
16516 fn ditl_iter_rejects_or_stops_at_truncated_item() {
16517 let (_disp, _cpu, mut bus) = setup();
16518 let mut ditl = Vec::new();
16519 push_ditl_count(&mut ditl, 1);
16520 push_ditl_item(&mut ditl, 0, (1, 2, 11, 42), 4, 2, b"OK");
16521 ditl.extend_from_slice(&0u32.to_be_bytes());
16522 for coord in [12i16, 2, 22, 72] {
16523 ditl.extend_from_slice(&coord.to_be_bytes());
16524 }
16525 ditl.push(8);
16526 ditl.push(6);
16527 ditl.extend_from_slice(b"Bad");
16528
16529 let items = parse_ditl_bytes(&mut bus, &ditl);
16530
16531 assert_eq!(items.len(), 1);
16532 assert_eq!(items[0].text, "OK");
16533 }
16534
16535 fn build_test_dlog(bounds: (i16, i16, i16, i16), items_id: i16, position: u16) -> Vec<u8> {
16536 build_test_dlog_with_title(bounds, items_id, "", position)
16537 }
16538
16539 fn build_test_dlog_with_title(
16540 bounds: (i16, i16, i16, i16),
16541 items_id: i16,
16542 title: &str,
16543 position: u16,
16544 ) -> Vec<u8> {
16545 let mut data = Vec::with_capacity(22 + title.len() + (title.len() & 1));
16546 data.extend_from_slice(&bounds.0.to_be_bytes());
16547 data.extend_from_slice(&bounds.1.to_be_bytes());
16548 data.extend_from_slice(&bounds.2.to_be_bytes());
16549 data.extend_from_slice(&bounds.3.to_be_bytes());
16550 data.extend_from_slice(&2i16.to_be_bytes()); data.push(0); data.push(0); data.push(0); data.push(0); data.extend_from_slice(&0u32.to_be_bytes()); data.extend_from_slice(&items_id.to_be_bytes());
16557 data.push(title.len() as u8);
16558 data.extend_from_slice(title.as_bytes());
16559 if (1 + title.len()) % 2 != 0 {
16560 data.push(0); }
16562 data.extend_from_slice(&position.to_be_bytes());
16563 data
16564 }
16565
16566 fn build_test_ditl_item(
16567 item_type: u8,
16568 rect: (i16, i16, i16, i16),
16569 item_data: &[u8],
16570 ) -> Vec<u8> {
16571 build_test_ditl_items(&[(item_type, rect, item_data)])
16572 }
16573
16574 fn build_test_ditl_items(items: &[(u8, (i16, i16, i16, i16), &[u8])]) -> Vec<u8> {
16575 let total_len = items.iter().fold(2usize, |len, (_, _, item_data)| {
16576 len + 14 + item_data.len() + (item_data.len() & 1)
16577 });
16578 let mut data = Vec::with_capacity(total_len);
16579 data.extend_from_slice(&((items.len() as u16).saturating_sub(1)).to_be_bytes());
16580 for (item_type, rect, item_data) in items {
16581 append_test_ditl_item(&mut data, *item_type, *rect, item_data);
16582 }
16583 data
16584 }
16585
16586 fn append_test_ditl_item(
16587 data: &mut Vec<u8>,
16588 item_type: u8,
16589 rect: (i16, i16, i16, i16),
16590 item_data: &[u8],
16591 ) {
16592 data.extend_from_slice(&0u32.to_be_bytes()); data.extend_from_slice(&rect.0.to_be_bytes());
16594 data.extend_from_slice(&rect.1.to_be_bytes());
16595 data.extend_from_slice(&rect.2.to_be_bytes());
16596 data.extend_from_slice(&rect.3.to_be_bytes());
16597 data.push(item_type);
16598 data.push(item_data.len() as u8);
16599 data.extend_from_slice(item_data);
16600 if item_data.len() % 2 != 0 {
16601 data.push(0);
16602 }
16603 }
16604
16605 fn build_test_alrt(bounds: (i16, i16, i16, i16), items_id: i16, stages: u16) -> Vec<u8> {
16606 let mut data = Vec::with_capacity(12);
16607 data.extend_from_slice(&bounds.0.to_be_bytes());
16608 data.extend_from_slice(&bounds.1.to_be_bytes());
16609 data.extend_from_slice(&bounds.2.to_be_bytes());
16610 data.extend_from_slice(&bounds.3.to_be_bytes());
16611 data.extend_from_slice(&items_id.to_be_bytes());
16612 data.extend_from_slice(&stages.to_be_bytes());
16613 data
16614 }
16615
16616 #[test]
16617 fn multi_button_alert_tracks_click_and_returns_selected_item() {
16618 let (mut disp, mut cpu, mut bus) = setup();
16623 let alert_id = 3300;
16624 let ditl_id = 3301;
16625 let alrt = build_test_alrt((100, 100, 210, 400), ditl_id, 0x5555);
16626 let ditl = build_test_ditl_items(&[
16627 (4, (70, 230, 90, 290), b"Choice 1"),
16628 (4, (70, 130, 90, 210), b"Choice 2"),
16629 (4, (70, 20, 90, 100), b"Cancel"),
16630 (
16631 8,
16632 (16, 20, 56, 280),
16633 b"Select one of these options before continuing.",
16634 ),
16635 ]);
16636 disp.install_test_resource(&mut bus, *b"ALRT", alert_id, &alrt);
16637 disp.install_test_resource(&mut bus, *b"DITL", ditl_id, &ditl);
16638
16639 bus.write_long(TEST_SP, 0); bus.write_word(TEST_SP + 4, alert_id as u16);
16641 bus.write_word(TEST_SP + 6, 0xCAFE);
16642 let result = disp.dispatch_dialog(true, 0x185, &mut cpu, &mut bus);
16643
16644 assert!(result.unwrap().is_ok());
16645 assert_eq!(cpu.read_reg(Register::A7), TEST_SP);
16646 assert_eq!(bus.read_word(TEST_SP + 6), 0);
16647 assert!(disp.dialog_tracking.is_some());
16648 assert!(disp.is_tracking_refire(0xA985));
16649
16650 disp.push_mouse_down(180, 250);
16651 for _ in 0..4 {
16652 let result = disp.dispatch_dialog(true, 0x185, &mut cpu, &mut bus);
16653 assert!(result.unwrap().is_ok());
16654 if disp
16655 .dialog_tracking
16656 .as_ref()
16657 .and_then(|tracking| tracking.active_button.as_ref())
16658 .is_some()
16659 {
16660 break;
16661 }
16662 }
16663 assert!(disp
16664 .dialog_tracking
16665 .as_ref()
16666 .and_then(|tracking| tracking.active_button.as_ref())
16667 .is_some_and(|button| button.item_no == 2));
16668
16669 disp.push_mouse_up(180, 250);
16670 let result = disp.dispatch_dialog(true, 0x185, &mut cpu, &mut bus);
16671 assert!(result.unwrap().is_ok());
16672
16673 for _ in 0..64 {
16674 if disp.dialog_tracking.is_none() {
16675 break;
16676 }
16677 let result = disp.dispatch_dialog(true, 0x185, &mut cpu, &mut bus);
16678 assert!(result.unwrap().is_ok());
16679 }
16680
16681 assert!(disp.dialog_tracking.is_none());
16682 assert_eq!(bus.read_word(TEST_SP + 6), 2);
16683 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 6);
16684 }
16685
16686 fn loaded_resource_handle_for_test(
16687 disp: &TrapDispatcher,
16688 res_type: [u8; 4],
16689 res_id: i16,
16690 ) -> u32 {
16691 disp.loaded_handles
16692 .iter()
16693 .find_map(|(&handle, &(_, loaded_type, loaded_id))| {
16694 if loaded_type == res_type && loaded_id == res_id {
16695 Some(handle)
16696 } else {
16697 None
16698 }
16699 })
16700 .unwrap_or_else(|| {
16701 panic!(
16702 "expected loaded {} resource {}",
16703 String::from_utf8_lossy(&res_type),
16704 res_id
16705 )
16706 })
16707 }
16708
16709 fn assert_resource_nonpurgeable(
16710 disp: &TrapDispatcher,
16711 bus: &MacMemoryBus,
16712 res_type: [u8; 4],
16713 res_id: i16,
16714 ) {
16715 let handle = loaded_resource_handle_for_test(disp, res_type, res_id);
16716 assert_ne!(
16717 bus.read_long(handle),
16718 0,
16719 "{} {} should have a non-NIL master pointer",
16720 String::from_utf8_lossy(&res_type),
16721 res_id
16722 );
16723 assert_eq!(
16724 disp.handle_state_bits.get(&handle).copied().unwrap_or(0) & 0x40,
16725 0,
16726 "{} {} should be nonpurgeable",
16727 String::from_utf8_lossy(&res_type),
16728 res_id
16729 );
16730 }
16731
16732 fn assert_resource_purgeable(disp: &TrapDispatcher, res_type: [u8; 4], res_id: i16) {
16733 let handle = loaded_resource_handle_for_test(disp, res_type, res_id);
16734 assert_ne!(
16735 disp.handle_state_bits.get(&handle).copied().unwrap_or(0) & 0x40,
16736 0,
16737 "{} {} should be purgeable",
16738 String::from_utf8_lossy(&res_type),
16739 res_id
16740 );
16741 }
16742
16743 fn ditl_item_handle_field(bus: &MacMemoryBus, ditl_ptr: u32, item_no: i16) -> u32 {
16744 if item_no <= 0 || ditl_ptr == 0 {
16745 return 0;
16746 }
16747
16748 let max_index = bus.read_word(ditl_ptr) as i16;
16749 if item_no > max_index + 1 {
16750 return 0;
16751 }
16752
16753 let mut offset = 2u32;
16754 for current_item in 1..=max_index + 1 {
16755 let handle_addr = ditl_ptr + offset;
16756 offset += 4; offset += 8; let data_len = bus.read_byte(ditl_ptr + offset + 1) as u32;
16759 offset += 2; let padded = (data_len + 1) & !1;
16761 if current_item == item_no {
16762 return bus.read_long(handle_addr);
16763 }
16764 offset += padded;
16765 }
16766
16767 0
16768 }
16769
16770 #[test]
16774 fn dialogdispatch_getstdfilterproc_selector_03_writes_non_nil_shim_and_returns_noerr() {
16775 let (mut disp, mut cpu, mut bus) = setup();
16776 let proc_storage_ptr = 0x300000u32;
16777
16778 bus.write_long(proc_storage_ptr, 0x00AB_CDEF);
16779 bus.write_long(TEST_SP, proc_storage_ptr); bus.write_word(TEST_SP + 4, 0xBEEF); cpu.write_reg(Register::D0, 0x0203); let result = disp.dispatch_dialog(true, 0x268, &mut cpu, &mut bus);
16784 assert!(result.unwrap().is_ok());
16785
16786 let shim = bus.read_long(proc_storage_ptr);
16787 assert_ne!(shim, 0);
16788 assert_eq!(shim, disp.dialog_std_filter_proc);
16789 assert_eq!(bus.read_word(shim), 0x4EF9); assert_eq!(bus.read_long(shim + 2), shim + 6);
16791 assert_eq!(bus.read_word(shim + 6), 0x7000); assert_eq!(bus.read_word(shim + 8), 0x426F); assert_eq!(bus.read_word(shim + 10), 0x0010);
16794 assert_eq!(bus.read_word(shim + 12), 0x4E74); assert_eq!(bus.read_word(shim + 14), 0x000C);
16796 assert_eq!(bus.read_word(TEST_SP + 4), 0);
16797 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 4);
16798 }
16799
16800 #[test]
16801 fn dialogdispatch_getstdfilterproc_selector_03_reuses_cached_shim() {
16802 let (mut disp, mut cpu, mut bus) = setup();
16803 let slot_a = 0x300000u32;
16804 let slot_b = 0x300004u32;
16805
16806 bus.write_long(TEST_SP, slot_a);
16807 bus.write_word(TEST_SP + 4, 0xBEEF);
16808 cpu.write_reg(Register::D0, 0x0203);
16809 let first = disp.dispatch_dialog(true, 0x268, &mut cpu, &mut bus);
16810 assert!(first.unwrap().is_ok());
16811 let shim_a = bus.read_long(slot_a);
16812 assert_ne!(shim_a, 0);
16813
16814 cpu.write_reg(Register::A7, TEST_SP);
16815 bus.write_long(TEST_SP, slot_b);
16816 bus.write_word(TEST_SP + 4, 0xCAFE);
16817 cpu.write_reg(Register::D0, 0x0203);
16818 let second = disp.dispatch_dialog(true, 0x268, &mut cpu, &mut bus);
16819 assert!(second.unwrap().is_ok());
16820 let shim_b = bus.read_long(slot_b);
16821
16822 assert_eq!(shim_a, shim_b);
16823 assert_eq!(shim_b, disp.dialog_std_filter_proc);
16824 assert_eq!(bus.read_word(shim_b), 0x4EF9);
16825 assert_eq!(bus.read_word(shim_b + 6), 0x7000);
16826 assert_eq!(bus.read_word(TEST_SP + 4), 0);
16827 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 4);
16828 }
16829
16830 #[test]
16831 fn dialogdispatch_setdialogdefaultitem_selector_04_writes_adefitem_and_updates_tracking() {
16832 let (mut disp, mut cpu, mut bus) = setup();
16833 let dialog_ptr = bus.alloc(256);
16834
16835 disp.dialog_tracking = Some(crate::trap::dispatch::DialogTrackingState {
16836 dialog_ptr,
16837 bounds: (0, 0, 0, 0),
16838 title: String::new(),
16839 proc_id: 0,
16840 items: Vec::new(),
16841 default_item: 1,
16842 cancel_item: 2,
16843 edit_text: String::new(),
16844 edit_item: 0,
16845 saved_pixels: Vec::new(),
16846 stack_ptr: TEST_SP,
16847 item_hit_ptr: 0,
16848 rendered_pixels: Vec::new(),
16849 flash_remaining: 0,
16850 flash_delay: 0,
16851 flash_item: 0,
16852 edit_text_modified: false,
16853 draw_proc_queue: VecDeque::new(),
16854 draw_procs_done: true,
16855 rendered_pixels_final: true,
16856 filter_proc: 0,
16857 game_managed: false,
16858 last_filter_event: None,
16859 popup_draws: Vec::new(),
16860 active_popup: None,
16861 active_button: None,
16862 active_user_item: None,
16863 });
16864
16865 bus.write_word(TEST_SP, 9); bus.write_long(TEST_SP + 2, dialog_ptr); bus.write_word(TEST_SP + 6, 0xBEEF); cpu.write_reg(Register::D0, 0x0304); let result = disp.dispatch_dialog(true, 0x268, &mut cpu, &mut bus);
16871 assert!(result.unwrap().is_ok());
16872
16873 assert_eq!(bus.read_word(dialog_ptr + 168), 9);
16874 assert_eq!(
16875 disp.dialog_tracking.as_ref().map(|t| t.default_item),
16876 Some(9)
16877 );
16878 assert_eq!(bus.read_word(TEST_SP + 6), 0);
16879 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 6);
16880 }
16881
16882 #[test]
16883 fn dialogdispatch_setdialogcancelitem_selector_05_updates_tracking_cancel_item_and_returns_noerr(
16884 ) {
16885 let (mut disp, mut cpu, mut bus) = setup();
16886 let dialog_ptr = bus.alloc(256);
16887
16888 disp.dialog_tracking = Some(crate::trap::dispatch::DialogTrackingState {
16889 dialog_ptr,
16890 bounds: (0, 0, 0, 0),
16891 title: String::new(),
16892 proc_id: 0,
16893 items: Vec::new(),
16894 default_item: 1,
16895 cancel_item: 2,
16896 edit_text: String::new(),
16897 edit_item: 0,
16898 saved_pixels: Vec::new(),
16899 stack_ptr: TEST_SP,
16900 item_hit_ptr: 0,
16901 rendered_pixels: Vec::new(),
16902 flash_remaining: 0,
16903 flash_delay: 0,
16904 flash_item: 0,
16905 edit_text_modified: false,
16906 draw_proc_queue: VecDeque::new(),
16907 draw_procs_done: true,
16908 rendered_pixels_final: true,
16909 filter_proc: 0,
16910 game_managed: false,
16911 last_filter_event: None,
16912 popup_draws: Vec::new(),
16913 active_popup: None,
16914 active_button: None,
16915 active_user_item: None,
16916 });
16917
16918 bus.write_word(TEST_SP, 7); bus.write_long(TEST_SP + 2, dialog_ptr); bus.write_word(TEST_SP + 6, 0xBEEF); cpu.write_reg(Register::D0, 0x0305); let result = disp.dispatch_dialog(true, 0x268, &mut cpu, &mut bus);
16924 assert!(result.unwrap().is_ok());
16925
16926 assert_eq!(disp.dialog_cancel_items.get(&dialog_ptr), Some(&7));
16927 assert_eq!(
16928 disp.dialog_tracking.as_ref().map(|t| t.cancel_item),
16929 Some(7)
16930 );
16931 assert_eq!(bus.read_word(TEST_SP + 6), 0);
16932 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 6);
16933 }
16934
16935 #[test]
16936 fn dialogdispatch_modal_dialog_first_entry_honors_preserved_default_and_cancel_items() {
16937 let (mut disp, mut cpu, mut bus) = setup();
16938 let dialog_ptr = 0x200000u32;
16939 let item_hit_ptr = 0x300000u32;
16940
16941 disp.front_window = dialog_ptr;
16942 disp.window_bounds = (92, 95, 240, 320);
16943 disp.window_proc_id = 1;
16944 disp.window_title = "AA68".to_string();
16945 disp.dialog_items.insert(
16946 dialog_ptr,
16947 vec![
16948 DialogItem {
16949 item_type: 4,
16950 rect: (20, 20, 40, 90),
16951 text: "OK".to_string(),
16952 resource_id: 0,
16953 proc_ptr: 0,
16954 sel_start: 0,
16955 sel_end: 0,
16956 },
16957 DialogItem {
16958 item_type: 4,
16959 rect: (20, 110, 40, 200),
16960 text: "Cancel".to_string(),
16961 resource_id: 0,
16962 proc_ptr: 0,
16963 sel_start: 0,
16964 sel_end: 0,
16965 },
16966 ],
16967 );
16968 bus.write_word(dialog_ptr + 168, 2);
16969 disp.dialog_cancel_items.insert(dialog_ptr, 1);
16970 bus.write_long(TEST_SP, item_hit_ptr);
16971 bus.write_long(TEST_SP + 4, 0);
16972
16973 let result = disp.dispatch_dialog(true, 0x191, &mut cpu, &mut bus);
16974 assert!(result.unwrap().is_ok());
16975 assert_eq!(cpu.read_reg(Register::A7), TEST_SP);
16976
16977 let tracking = disp.dialog_tracking.as_ref().unwrap();
16978 assert_eq!(tracking.default_item, 2);
16979 assert_eq!(tracking.cancel_item, 1);
16980 }
16981
16982 #[test]
16983 fn dialogdispatch_setdialogtrackscursor_selector_06_returns_noerr_and_pops_arguments() {
16984 let (mut disp, mut cpu, mut bus) = setup();
16985 let dialog_ptr = bus.alloc(256);
16986
16987 bus.write_word(TEST_SP, 1); bus.write_long(TEST_SP + 2, dialog_ptr); bus.write_word(TEST_SP + 6, 0xBEEF); cpu.write_reg(Register::D0, 0x0306); let result = disp.dispatch_dialog(true, 0x268, &mut cpu, &mut bus);
16993 assert!(result.unwrap().is_ok());
16994
16995 assert_eq!(bus.read_word(TEST_SP + 6), 0);
16996 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 6);
16997 }
16998
16999 #[test]
17002 fn get_new_dialog_missing_dlog_sets_reserr_and_returns_nil() {
17003 let (mut disp, mut cpu, mut bus) = setup();
17007 bus.write_word(0x0A60, 0);
17008
17009 let result = disp.dispatch_dialog(true, 0x17C, &mut cpu, &mut bus);
17010 assert!(result.unwrap().is_ok());
17011 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 10);
17012 let dlg_ptr = bus.read_long(TEST_SP + 10);
17013 assert_eq!(
17014 dlg_ptr, 0,
17015 "GetNewDialog must return NIL when DLOG is missing"
17016 );
17017 assert_eq!(bus.read_word(0x0A60) as i16, -192);
17018 assert!(disp.window_list.is_empty());
17019 }
17020
17021 #[test]
17022 fn get_new_dialog_missing_ditl_sets_reserr_and_returns_nil() {
17023 let (mut disp, mut cpu, mut bus) = setup();
17026 let dlog = build_test_dlog((10, 20, 110, 220), 1909, 0);
17027 disp.install_test_resource(&mut bus, *b"DLOG", 1908, &dlog);
17028 bus.write_word(0x0A60, 0);
17029 bus.write_word(TEST_SP + 8, 1908);
17030
17031 let result = disp.dispatch_dialog(true, 0x17C, &mut cpu, &mut bus);
17032
17033 assert!(result.unwrap().is_ok());
17034 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 10);
17035 assert_eq!(bus.read_long(TEST_SP + 10), 0);
17036 assert_eq!(bus.read_word(0x0A60) as i16, -192);
17037 assert!(disp.window_list.is_empty());
17038 assert!(disp.dialog_items.is_empty());
17039 }
17040
17041 #[test]
17042 fn get_new_dialog_nil_storage_allocates_low_byte_clean_dialog_record() {
17043 let (mut disp, mut cpu, mut bus) = setup();
17044 let screen_base = bus.alloc((640 * 480) as u32);
17045 bus.write_long(0x0824, screen_base);
17046 disp.screen_mode = (screen_base, 640, 640, 480, 8);
17047
17048 let dlog = build_test_dlog((40, 50, 120, 240), 1911, 0);
17049 let ditl = build_test_ditl_item(4, (50, 80, 70, 140), b"OK");
17050 disp.install_test_resource(&mut bus, *b"DLOG", 1910, &dlog);
17051 disp.install_test_resource(&mut bus, *b"DITL", 1911, &ditl);
17052 let skew = bus.alloc(5);
17053 assert_ne!(
17054 (skew + MacMemoryBus::allocation_bucket_size(5)) & 0xFF,
17055 0,
17056 "test precondition should leave the heap skewed before GetNewDialog"
17057 );
17058
17059 bus.write_long(TEST_SP, 0xFFFF_FFFF); bus.write_long(TEST_SP + 4, 0); bus.write_word(TEST_SP + 8, 1910);
17062 bus.write_long(TEST_SP + 10, 0xDEAD_BEEF);
17063
17064 disp.dispatch_dialog(true, 0x17C, &mut cpu, &mut bus)
17065 .unwrap()
17066 .unwrap();
17067
17068 let dialog_ptr = bus.read_long(TEST_SP + 10);
17069 assert_ne!(dialog_ptr, 0);
17070 assert_eq!(
17071 dialog_ptr & 0xFF,
17072 0,
17073 "manager-owned DialogRecord pointers should keep the low byte clear"
17074 );
17075 assert_eq!(
17076 bus.get_alloc_size(dialog_ptr),
17077 Some(170),
17078 "DialogRecord allocation should keep its logical size"
17079 );
17080 }
17081
17082 #[test]
17083 fn get_new_dialog_copies_ditl_not_aliases_resource() {
17084 let (mut disp, mut cpu, mut bus) = setup();
17088 let screen_base = bus.alloc((640 * 480) as u32);
17089 bus.write_long(0x0824, screen_base);
17090 disp.screen_mode = (screen_base, 640, 640, 480, 8);
17091
17092 let dlog = build_test_dlog((40, 50, 110, 230), 2201, 0);
17093 let ditl = build_test_ditl_item(8, (10, 12, 28, 120), b"Shared");
17094 disp.install_test_resource(&mut bus, *b"DLOG", 2200, &dlog);
17095 let original_ditl_ptr = disp.install_test_resource(&mut bus, *b"DITL", 2201, &ditl);
17096
17097 cpu.write_reg(Register::A7, TEST_SP);
17098 bus.write_long(TEST_SP, 0xFFFF_FFFF); bus.write_long(TEST_SP + 4, 0); bus.write_word(TEST_SP + 8, 2200); bus.write_long(TEST_SP + 10, 0xDEAD_BEEF);
17102 disp.dispatch_dialog(true, 0x17C, &mut cpu, &mut bus)
17103 .unwrap()
17104 .unwrap();
17105 let first_dialog = bus.read_long(TEST_SP + 10);
17106 let first_ditl_ptr = bus.read_long(bus.read_long(first_dialog + 156));
17107
17108 assert_ne!(first_dialog, 0);
17109 assert_ne!(first_ditl_ptr, 0);
17110 assert_ne!(first_ditl_ptr, original_ditl_ptr);
17111 assert_eq!(ditl_item_handle_field(&bus, original_ditl_ptr, 1), 0);
17112 assert_ne!(ditl_item_handle_field(&bus, first_ditl_ptr, 1), 0);
17113
17114 bus.write_long(first_ditl_ptr + 2, 0xAABB_CCDD);
17115
17116 cpu.write_reg(Register::A7, TEST_SP);
17117 bus.write_long(TEST_SP, 0xFFFF_FFFF); bus.write_long(TEST_SP + 4, 0); bus.write_word(TEST_SP + 8, 2200); bus.write_long(TEST_SP + 10, 0xDEAD_BEEF);
17121 disp.dispatch_dialog(true, 0x17C, &mut cpu, &mut bus)
17122 .unwrap()
17123 .unwrap();
17124 let second_dialog = bus.read_long(TEST_SP + 10);
17125 let second_ditl_ptr = bus.read_long(bus.read_long(second_dialog + 156));
17126 let second_text_handle = ditl_item_handle_field(&bus, second_ditl_ptr, 1);
17127
17128 assert_ne!(second_dialog, 0);
17129 assert_ne!(second_ditl_ptr, original_ditl_ptr);
17130 assert_ne!(second_ditl_ptr, first_ditl_ptr);
17131 assert_ne!(second_text_handle, 0);
17132 assert_ne!(second_text_handle, 0xAABB_CCDD);
17133 assert_eq!(ditl_item_handle_field(&bus, original_ditl_ptr, 1), 0);
17134 }
17135
17136 #[test]
17137 fn get_new_dialog_rewrites_reserved_item_handles() {
17138 let (mut disp, mut cpu, mut bus) = setup();
17142 let screen_base = bus.alloc((640 * 480) as u32);
17143 bus.write_long(0x0824, screen_base);
17144 disp.screen_mode = (screen_base, 640, 640, 480, 8);
17145
17146 let cntl_id = 2301i16.to_be_bytes();
17147 let icon_id = 2302i16.to_be_bytes();
17148 let pict_id = 2303i16.to_be_bytes();
17149 let dlog = build_test_dlog((40, 50, 130, 260), 2300, 0);
17150 let ditl = build_test_ditl_items(&[
17151 (8, (10, 12, 24, 140), b"Static".as_slice()),
17152 (16, (30, 12, 44, 140), b"Edit".as_slice()),
17153 (7, (50, 12, 70, 120), cntl_id.as_slice()),
17154 (32, (10, 150, 42, 182), icon_id.as_slice()),
17155 (64, (48, 150, 80, 220), pict_id.as_slice()),
17156 ]);
17157
17158 let mut cntl = Vec::new();
17159 for word in [0i16, 0, 20, 110, 1, -1, 3, 0, 0] {
17160 cntl.extend_from_slice(&(word as u16).to_be_bytes());
17161 }
17162 cntl.extend_from_slice(&0x1234_5678u32.to_be_bytes());
17163 cntl.push(4);
17164 cntl.extend_from_slice(b"Pick");
17165 disp.install_test_resource(&mut bus, *b"DLOG", 2299, &dlog);
17166 disp.install_test_resource(&mut bus, *b"CNTL", 2301, &cntl);
17167 let icon_ptr = disp.install_test_resource(&mut bus, *b"ICON", 2302, &[0xAA; 128]);
17168 let pict_ptr = disp.install_test_resource(&mut bus, *b"PICT", 2303, &[0x11; 32]);
17169 let original_ditl_ptr = disp.install_test_resource(&mut bus, *b"DITL", 2300, &ditl);
17170
17171 bus.write_long(TEST_SP, 0xFFFF_FFFF); bus.write_long(TEST_SP + 4, 0); bus.write_word(TEST_SP + 8, 2299); disp.dispatch_dialog(true, 0x17C, &mut cpu, &mut bus)
17175 .unwrap()
17176 .unwrap();
17177
17178 let dialog_ptr = bus.read_long(TEST_SP + 10);
17179 let copied_ditl_ptr = bus.read_long(bus.read_long(dialog_ptr + 156));
17180 assert_ne!(dialog_ptr, 0);
17181 assert_ne!(copied_ditl_ptr, original_ditl_ptr);
17182
17183 for item_no in 1..=5 {
17184 assert_eq!(
17185 ditl_item_handle_field(&bus, original_ditl_ptr, item_no),
17186 0,
17187 "resource DITL item {} handle field must stay reserved",
17188 item_no
17189 );
17190 }
17191
17192 let static_handle = ditl_item_handle_field(&bus, copied_ditl_ptr, 1);
17193 let edit_handle = ditl_item_handle_field(&bus, copied_ditl_ptr, 2);
17194 let control_handle = ditl_item_handle_field(&bus, copied_ditl_ptr, 3);
17195 let icon_handle = ditl_item_handle_field(&bus, copied_ditl_ptr, 4);
17196 let pict_handle = ditl_item_handle_field(&bus, copied_ditl_ptr, 5);
17197
17198 assert_eq!(
17199 bus.read_bytes(bus.read_long(static_handle), 6),
17200 b"Static".to_vec()
17201 );
17202 assert_eq!(
17203 bus.read_bytes(bus.read_long(edit_handle), 4),
17204 b"Edit".to_vec()
17205 );
17206 assert_eq!(bus.read_long(bus.read_long(control_handle) + 4), dialog_ptr);
17207 assert_eq!(
17208 disp.dialog_control_handles.get(&control_handle),
17209 Some(&(dialog_ptr, 3))
17210 );
17211 assert_eq!(bus.read_long(icon_handle), icon_ptr);
17212 assert_eq!(bus.read_long(pict_handle), pict_ptr);
17213 }
17214
17215 #[test]
17216 fn get_new_dialog_centers_standard_dlog_with_position_constant() {
17217 let (mut disp, mut cpu, mut bus) = setup();
17218 let screen_base = bus.alloc((800 * 600) as u32);
17219 bus.write_long(0x0824, screen_base);
17220 disp.screen_mode = (screen_base, 800, 800, 600, 8);
17221
17222 let dlog = build_test_dlog((10, 20, 110, 220), 1503, 0x280A);
17223 let ditl = build_test_ditl_item(4, (20, 20, 40, 80), b"OK");
17224 disp.install_test_resource(&mut bus, *b"DLOG", 1502, &dlog);
17225 disp.install_test_resource(&mut bus, *b"DITL", 1503, &ditl);
17226 bus.write_long(TEST_SP, 0); bus.write_long(TEST_SP + 4, 0); bus.write_word(TEST_SP + 8, 1502); let result = disp.dispatch_dialog(true, 0x17C, &mut cpu, &mut bus);
17231 assert!(result.unwrap().is_ok());
17232
17233 let dlg_ptr = bus.read_long(TEST_SP + 10);
17234 assert_ne!(dlg_ptr, 0);
17235 assert_eq!(disp.window_bounds, (250, 300, 350, 500));
17236 }
17237
17238 #[test]
17239 fn get_new_dialog_uses_system7_alert_position_near_top() {
17240 let (mut disp, mut cpu, mut bus) = setup();
17245 let screen_base = bus.alloc((800 * 600) as u32);
17246 bus.write_long(0x0824, screen_base);
17247 disp.screen_mode = (screen_base, 800, 800, 600, 8);
17248
17249 let dlog = build_test_dlog((40, 40, 310, 530), 1513, 0x300A);
17250 let ditl = build_test_ditl_item(4, (241, 291, 261, 381), b"Not Yet");
17251 disp.install_test_resource(&mut bus, *b"DLOG", 1512, &dlog);
17252 disp.install_test_resource(&mut bus, *b"DITL", 1513, &ditl);
17253 bus.write_long(TEST_SP, 0); bus.write_long(TEST_SP + 4, 0); bus.write_word(TEST_SP + 8, 1512); let result = disp.dispatch_dialog(true, 0x17C, &mut cpu, &mut bus);
17258 assert!(result.unwrap().is_ok());
17259
17260 let dlg_ptr = bus.read_long(TEST_SP + 10);
17261 assert_ne!(dlg_ptr, 0);
17262 assert_eq!(disp.window_bounds, (66, 155, 336, 645));
17263 }
17264
17265 #[test]
17266 fn get_new_dialog_draws_visible_shell_with_unresolved_user_item_proc() {
17267 let (mut disp, mut cpu, mut bus) = setup();
17272 let screen_base = bus.alloc((800 * 600) as u32);
17273 bus.write_bytes(screen_base, &vec![0x77; 800 * 600]);
17274 bus.write_long(0x0824, screen_base);
17275 disp.screen_mode = (screen_base, 800, 800, 600, 8);
17276
17277 let mut dlog = build_test_dlog((100, 100, 180, 260), 1701, 0);
17278 dlog[10] = 1; let ditl = build_test_ditl_items(&[
17280 (0x80, (8, 8, 30, 80), b"".as_slice()),
17281 (4, (44, 20, 64, 90), b"OK".as_slice()),
17282 ]);
17283 disp.install_test_resource(&mut bus, *b"DLOG", 1700, &dlog);
17284 disp.install_test_resource(&mut bus, *b"DITL", 1701, &ditl);
17285 bus.write_long(TEST_SP, 0xFFFF_FFFF); bus.write_long(TEST_SP + 4, 0); bus.write_word(TEST_SP + 8, 1700); let result = disp.dispatch_dialog(true, 0x17C, &mut cpu, &mut bus);
17290 assert!(result.unwrap().is_ok());
17291
17292 let dlg_ptr = bus.read_long(TEST_SP + 10);
17293 assert_ne!(dlg_ptr, 0);
17294 assert_eq!(bus.read_byte(dlg_ptr + 110), 0xFF);
17295 assert!(disp.dialog_items.contains_key(&dlg_ptr));
17296 assert!(
17297 disp.dialog_saved_pixels.contains_key(&dlg_ptr),
17298 "visible dialogs save the covered pixels before drawing"
17299 );
17300 assert!(
17301 disp.dialog_visible_snapshots.contains_key(&dlg_ptr),
17302 "visible dialogs retain their clean initial shell for first ModalDialog entry"
17303 );
17304 assert!(!disp.dialog_initial_draw_deferred.contains(&dlg_ptr));
17305 assert!(
17306 disp.event_queue
17307 .iter()
17308 .any(|event| event.what == 6 && event.message == dlg_ptr),
17309 "visible dialog should still get an update event"
17310 );
17311
17312 let probe_addr = screen_base + 120 * 800 + 120;
17313 assert_ne!(
17314 bus.read_byte(probe_addr),
17315 0x77,
17316 "visible GetNewDialog should draw the standard dialog background immediately"
17317 );
17318 }
17319
17320 #[test]
17321 fn hidden_dialog_quickdraw_shape_is_clipped_by_empty_vis_region() {
17322 let (mut disp, mut cpu, mut bus) = setup();
17327 let screen_base = bus.alloc((800 * 600) as u32);
17328 bus.write_bytes(screen_base, &vec![0x77; 800 * 600]);
17329 bus.write_long(0x0824, screen_base);
17330 disp.screen_mode = (screen_base, 800, 800, 600, 8);
17331
17332 let dlog = build_test_dlog((100, 100, 180, 260), 1705, 0);
17333 let ditl = build_test_ditl_item(8, (8, 8, 24, 90), b"Hidden");
17334 disp.install_test_resource(&mut bus, *b"DLOG", 1704, &dlog);
17335 disp.install_test_resource(&mut bus, *b"DITL", 1705, &ditl);
17336 bus.write_long(TEST_SP, 0xFFFF_FFFF); bus.write_long(TEST_SP + 4, 0); bus.write_word(TEST_SP + 8, 1704); let result = disp.dispatch_dialog(true, 0x17C, &mut cpu, &mut bus);
17341 assert!(result.unwrap().is_ok());
17342 let dlg_ptr = bus.read_long(TEST_SP + 10);
17343 assert_ne!(dlg_ptr, 0);
17344 assert_eq!(bus.read_byte(dlg_ptr + 110), 0);
17345 assert_eq!(
17346 TrapDispatcher::region_handle_rect(&bus, bus.read_long(dlg_ptr + 24)),
17347 None
17348 );
17349
17350 let rect_ptr = bus.alloc(8);
17351 bus.write_word(rect_ptr, 20);
17352 bus.write_word(rect_ptr + 2, 20);
17353 bus.write_word(rect_ptr + 4, 30);
17354 bus.write_word(rect_ptr + 6, 30);
17355 bus.write_long(TEST_SP, rect_ptr);
17356
17357 let result = disp.dispatch_quickdraw(true, 0x0A2, &mut cpu, &mut bus);
17358 assert!(result.unwrap().is_ok());
17359
17360 let probe_addr = screen_base + 125 * 800 + 125;
17361 assert_eq!(
17362 bus.read_byte(probe_addr),
17363 0x77,
17364 "QuickDraw drawing through a hidden dialog port must be clipped"
17365 );
17366 assert!(
17367 !disp.dialog_saved_pixels.contains_key(&dlg_ptr),
17368 "no visible draw means no saved-under snapshot"
17369 );
17370 }
17371
17372 #[test]
17373 fn dispos_dialog_skips_saved_pixels_for_never_drawn_deferred_dialog() {
17374 let (mut disp, mut cpu, mut bus) = setup();
17379 let screen_base = bus.alloc((800 * 600) as u32);
17380 bus.write_bytes(screen_base, &vec![0x77; 800 * 600]);
17381 bus.write_long(0x0824, screen_base);
17382 disp.screen_mode = (screen_base, 800, 800, 600, 8);
17383
17384 let mut dlog = build_test_dlog((100, 100, 180, 260), 1703, 0);
17385 dlog[10] = 1; let ditl = build_test_ditl_items(&[
17387 (0x80, (8, 8, 30, 80), b"".as_slice()),
17388 (4, (44, 20, 64, 90), b"OK".as_slice()),
17389 ]);
17390 disp.install_test_resource(&mut bus, *b"DLOG", 1702, &dlog);
17391 disp.install_test_resource(&mut bus, *b"DITL", 1703, &ditl);
17392 bus.write_long(TEST_SP, 0xFFFF_FFFF); bus.write_long(TEST_SP + 4, 0); bus.write_word(TEST_SP + 8, 1702); let result = disp.dispatch_dialog(true, 0x17C, &mut cpu, &mut bus);
17397 assert!(result.unwrap().is_ok());
17398 let dlg_ptr = bus.read_long(TEST_SP + 10);
17399 disp.dialog_initial_draw_deferred.insert(dlg_ptr);
17400
17401 disp.dialog_saved_pixels
17404 .insert(dlg_ptr, vec![0x33; 90 * 170]);
17405 let probe_addr = screen_base + 120 * 800 + 120;
17406 bus.write_byte(probe_addr, 0x77);
17407
17408 bus.write_long(TEST_SP, dlg_ptr);
17409 let result = disp.dispatch_dialog(true, 0x183, &mut cpu, &mut bus);
17410 assert!(result.unwrap().is_ok());
17411
17412 assert_eq!(
17413 bus.read_byte(probe_addr),
17414 0x77,
17415 "never-drawn deferred dialog must not restore saved background"
17416 );
17417 assert!(!disp.dialog_initial_draw_deferred.contains(&dlg_ptr));
17418 assert!(!disp.dialog_saved_pixels.contains_key(&dlg_ptr));
17419 }
17420
17421 #[test]
17426 fn save_dialog_pixels_handles_top_below_dbox_margin_without_overflow() {
17427 let (disp, _cpu, mut bus) = setup();
17428 let screen_base = bus.alloc((800 * 600) as u32);
17429 for i in 0..800u32 * 600 {
17430 bus.write_byte(screen_base + i, 0x42);
17431 }
17432 let mut d = disp;
17433 bus.write_long(0x0824, screen_base);
17434 d.screen_mode = (screen_base, 800, 800, 600, 8);
17435
17436 let saved = d.save_dialog_pixels(&bus, (2, 2, 50, 50));
17438 let row_width = 64usize;
17440 let row_count = 64usize;
17441 assert_eq!(saved.len(), row_width * row_count);
17442
17443 for row in 0..6 {
17445 for col in 0..row_width {
17446 assert_eq!(
17447 saved[row * row_width + col],
17448 0x00,
17449 "off-screen row {} col {} must be zero-padded",
17450 row,
17451 col
17452 );
17453 }
17454 }
17455 let row0 = &saved[6 * row_width..7 * row_width];
17458 for (col, &px) in row0.iter().enumerate().take(6) {
17459 assert_eq!(
17460 px, 0x00,
17461 "off-screen column {} within on-screen row must be zero",
17462 col
17463 );
17464 }
17465 for (col, &px) in row0.iter().enumerate().skip(6) {
17466 assert_eq!(
17467 px, 0x42,
17468 "on-screen pixel at col {} must round-trip via framebuffer",
17469 col
17470 );
17471 }
17472 }
17473
17474 #[test]
17477 fn save_rect_pixels_handles_negative_top_without_overflow() {
17478 let (disp, _cpu, mut bus) = setup();
17479 let screen_base = bus.alloc((800 * 600) as u32);
17480 for i in 0..800u32 * 600 {
17481 bus.write_byte(screen_base + i, 0x55);
17482 }
17483 let mut d = disp;
17484 bus.write_long(0x0824, screen_base);
17485 d.screen_mode = (screen_base, 800, 800, 600, 8);
17486
17487 let saved = d.save_rect_pixels(&bus, (-3, 0, 5, 10));
17489 let row_width = 10usize;
17490 let row_count = (5 - (-3)) as usize;
17491 assert_eq!(saved.len(), row_width * row_count);
17492 for row in 0..3 {
17494 for col in 0..row_width {
17495 assert_eq!(saved[row * row_width + col], 0x00);
17496 }
17497 }
17498 for row in 3..8 {
17500 for col in 0..row_width {
17501 assert_eq!(saved[row * row_width + col], 0x55);
17502 }
17503 }
17504 }
17505
17506 #[test]
17507 fn save_dialog_pixels_handles_top_above_screen_height_without_overflow() {
17508 let (disp, _cpu, mut bus) = setup();
17509 let screen_base = bus.alloc((800 * 600) as u32);
17510 let mut d = disp;
17511 bus.write_long(0x0824, screen_base);
17512 d.screen_mode = (screen_base, 800, 800, 600, 8);
17513
17514 let saved = d.save_dialog_pixels(&bus, (600, 0, 700, 50));
17517 let row_count = (700 + TrapDispatcher::DBOX_FRAME_MARGIN
17518 - (600 - TrapDispatcher::DBOX_FRAME_MARGIN)) as usize;
17519 let row_width = (50 + TrapDispatcher::DBOX_FRAME_MARGIN
17520 - (0 - TrapDispatcher::DBOX_FRAME_MARGIN)) as usize;
17521 assert_eq!(saved.len(), row_count * row_width);
17522 }
17523
17524 fn install_new_dialog_test_screen(
17525 disp: &mut TrapDispatcher,
17526 bus: &mut MacMemoryBus,
17527 fill: u8,
17528 ) -> u32 {
17529 let screen_base = bus.alloc((640 * 480) as u32);
17530 bus.write_bytes(screen_base, &vec![fill; 640 * 480]);
17531 bus.write_long(0x0824, screen_base);
17532 disp.screen_mode = (screen_base, 640, 640, 480, 8);
17533 screen_base
17534 }
17535
17536 fn call_new_dialog_for_test(
17537 disp: &mut TrapDispatcher,
17538 cpu: &mut MockCpu,
17539 bus: &mut MacMemoryBus,
17540 storage_ptr: u32,
17541 bounds: (i16, i16, i16, i16),
17542 visible: bool,
17543 proc_id: i16,
17544 behind: u32,
17545 items_handle: u32,
17546 ) -> (u32, u32) {
17547 let title_ptr = bus.alloc(32);
17548 bus.write_pstring(title_ptr, b"Lifecycle");
17549 let bounds_ptr = bus.alloc(8);
17550 bus.write_word(bounds_ptr, bounds.0 as u16);
17551 bus.write_word(bounds_ptr + 2, bounds.1 as u16);
17552 bus.write_word(bounds_ptr + 4, bounds.2 as u16);
17553 bus.write_word(bounds_ptr + 6, bounds.3 as u16);
17554
17555 let sp = TEST_SP - 30;
17556 cpu.write_reg(Register::A7, sp);
17557 for offset in 0..34u32 {
17558 bus.write_byte(sp + offset, 0);
17559 }
17560 bus.write_long(sp, items_handle);
17561 bus.write_long(sp + 4, 0x1234_5678); bus.write_byte(sp + 8, 0xFF); bus.write_long(sp + 10, behind);
17564 bus.write_word(sp + 14, proc_id as u16);
17565 bus.write_byte(sp + 16, if visible { 0xFF } else { 0 });
17566 bus.write_long(sp + 18, title_ptr);
17567 bus.write_long(sp + 22, bounds_ptr);
17568 bus.write_long(sp + 26, storage_ptr);
17569 bus.write_long(sp + 30, 0xDEAD_BEEF);
17570
17571 disp.dispatch_dialog(true, 0x17D, cpu, bus)
17572 .unwrap()
17573 .unwrap();
17574 (sp, bus.read_long(sp + 30))
17575 }
17576
17577 fn install_ditl_handle_for_test(bus: &mut MacMemoryBus, ditl: &[u8]) -> (u32, u32) {
17578 let items_ptr = bus.alloc(ditl.len() as u32);
17579 bus.write_bytes(items_ptr, ditl);
17580 let items_handle = bus.alloc(4);
17581 bus.write_long(items_handle, items_ptr);
17582 (items_handle, items_ptr)
17583 }
17584
17585 #[test]
17586 fn new_dialog_with_storage_uses_caller_record() {
17587 let (mut disp, mut cpu, mut bus) = setup();
17590 install_new_dialog_test_screen(&mut disp, &mut bus, 0x77);
17591 let storage_ptr = bus.alloc(170);
17592
17593 let (sp, dialog_ptr) = call_new_dialog_for_test(
17594 &mut disp,
17595 &mut cpu,
17596 &mut bus,
17597 storage_ptr,
17598 (80, 90, 150, 260),
17599 false,
17600 4,
17601 0xFFFF_FFFF,
17602 0,
17603 );
17604
17605 assert_eq!(dialog_ptr, storage_ptr);
17606 assert_eq!(bus.read_long(sp + 30), storage_ptr);
17607 assert_eq!(cpu.read_reg(Register::A7), sp + 30);
17608 assert_eq!(disp.window_list, vec![storage_ptr]);
17609 }
17610
17611 #[test]
17612 fn new_dialog_without_storage_allocates_record() {
17613 let (mut disp, mut cpu, mut bus) = setup();
17616 install_new_dialog_test_screen(&mut disp, &mut bus, 0x77);
17617
17618 let (_sp, dialog_ptr) = call_new_dialog_for_test(
17619 &mut disp,
17620 &mut cpu,
17621 &mut bus,
17622 0,
17623 (80, 90, 150, 260),
17624 false,
17625 2,
17626 0xFFFF_FFFF,
17627 0,
17628 );
17629
17630 assert_ne!(dialog_ptr, 0);
17631 assert_eq!(bus.get_alloc_size(dialog_ptr), Some(170));
17632 assert_eq!(disp.window_list, vec![dialog_ptr]);
17633 }
17634
17635 #[test]
17636 fn new_dialog_sets_window_kind_dialog_kind() {
17637 let (mut disp, mut cpu, mut bus) = setup();
17641 install_new_dialog_test_screen(&mut disp, &mut bus, 0x77);
17642
17643 let (_sp, dialog_ptr) = call_new_dialog_for_test(
17644 &mut disp,
17645 &mut cpu,
17646 &mut bus,
17647 0,
17648 (80, 90, 150, 260),
17649 false,
17650 4,
17651 0xFFFF_FFFF,
17652 0,
17653 );
17654
17655 assert_eq!(bus.read_word(dialog_ptr + 108) as i16, 2);
17656 assert_eq!(disp.window_proc_ids.get(&dialog_ptr), Some(&4));
17657 }
17658
17659 #[test]
17660 fn new_dialog_sets_dialog_port_font_to_dialog_font() {
17661 let (mut disp, mut cpu, mut bus) = setup();
17664 install_new_dialog_test_screen(&mut disp, &mut bus, 0x77);
17665 bus.write_word(crate::memory::globals::addr::DLG_FONT, 3); let (_sp, dialog_ptr) = call_new_dialog_for_test(
17668 &mut disp,
17669 &mut cpu,
17670 &mut bus,
17671 0,
17672 (80, 90, 150, 260),
17673 false,
17674 2,
17675 0xFFFF_FFFF,
17676 0,
17677 );
17678
17679 assert_eq!(bus.read_word(dialog_ptr + 68) as i16, 3);
17680 assert_eq!(disp.tx_font, 3);
17681 assert_eq!(
17682 disp.port_draw_states
17683 .get(&dialog_ptr)
17684 .map(|state| state.tx_font),
17685 Some(3)
17686 );
17687 }
17688
17689 #[test]
17690 fn new_dialog_visible_queues_or_draws_expected_update_path() {
17691 let (mut disp, mut cpu, mut bus) = setup();
17694 let screen_base = install_new_dialog_test_screen(&mut disp, &mut bus, 0x77);
17695
17696 let (_sp, dialog_ptr) = call_new_dialog_for_test(
17697 &mut disp,
17698 &mut cpu,
17699 &mut bus,
17700 0,
17701 (80, 90, 150, 260),
17702 true,
17703 2,
17704 0xFFFF_FFFF,
17705 0,
17706 );
17707
17708 assert_eq!(bus.read_byte(dialog_ptr + 110), 0xFF);
17709 assert!(disp
17710 .event_queue
17711 .iter()
17712 .any(|event| event.what == 6 && event.message == dialog_ptr));
17713 assert!(
17714 TrapDispatcher::region_handle_rect(&bus, bus.read_long(dialog_ptr + 122)).is_some()
17715 );
17716 assert_ne!(bus.read_byte(screen_base + 100 * 640 + 110), 0x77);
17717 }
17718
17719 #[test]
17720 fn new_dialog_invisible_defers_screen_pixels() {
17721 let (mut disp, mut cpu, mut bus) = setup();
17724 let screen_base = install_new_dialog_test_screen(&mut disp, &mut bus, 0x77);
17725
17726 let (_sp, dialog_ptr) = call_new_dialog_for_test(
17727 &mut disp,
17728 &mut cpu,
17729 &mut bus,
17730 0,
17731 (80, 90, 150, 260),
17732 false,
17733 2,
17734 0xFFFF_FFFF,
17735 0,
17736 );
17737
17738 assert_eq!(bus.read_byte(dialog_ptr + 110), 0);
17739 assert!(!disp
17740 .event_queue
17741 .iter()
17742 .any(|event| event.what == 6 && event.message == dialog_ptr));
17743 assert_eq!(
17744 TrapDispatcher::region_handle_rect(&bus, bus.read_long(dialog_ptr + 122)),
17745 None
17746 );
17747 assert_eq!(bus.read_byte(screen_base + 100 * 640 + 110), 0x77);
17748 }
17749
17750 #[test]
17751 fn new_dialog_honors_behind_nil_and_inserts_at_back() {
17752 let (mut disp, mut cpu, mut bus) = setup();
17755 let existing = 0x200040u32;
17757 disp.window_list = vec![existing];
17758 disp.front_window = existing;
17759 bus.write_byte(existing + 110u32, 0xFF); let screen_base = bus.alloc((800 * 600) as u32);
17764 bus.write_long(0x0824, screen_base);
17765 disp.screen_mode = (screen_base, 800, 800, 600, 8);
17766
17767 let bounds_rect_ptr = 0x301200u32;
17770 bus.write_word(bounds_rect_ptr, 100);
17771 bus.write_word(bounds_rect_ptr + 2, 100);
17772 bus.write_word(bounds_rect_ptr + 4, 300);
17773 bus.write_word(bounds_rect_ptr + 6, 400);
17774
17775 let sp = TEST_SP - 30;
17776 cpu.write_reg(Register::A7, sp);
17777 for i in 0..34u32 {
17778 bus.write_byte(sp + i, 0);
17779 }
17780 bus.write_long(sp + 22, bounds_rect_ptr);
17781 bus.write_word(sp + 16, 1); bus.write_word(sp + 14, 1); bus.write_long(sp + 10, 0); let pre_a7 = cpu.read_reg(Register::A7);
17786 let result = disp.dispatch_dialog(true, 0x17D, &mut cpu, &mut bus);
17787 assert!(result.unwrap().is_ok());
17788 assert_eq!(
17789 cpu.read_reg(Register::A7),
17790 pre_a7 + 30,
17791 "NewDialog must pop 30 bytes of parameters and leave result at new SP+0"
17792 );
17793 let dlg_ptr = bus.read_long(sp + 30);
17794 assert_ne!(dlg_ptr, 0);
17795 assert_eq!(
17796 bus.read_word(dlg_ptr + 108),
17797 2,
17798 "DialogRecord.window.windowKind must be dialogKind"
17799 );
17800 assert_eq!(
17801 disp.window_proc_ids.get(&dlg_ptr),
17802 Some(&1),
17803 "dialog WDEF procID must be tracked separately from windowKind"
17804 );
17805 assert_eq!(
17806 disp.window_list,
17807 vec![existing, dlg_ptr],
17808 "NewDialog(behind=NIL) must insert dialog at the back"
17809 );
17810 assert_eq!(
17811 disp.front_window, existing,
17812 "front must stay on the pre-existing visible window"
17813 );
17814 }
17815
17816 #[test]
17817 fn new_cdialog_honors_behind_nil_and_inserts_at_back() {
17818 let (mut disp, mut cpu, mut bus) = setup();
17821 let existing = 0x200040u32;
17822 disp.window_list = vec![existing];
17823 disp.front_window = existing;
17824 bus.write_byte(existing + 110u32, 0xFF); let screen_base = bus.alloc((800 * 600) as u32);
17827 bus.write_long(0x0824, screen_base);
17828 disp.screen_mode = (screen_base, 800, 800, 600, 8);
17829
17830 let bounds_rect_ptr = 0x301280u32;
17831 bus.write_word(bounds_rect_ptr, 120);
17832 bus.write_word(bounds_rect_ptr + 2, 120);
17833 bus.write_word(bounds_rect_ptr + 4, 320);
17834 bus.write_word(bounds_rect_ptr + 6, 420);
17835
17836 let sp = TEST_SP - 30;
17837 cpu.write_reg(Register::A7, sp);
17838 for i in 0..34u32 {
17839 bus.write_byte(sp + i, 0);
17840 }
17841 bus.write_long(sp + 22, bounds_rect_ptr);
17842 bus.write_word(sp + 16, 1); bus.write_long(sp + 10, 0); let pre_a7 = cpu.read_reg(Register::A7);
17846 let result = disp.dispatch_dialog(true, 0x24B, &mut cpu, &mut bus);
17847 assert!(result.unwrap().is_ok());
17848 assert_eq!(
17849 cpu.read_reg(Register::A7),
17850 pre_a7 + 30,
17851 "NewCDialog must pop 30 bytes of parameters and leave result at new SP+0"
17852 );
17853 let dlg_ptr = bus.read_long(sp + 30);
17854 assert_ne!(dlg_ptr, 0);
17855 assert_eq!(
17856 disp.window_list,
17857 vec![existing, dlg_ptr],
17858 "NewCDialog(behind=NIL) must insert dialog at the back"
17859 );
17860 assert_eq!(
17861 disp.front_window, existing,
17862 "front must stay on the pre-existing visible window"
17863 );
17864 }
17865
17866 #[test]
17867 fn get_new_dialog_honors_behind_specific_window() {
17868 let (mut disp, mut cpu, mut bus) = setup();
17876
17877 let sp = TEST_SP;
17878 bus.write_long(sp, 0xDEAD0000);
17880
17881 let result = disp.dispatch_dialog(true, 0x17C, &mut cpu, &mut bus);
17882 assert!(result.unwrap().is_ok());
17883 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 10);
17884 }
17885
17886 #[test]
17889 fn select_dialog_item_text_zero_to_32767_selects_entire_text_and_sets_editfield() {
17890 let (mut disp, mut cpu, mut bus) = setup();
17893 let dialog_ptr = bus.alloc(170);
17894 bus.write_word(dialog_ptr + 164, 0xFFFF); disp.dialog_items.insert(
17897 dialog_ptr,
17898 vec![DialogItem {
17899 item_type: 16,
17900 rect: (10, 20, 30, 40),
17901 text: "ABCDE".to_string(),
17902 resource_id: 0,
17903 proc_ptr: 0,
17904 sel_start: 0,
17905 sel_end: 0,
17906 }],
17907 );
17908
17909 bus.write_word(TEST_SP, 32767u16); bus.write_word(TEST_SP + 2, 0); bus.write_word(TEST_SP + 4, 1); bus.write_long(TEST_SP + 6, dialog_ptr); let result = disp.dispatch_dialog(true, 0x17E, &mut cpu, &mut bus);
17915 assert!(result.unwrap().is_ok());
17916 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 10);
17917
17918 let item = &disp.dialog_items[&dialog_ptr][0];
17919 assert_eq!(item.sel_start, 0);
17920 assert_eq!(item.sel_end, 5);
17921 assert_eq!(bus.read_word(dialog_ptr + 164), 0);
17922 }
17923
17924 #[test]
17925 fn select_dialog_item_text_clamps_and_normalizes_selection_bounds() {
17926 let (mut disp, mut cpu, mut bus) = setup();
17930 let dialog_ptr = bus.alloc(170);
17931
17932 disp.dialog_items.insert(
17933 dialog_ptr,
17934 vec![DialogItem {
17935 item_type: 16,
17936 rect: (10, 20, 30, 40),
17937 text: "ABCDE".to_string(),
17938 resource_id: 0,
17939 proc_ptr: 0,
17940 sel_start: 0,
17941 sel_end: 0,
17942 }],
17943 );
17944
17945 bus.write_word(TEST_SP, 2); bus.write_word(TEST_SP + 2, 9); bus.write_word(TEST_SP + 4, 1); bus.write_long(TEST_SP + 6, dialog_ptr); let result = disp.dispatch_dialog(true, 0x17E, &mut cpu, &mut bus);
17951 assert!(result.unwrap().is_ok());
17952 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 10);
17953
17954 let item = &disp.dialog_items[&dialog_ptr][0];
17955 assert_eq!(item.sel_start, 2);
17956 assert_eq!(item.sel_end, 5);
17957 }
17958
17959 #[test]
17960 fn select_dialog_item_text_non_edit_item_is_noop() {
17961 let (mut disp, mut cpu, mut bus) = setup();
17964 let dialog_ptr = bus.alloc(170);
17965 bus.write_word(dialog_ptr + 164, 0x7FFF); disp.dialog_items.insert(
17968 dialog_ptr,
17969 vec![DialogItem {
17970 item_type: 8, rect: (10, 20, 30, 40),
17972 text: "STATIC".to_string(),
17973 resource_id: 0,
17974 proc_ptr: 0,
17975 sel_start: 1,
17976 sel_end: 3,
17977 }],
17978 );
17979
17980 bus.write_word(TEST_SP, 4); bus.write_word(TEST_SP + 2, 0); bus.write_word(TEST_SP + 4, 1); bus.write_long(TEST_SP + 6, dialog_ptr); let result = disp.dispatch_dialog(true, 0x17E, &mut cpu, &mut bus);
17986 assert!(result.unwrap().is_ok());
17987 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 10);
17988
17989 let item = &disp.dialog_items[&dialog_ptr][0];
17990 assert_eq!(item.sel_start, 1);
17991 assert_eq!(item.sel_end, 3);
17992 assert_eq!(bus.read_word(dialog_ptr + 164), 0x7FFF);
17993 }
17994
17995 #[test]
18000 fn alert_returns_minus_one_when_alrt_resource_missing() {
18001 let (mut disp, mut cpu, mut bus) = setup();
18002
18003 bus.write_word(TEST_SP + 4, 128);
18006 let result = disp.dispatch_dialog(true, 0x185, &mut cpu, &mut bus);
18007 assert!(result.unwrap().is_ok());
18008 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 6);
18009 assert_eq!(bus.read_word(TEST_SP + 6) as i16, -1);
18010 }
18011
18012 #[test]
18018 fn te_line_origin_te_just_left_flush_left() {
18019 assert_eq!(
18021 TrapDispatcher::te_line_origin_x(0, 20, 200, 80),
18022 21,
18023 "teJustLeft (0) must anchor one pixel inside box_left"
18024 );
18025 }
18026
18027 #[test]
18028 fn te_line_origin_te_just_center_midpoint() {
18029 assert_eq!(
18032 TrapDispatcher::te_line_origin_x(1, 20, 200, 80),
18033 70,
18034 "teJustCenter must midpoint the slack"
18035 );
18036 }
18037
18038 #[test]
18039 fn te_line_origin_te_just_right_flush_right() {
18040 assert_eq!(
18043 TrapDispatcher::te_line_origin_x(-1, 20, 200, 80),
18044 120,
18045 "teJustRight (-1) must anchor at box_right - line_width"
18046 );
18047 }
18048
18049 #[test]
18050 fn te_line_origin_te_force_left_flush_left() {
18051 assert_eq!(
18055 TrapDispatcher::te_line_origin_x(-2, 20, 200, 80),
18056 21,
18057 "teForceLeft (-2) must anchor one pixel inside box_left"
18058 );
18059 }
18060
18061 #[test]
18062 fn te_line_origin_te_just_system_defaults_left() {
18063 assert_eq!(
18066 TrapDispatcher::te_line_origin_x(0, 20, 200, 80),
18067 21,
18068 "teJustSystem must default to flush left inside box_left"
18069 );
18070 }
18071
18072 #[test]
18075 fn stop_alert_returns_minus_one_when_alrt_resource_missing() {
18076 let (mut disp, mut cpu, mut bus) = setup();
18077 bus.write_word(crate::memory::globals::addr::ALERT_STAGE, 2);
18078 bus.write_word(crate::memory::globals::addr::ANUMBER, 0xC0DE);
18079 bus.write_word(TEST_SP + 4, 128);
18080 let result = disp.dispatch_dialog(true, 0x186, &mut cpu, &mut bus);
18081 assert!(result.unwrap().is_ok());
18082 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 6);
18083 assert_eq!(bus.read_word(TEST_SP + 6) as i16, -1);
18084 assert_eq!(bus.read_word(crate::memory::globals::addr::ALERT_STAGE), 2);
18085 assert_eq!(bus.read_word(crate::memory::globals::addr::ANUMBER), 0xC0DE);
18086 }
18087
18088 #[test]
18091 fn note_alert_returns_minus_one_when_alrt_resource_missing() {
18092 let (mut disp, mut cpu, mut bus) = setup();
18093 bus.write_word(crate::memory::globals::addr::ALERT_STAGE, 1);
18094 bus.write_word(crate::memory::globals::addr::ANUMBER, 0xBEEF);
18095 bus.write_word(TEST_SP + 4, 128);
18096 let result = disp.dispatch_dialog(true, 0x187, &mut cpu, &mut bus);
18097 assert!(result.unwrap().is_ok());
18098 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 6);
18099 assert_eq!(bus.read_word(TEST_SP + 6) as i16, -1);
18100 assert_eq!(bus.read_word(crate::memory::globals::addr::ALERT_STAGE), 1);
18101 assert_eq!(bus.read_word(crate::memory::globals::addr::ANUMBER), 0xBEEF);
18102 }
18103
18104 #[test]
18107 fn caution_alert_returns_minus_one_when_alrt_resource_missing() {
18108 let (mut disp, mut cpu, mut bus) = setup();
18109 bus.write_word(crate::memory::globals::addr::ALERT_STAGE, 3);
18110 bus.write_word(crate::memory::globals::addr::ANUMBER, 0xFACE);
18111 bus.write_word(TEST_SP + 4, 128);
18112 let result = disp.dispatch_dialog(true, 0x188, &mut cpu, &mut bus);
18113 assert!(result.unwrap().is_ok());
18114 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 6);
18115 assert_eq!(bus.read_word(TEST_SP + 6) as i16, -1);
18116 assert_eq!(bus.read_word(crate::memory::globals::addr::ALERT_STAGE), 3);
18117 assert_eq!(bus.read_word(crate::memory::globals::addr::ANUMBER), 0xFACE);
18118 }
18119
18120 fn build_alrt_template(items_id: i16, stages: u16) -> Vec<u8> {
18136 let mut v = Vec::with_capacity(12);
18137 v.extend_from_slice(&0u16.to_be_bytes()); v.extend_from_slice(&0u16.to_be_bytes()); v.extend_from_slice(&80u16.to_be_bytes()); v.extend_from_slice(&200u16.to_be_bytes()); v.extend_from_slice(&items_id.to_be_bytes()); v.extend_from_slice(&stages.to_be_bytes()); v
18144 }
18145
18146 #[test]
18149 fn could_dialog_present_resource_sets_reserr_noerr() {
18150 let (mut disp, mut cpu, mut bus) = setup();
18154 let dlog = vec![0u8; 20];
18155 disp.install_test_resource(&mut bus, *b"DLOG", 300, &dlog);
18156 bus.write_word(0x0A60, 0x7FFF);
18157 bus.write_word(TEST_SP, 300);
18158
18159 let pre_a7 = cpu.read_reg(Register::A7);
18160 let result = disp.dispatch_dialog(true, 0x179, &mut cpu, &mut bus);
18161 assert!(result.unwrap().is_ok());
18162 assert_eq!(cpu.read_reg(Register::A7), pre_a7 + 2);
18163 assert_eq!(bus.read_word(0x0A60) as i16, 0);
18164 }
18165
18166 #[test]
18167 fn could_dialog_missing_resource_sets_reserr_resnotfound() {
18168 let (mut disp, mut cpu, mut bus) = setup();
18170 bus.write_word(0x0A60, 0);
18171 bus.write_word(TEST_SP, 301);
18172
18173 let pre_a7 = cpu.read_reg(Register::A7);
18174 let result = disp.dispatch_dialog(true, 0x179, &mut cpu, &mut bus);
18175 assert!(result.unwrap().is_ok());
18176 assert_eq!(cpu.read_reg(Register::A7), pre_a7 + 2);
18177 assert_eq!(bus.read_word(0x0A60) as i16, 0);
18178 }
18179
18180 #[test]
18181 fn free_dialog_present_resource_sets_reserr_noerr() {
18182 let (mut disp, mut cpu, mut bus) = setup();
18186 let dlog = vec![0u8; 20];
18187 disp.install_test_resource(&mut bus, *b"DLOG", 302, &dlog);
18188 bus.write_word(0x0A60, 0x7FFF);
18189 bus.write_word(TEST_SP, 302);
18190
18191 let pre_a7 = cpu.read_reg(Register::A7);
18192 let result = disp.dispatch_dialog(true, 0x17A, &mut cpu, &mut bus);
18193 assert!(result.unwrap().is_ok());
18194 assert_eq!(cpu.read_reg(Register::A7), pre_a7 + 2);
18195 assert_eq!(bus.read_word(0x0A60) as i16, 0);
18196 }
18197
18198 #[test]
18199 fn free_dialog_missing_resource_sets_reserr_resnotfound() {
18200 let (mut disp, mut cpu, mut bus) = setup();
18202 bus.write_word(0x0A60, 0);
18203 bus.write_word(TEST_SP, 303);
18204
18205 let pre_a7 = cpu.read_reg(Register::A7);
18206 let result = disp.dispatch_dialog(true, 0x17A, &mut cpu, &mut bus);
18207 assert!(result.unwrap().is_ok());
18208 assert_eq!(cpu.read_reg(Register::A7), pre_a7 + 2);
18209 assert_eq!(bus.read_word(0x0A60) as i16, 0);
18210 }
18211
18212 #[test]
18213 fn could_dialog_free_dialog_purgeability() {
18214 let (mut disp, mut cpu, mut bus) = setup();
18218 let dlog_id: i16 = 3100;
18219 let ditl_id: i16 = 3101;
18220 let cntl_id: i16 = 3102;
18221 let icon_id: i16 = 3103;
18222 let pict_id: i16 = 3104;
18223 let dlog = build_test_dlog((10, 20, 110, 220), ditl_id, 0);
18224 let ditl = build_test_ditl_items(&[
18225 (7, (1, 2, 12, 82), &cntl_id.to_be_bytes()),
18226 (32, (14, 2, 46, 34), &icon_id.to_be_bytes()),
18227 (64, (48, 2, 88, 82), &pict_id.to_be_bytes()),
18228 ]);
18229 disp.install_test_resource(&mut bus, *b"DLOG", dlog_id, &dlog);
18230 disp.install_test_resource(&mut bus, *b"DITL", ditl_id, &ditl);
18231 disp.install_test_resource(&mut bus, *b"CNTL", cntl_id, &[0u8; 32]);
18232 disp.install_test_resource(&mut bus, *b"ICON", icon_id, &[0xAA; 128]);
18233 disp.install_test_resource(&mut bus, *b"PICT", pict_id, &[0x11; 32]);
18234 disp.res_load = false;
18235
18236 bus.write_word(0x0A60, 0x7FFF);
18237 bus.write_word(TEST_SP, dlog_id as u16);
18238 let pre_a7 = cpu.read_reg(Register::A7);
18239 assert!(disp
18240 .dispatch_dialog(true, 0x179, &mut cpu, &mut bus)
18241 .unwrap()
18242 .is_ok());
18243 assert_eq!(cpu.read_reg(Register::A7), pre_a7 + 2);
18244 assert_eq!(bus.read_word(0x0A60) as i16, 0);
18245 for (res_type, res_id) in [
18246 (*b"DLOG", dlog_id),
18247 (*b"DITL", ditl_id),
18248 (*b"CNTL", cntl_id),
18249 (*b"ICON", icon_id),
18250 (*b"PICT", pict_id),
18251 ] {
18252 assert_resource_nonpurgeable(&disp, &bus, res_type, res_id);
18253 }
18254
18255 cpu.write_reg(Register::A7, TEST_SP);
18256 bus.write_word(TEST_SP, dlog_id as u16);
18257 assert!(disp
18258 .dispatch_dialog(true, 0x17A, &mut cpu, &mut bus)
18259 .unwrap()
18260 .is_ok());
18261 assert_eq!(bus.read_word(0x0A60) as i16, 0);
18262 for (res_type, res_id) in [
18263 (*b"DLOG", dlog_id),
18264 (*b"DITL", ditl_id),
18265 (*b"CNTL", cntl_id),
18266 (*b"ICON", icon_id),
18267 (*b"PICT", pict_id),
18268 ] {
18269 assert_resource_purgeable(&disp, res_type, res_id);
18270 }
18271 }
18272
18273 #[test]
18276 fn could_alert_present_resource_sets_reserr_noerr() {
18277 let (mut disp, mut cpu, mut bus) = setup();
18281 let alrt = build_alrt_template(200, 0x0000);
18282 disp.install_test_resource(&mut bus, *b"ALRT", 400, &alrt);
18283 bus.write_word(0x0A60, 0x7FFF);
18284 bus.write_word(TEST_SP, 400);
18285
18286 let pre_a7 = cpu.read_reg(Register::A7);
18287 let result = disp.dispatch_dialog(true, 0x189, &mut cpu, &mut bus);
18288 assert!(result.unwrap().is_ok());
18289 assert_eq!(cpu.read_reg(Register::A7), pre_a7 + 2);
18290 assert_eq!(bus.read_word(0x0A60) as i16, 0);
18291 }
18292
18293 #[test]
18294 fn could_alert_missing_resource_sets_reserr_resnotfound() {
18295 let (mut disp, mut cpu, mut bus) = setup();
18297 bus.write_word(0x0A60, 0);
18298 bus.write_word(TEST_SP, 401);
18299
18300 let pre_a7 = cpu.read_reg(Register::A7);
18301 let result = disp.dispatch_dialog(true, 0x189, &mut cpu, &mut bus);
18302 assert!(result.unwrap().is_ok());
18303 assert_eq!(cpu.read_reg(Register::A7), pre_a7 + 2);
18304 assert_eq!(bus.read_word(0x0A60) as i16, 0);
18305 }
18306
18307 #[test]
18308 fn free_alert_present_resource_sets_reserr_noerr() {
18309 let (mut disp, mut cpu, mut bus) = setup();
18312 let alrt = build_alrt_template(200, 0x0000);
18313 disp.install_test_resource(&mut bus, *b"ALRT", 402, &alrt);
18314 bus.write_word(0x0A60, 0x7FFF);
18315 bus.write_word(TEST_SP, 402);
18316
18317 let pre_a7 = cpu.read_reg(Register::A7);
18318 let result = disp.dispatch_dialog(true, 0x18A, &mut cpu, &mut bus);
18319 assert!(result.unwrap().is_ok());
18320 assert_eq!(cpu.read_reg(Register::A7), pre_a7 + 2);
18321 assert_eq!(bus.read_word(0x0A60) as i16, 0);
18322 }
18323
18324 #[test]
18325 fn free_alert_missing_resource_sets_reserr_resnotfound() {
18326 let (mut disp, mut cpu, mut bus) = setup();
18328 bus.write_word(0x0A60, 0);
18329 bus.write_word(TEST_SP, 403);
18330
18331 let pre_a7 = cpu.read_reg(Register::A7);
18332 let result = disp.dispatch_dialog(true, 0x18A, &mut cpu, &mut bus);
18333 assert!(result.unwrap().is_ok());
18334 assert_eq!(cpu.read_reg(Register::A7), pre_a7 + 2);
18335 assert_eq!(bus.read_word(0x0A60) as i16, 0);
18336 }
18337
18338 #[test]
18339 fn could_alert_and_free_alert_leave_loaded_alert_attrs_unchanged() {
18340 let (mut disp, mut cpu, mut bus) = setup();
18341 let alrt = build_alrt_template(200, 0x0000);
18342 let data_ptr = disp.install_test_resource(&mut bus, *b"ALRT", 404, &alrt);
18343 let handle = disp.get_or_create_resource_handle(&mut bus, *b"ALRT", 404, data_ptr);
18344 let attrs_before = disp.resource_attributes_for_handle(handle).unwrap();
18345
18346 cpu.write_reg(Register::A7, TEST_SP);
18347 bus.write_word(TEST_SP, 404);
18348 assert!(disp
18349 .dispatch_dialog(true, 0x189, &mut cpu, &mut bus)
18350 .unwrap()
18351 .is_ok());
18352 assert_eq!(bus.read_word(0x0A60) as i16, 0);
18353 assert_eq!(
18354 disp.resource_attributes_for_handle(handle).unwrap(),
18355 attrs_before
18356 );
18357
18358 cpu.write_reg(Register::A7, TEST_SP);
18359 bus.write_word(TEST_SP, 404);
18360 assert!(disp
18361 .dispatch_dialog(true, 0x18A, &mut cpu, &mut bus)
18362 .unwrap()
18363 .is_ok());
18364 assert_eq!(bus.read_word(0x0A60) as i16, 0);
18365 assert_eq!(
18366 disp.resource_attributes_for_handle(handle).unwrap(),
18367 attrs_before
18368 );
18369 }
18370
18371 #[test]
18372 fn could_alert_free_alert_purgeability() {
18373 let (mut disp, mut cpu, mut bus) = setup();
18376 let alrt_id: i16 = 3200;
18377 let ditl_id: i16 = 3201;
18378 let cntl_id: i16 = 3202;
18379 let icon_id: i16 = 3203;
18380 let pict_id: i16 = 3204;
18381 let alrt = build_alrt_template(ditl_id, 0x0000);
18382 let ditl = build_test_ditl_items(&[
18383 (7, (1, 2, 12, 82), &cntl_id.to_be_bytes()),
18384 (32, (14, 2, 46, 34), &icon_id.to_be_bytes()),
18385 (64, (48, 2, 88, 82), &pict_id.to_be_bytes()),
18386 ]);
18387 disp.install_test_resource(&mut bus, *b"ALRT", alrt_id, &alrt);
18388 disp.install_test_resource(&mut bus, *b"DITL", ditl_id, &ditl);
18389 disp.install_test_resource(&mut bus, *b"CNTL", cntl_id, &[0u8; 32]);
18390 disp.install_test_resource(&mut bus, *b"ICON", icon_id, &[0xAA; 128]);
18391 disp.install_test_resource(&mut bus, *b"PICT", pict_id, &[0x11; 32]);
18392 disp.res_load = false;
18393
18394 bus.write_word(0x0A60, 0x7FFF);
18395 bus.write_word(TEST_SP, alrt_id as u16);
18396 let pre_a7 = cpu.read_reg(Register::A7);
18397 assert!(disp
18398 .dispatch_dialog(true, 0x189, &mut cpu, &mut bus)
18399 .unwrap()
18400 .is_ok());
18401 assert_eq!(cpu.read_reg(Register::A7), pre_a7 + 2);
18402 assert_eq!(bus.read_word(0x0A60) as i16, 0);
18403 for (res_type, res_id) in [
18404 (*b"ALRT", alrt_id),
18405 (*b"DITL", ditl_id),
18406 (*b"CNTL", cntl_id),
18407 (*b"ICON", icon_id),
18408 (*b"PICT", pict_id),
18409 ] {
18410 assert_resource_nonpurgeable(&disp, &bus, res_type, res_id);
18411 }
18412
18413 cpu.write_reg(Register::A7, TEST_SP);
18414 bus.write_word(TEST_SP, alrt_id as u16);
18415 assert!(disp
18416 .dispatch_dialog(true, 0x18A, &mut cpu, &mut bus)
18417 .unwrap()
18418 .is_ok());
18419 assert_eq!(bus.read_word(0x0A60) as i16, 0);
18420 for (res_type, res_id) in [
18421 (*b"ALRT", alrt_id),
18422 (*b"DITL", ditl_id),
18423 (*b"CNTL", cntl_id),
18424 (*b"ICON", icon_id),
18425 (*b"PICT", pict_id),
18426 ] {
18427 assert_resource_purgeable(&disp, res_type, res_id);
18428 }
18429 }
18430
18431 #[test]
18432 fn alert_returns_item_1_when_stage_1_bold_flag_clear() {
18433 let (mut disp, mut cpu, mut bus) = setup();
18436 let alrt = build_alrt_template(200, 0x4444);
18437 disp.install_test_resource(&mut bus, *b"ALRT", 128, &alrt);
18438 bus.write_word(crate::memory::globals::addr::ALERT_STAGE, 0); bus.write_word(TEST_SP + 4, 128);
18440 let result = disp.dispatch_dialog(true, 0x185, &mut cpu, &mut bus);
18441 assert!(result.unwrap().is_ok());
18442 assert_eq!(
18443 bus.read_word(TEST_SP + 6) as i16,
18444 1,
18445 "Alert with stage 1 + bold flag clear must return item 1 (OK)"
18446 );
18447 }
18448
18449 #[test]
18450 fn alert_returns_item_2_when_stage_1_bold_flag_set() {
18451 let (mut disp, mut cpu, mut bus) = setup();
18454 let alrt = build_alrt_template(200, 0xCCCC);
18455 disp.install_test_resource(&mut bus, *b"ALRT", 129, &alrt);
18456 bus.write_word(crate::memory::globals::addr::ALERT_STAGE, 0);
18457 bus.write_word(TEST_SP + 4, 129);
18458 let result = disp.dispatch_dialog(true, 0x185, &mut cpu, &mut bus);
18459 assert!(result.unwrap().is_ok());
18460 assert_eq!(
18461 bus.read_word(TEST_SP + 6) as i16,
18462 2,
18463 "Alert with stage 1 + bold flag set must return item 2"
18464 );
18465 }
18466
18467 #[test]
18468 fn alert_steps_through_stages_and_caps_at_stage_4() {
18469 let (mut disp, mut cpu, mut bus) = setup();
18473 let alrt = build_alrt_template(200, 0xC4C4);
18474 disp.install_test_resource(&mut bus, *b"ALRT", 130, &alrt);
18475 bus.write_word(crate::memory::globals::addr::ALERT_STAGE, 0);
18476 bus.write_word(TEST_SP + 4, 130);
18477
18478 let expected = [1i16, 2, 1, 2, 2, 2];
18479 for (i, want) in expected.iter().enumerate() {
18480 cpu.write_reg(Register::A7, TEST_SP);
18481 let result = disp.dispatch_dialog(true, 0x185, &mut cpu, &mut bus);
18482 assert!(result.unwrap().is_ok());
18483 assert_eq!(
18484 bus.read_word(TEST_SP + 6) as i16,
18485 *want,
18486 "call #{} (stage {}): expected item {}",
18487 i + 1,
18488 (i + 1).min(4),
18489 want
18490 );
18491 }
18492 assert_eq!(
18494 bus.read_word(crate::memory::globals::addr::ALERT_STAGE),
18495 3,
18496 "AlertStage must cap at 3 (stages are 1..4 → word 0..3)"
18497 );
18498 }
18499
18500 #[test]
18501 fn alert_increments_alert_stage_word_after_dispatch() {
18502 let (mut disp, mut cpu, mut bus) = setup();
18505 let alrt = build_alrt_template(200, 0x4444);
18506 disp.install_test_resource(&mut bus, *b"ALRT", 131, &alrt);
18507 bus.write_word(crate::memory::globals::addr::ALERT_STAGE, 0);
18508 bus.write_word(TEST_SP + 4, 131);
18509 let _ = disp.dispatch_dialog(true, 0x185, &mut cpu, &mut bus);
18510 assert_eq!(
18511 bus.read_word(crate::memory::globals::addr::ALERT_STAGE),
18512 1,
18513 "Alert must increment AlertStage 0 → 1 after dispatch"
18514 );
18515 }
18516
18517 #[test]
18518 fn alert_does_not_increment_alert_stage_when_alrt_missing() {
18519 let (mut disp, mut cpu, mut bus) = setup();
18524 bus.write_word(crate::memory::globals::addr::ALERT_STAGE, 2);
18525 bus.write_word(TEST_SP + 4, 999); let result = disp.dispatch_dialog(true, 0x185, &mut cpu, &mut bus);
18527 assert!(result.unwrap().is_ok());
18528 assert_eq!(bus.read_word(TEST_SP + 6) as i16, -1);
18529 assert_eq!(
18530 bus.read_word(crate::memory::globals::addr::ALERT_STAGE),
18531 2,
18532 "missing-ALRT must NOT increment AlertStage"
18533 );
18534 }
18535
18536 #[test]
18537 fn alert_returns_minus_one_when_stage_box_drawn_clear_but_updates_stage() {
18538 let (mut disp, mut cpu, mut bus) = setup();
18542 let alrt = build_alrt_template(200, 0x0000);
18543 disp.install_test_resource(&mut bus, *b"ALRT", 133, &alrt);
18544 bus.write_word(crate::memory::globals::addr::ALERT_STAGE, 0);
18545 bus.write_word(crate::memory::globals::addr::ANUMBER, 0xCAFE);
18546 bus.write_word(TEST_SP + 4, 133);
18547
18548 let result = disp.dispatch_dialog(true, 0x185, &mut cpu, &mut bus);
18549 assert!(result.unwrap().is_ok());
18550 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 6);
18551 assert_eq!(
18552 bus.read_word(TEST_SP + 6) as i16,
18553 -1,
18554 "boxDrwn clear must suppress the alert box and return -1"
18555 );
18556 assert_eq!(
18557 bus.read_word(crate::memory::globals::addr::ALERT_STAGE),
18558 1,
18559 "suppressed present ALRT must still advance ACount"
18560 );
18561 assert_eq!(
18562 bus.read_word(crate::memory::globals::addr::ANUMBER) as i16,
18563 133,
18564 "suppressed present ALRT must still record ANumber"
18565 );
18566 }
18567
18568 #[test]
18569 fn alert_writes_anumber_with_alert_id_after_successful_dispatch() {
18570 let (mut disp, mut cpu, mut bus) = setup();
18573 let alrt = build_alrt_template(200, 0x4444);
18574 disp.install_test_resource(&mut bus, *b"ALRT", 250, &alrt);
18575 bus.write_word(crate::memory::globals::addr::ANUMBER, 0xCAFE);
18576 bus.write_word(crate::memory::globals::addr::ALERT_STAGE, 0);
18577 bus.write_word(TEST_SP + 4, 250);
18578 let _ = disp.dispatch_dialog(true, 0x185, &mut cpu, &mut bus);
18579 assert_eq!(
18580 bus.read_word(crate::memory::globals::addr::ANUMBER) as i16,
18581 250,
18582 "Alert must write the alertID to ANumber per IM:I I-423"
18583 );
18584 }
18585
18586 #[test]
18587 fn alert_does_not_overwrite_anumber_when_alrt_missing() {
18588 let (mut disp, mut cpu, mut bus) = setup();
18592 bus.write_word(crate::memory::globals::addr::ANUMBER, 0xC0DE);
18593 bus.write_word(TEST_SP + 4, 999); let _ = disp.dispatch_dialog(true, 0x185, &mut cpu, &mut bus);
18595 assert_eq!(
18596 bus.read_word(crate::memory::globals::addr::ANUMBER),
18597 0xC0DE,
18598 "missing-ALRT must NOT overwrite ANumber"
18599 );
18600 }
18601
18602 #[test]
18603 fn alert_family_share_dispatch_path_so_stop_note_caution_match_alert() {
18604 let alrt = build_alrt_template(200, 0x4444);
18610 let trap_words = [
18611 (0x185, "Alert"),
18612 (0x186, "StopAlert"),
18613 (0x187, "NoteAlert"),
18614 (0x188, "CautionAlert"),
18615 ];
18616 for (trap, name) in trap_words.iter() {
18617 let (mut disp, mut cpu, mut bus) = setup();
18618 disp.install_test_resource(&mut bus, *b"ALRT", 132, &alrt);
18619 bus.write_word(crate::memory::globals::addr::ALERT_STAGE, 0);
18620 bus.write_word(TEST_SP + 4, 132);
18621 let result = disp.dispatch_dialog(true, *trap, &mut cpu, &mut bus);
18622 assert!(result.unwrap().is_ok());
18623 assert_eq!(
18624 bus.read_word(TEST_SP + 6) as i16,
18625 1,
18626 "{name} must return item 1 (bold flag clear, stage 1) — \
18627 same as Alert"
18628 );
18629 }
18630 }
18631
18632 #[test]
18633 fn alert_pops_eight_bytes_per_pascal_signature() {
18634 let (mut disp, mut cpu, mut bus) = setup();
18640 bus.write_word(TEST_SP + 4, 999); let pre_a7 = cpu.read_reg(Register::A7);
18642 let _ = disp.dispatch_dialog(true, 0x185, &mut cpu, &mut bus);
18643 let post_a7 = cpu.read_reg(Register::A7);
18644 assert_eq!(
18645 post_a7,
18646 pre_a7 + 6,
18647 "Alert must advance A7 by 6 bytes (filterProc + alertID + return)"
18648 );
18649 }
18650
18651 #[test]
18654 fn init_dialogs_stores_resume_proc_at_lowmem_a8c() {
18655 let (mut disp, mut cpu, mut bus) = setup();
18659 bus.write_long(crate::memory::globals::addr::RESUME_PROC, 0xCAFEBABE);
18660 bus.write_long(TEST_SP, 0x00123456); let _ = disp.dispatch_dialog(true, 0x17B, &mut cpu, &mut bus);
18662 assert_eq!(
18663 bus.read_long(crate::memory::globals::addr::RESUME_PROC),
18664 0x00123456,
18665 "InitDialogs must store resumeProc at $0A8C"
18666 );
18667 }
18668
18669 #[test]
18670 fn init_dialogs_accepts_nil_resume_proc() {
18671 let (mut disp, mut cpu, mut bus) = setup();
18676 bus.write_long(crate::memory::globals::addr::RESUME_PROC, 0xDEADBEEF);
18677 bus.write_long(TEST_SP, 0); let _ = disp.dispatch_dialog(true, 0x17B, &mut cpu, &mut bus);
18679 assert_eq!(
18680 bus.read_long(crate::memory::globals::addr::RESUME_PROC),
18681 0,
18682 "InitDialogs must store NIL resumeProc as 0 at $0A8C"
18683 );
18684 }
18685
18686 #[test]
18687 fn init_dialogs_zeros_dabeeper_at_lowmem_a9c() {
18688 let (mut disp, mut cpu, mut bus) = setup();
18695 bus.write_long(crate::memory::globals::addr::DA_BEEPER, 0xDEADBEEF);
18696 bus.write_long(TEST_SP, 0);
18697 let _ = disp.dispatch_dialog(true, 0x17B, &mut cpu, &mut bus);
18698 assert_eq!(
18699 bus.read_long(crate::memory::globals::addr::DA_BEEPER),
18700 0,
18701 "InitDialogs must store NIL (silent default) at DABeeper $0A9C"
18702 );
18703 }
18704
18705 #[test]
18706 fn init_dialogs_zeros_alert_stage_at_lowmem_a9a() {
18707 let (mut disp, mut cpu, mut bus) = setup();
18713 bus.write_word(crate::memory::globals::addr::ALERT_STAGE, 3);
18714 bus.write_long(TEST_SP, 0);
18715 let _ = disp.dispatch_dialog(true, 0x17B, &mut cpu, &mut bus);
18716 assert_eq!(
18717 bus.read_word(crate::memory::globals::addr::ALERT_STAGE),
18718 0,
18719 "InitDialogs must zero AlertStage at $0A9A so the next Alert starts at stage 1"
18720 );
18721 }
18722
18723 #[test]
18724 fn init_dialogs_zeros_dastrings_array_at_lowmem_aa0() {
18725 let (mut disp, mut cpu, mut bus) = setup();
18729 for i in 0..4u32 {
18730 bus.write_long(
18731 crate::memory::globals::addr::DA_STRINGS + i * 4,
18732 0xDEAD0000 | i,
18733 );
18734 }
18735 bus.write_long(TEST_SP, 0);
18736 let _ = disp.dispatch_dialog(true, 0x17B, &mut cpu, &mut bus);
18737 for i in 0..4u32 {
18738 assert_eq!(
18739 bus.read_long(crate::memory::globals::addr::DA_STRINGS + i * 4),
18740 0,
18741 "InitDialogs must zero DAStrings[{}] at $0AA0+{}",
18742 i,
18743 i * 4
18744 );
18745 }
18746 }
18747
18748 #[test]
18749 fn init_dialogs_pops_four_bytes_resume_proc_param() {
18750 let (mut disp, mut cpu, mut bus) = setup();
18752 bus.write_long(TEST_SP, 0);
18753 let pre_a7 = cpu.read_reg(Register::A7);
18754 let _ = disp.dispatch_dialog(true, 0x17B, &mut cpu, &mut bus);
18755 assert_eq!(
18756 cpu.read_reg(Register::A7),
18757 pre_a7 + 4,
18758 "InitDialogs must pop 4 bytes (resumeProc ProcPtr)"
18759 );
18760 }
18761
18762 #[test]
18765 fn error_sound_stores_sound_proc_pointer_in_dabeeper() {
18766 let (mut disp, mut cpu, mut bus) = setup();
18770 let sound_proc = 0x0012_3456u32;
18771 bus.write_long(crate::memory::globals::addr::DA_BEEPER, 0);
18772 bus.write_long(TEST_SP, sound_proc);
18773
18774 let pre_a7 = cpu.read_reg(Register::A7);
18775 let result = disp.dispatch_dialog(true, 0x18C, &mut cpu, &mut bus);
18776 assert!(result.unwrap().is_ok());
18777 assert_eq!(
18778 bus.read_long(crate::memory::globals::addr::DA_BEEPER),
18779 sound_proc
18780 );
18781 assert_eq!(cpu.read_reg(Register::A7), pre_a7 + 4);
18782 }
18783
18784 #[test]
18785 fn error_sound_nil_clears_dabeeper_to_disable_alert_sound() {
18786 let (mut disp, mut cpu, mut bus) = setup();
18789 bus.write_long(crate::memory::globals::addr::DA_BEEPER, 0xDEADBEEF);
18790 bus.write_long(TEST_SP, 0);
18791
18792 let result = disp.dispatch_dialog(true, 0x18C, &mut cpu, &mut bus);
18793 assert!(result.unwrap().is_ok());
18794 assert_eq!(bus.read_long(crate::memory::globals::addr::DA_BEEPER), 0);
18795 }
18796
18797 #[test]
18798 fn error_sound_overrides_init_dialogs_default_sound_proc() {
18799 let (mut disp, mut cpu, mut bus) = setup();
18802 let sound_proc = 0x0012_3456u32;
18803
18804 bus.write_long(crate::memory::globals::addr::DA_BEEPER, 0xDEADBEEF);
18805 bus.write_long(TEST_SP, 0);
18806 let result = disp.dispatch_dialog(true, 0x17B, &mut cpu, &mut bus);
18807 assert!(result.unwrap().is_ok());
18808 assert_eq!(bus.read_long(crate::memory::globals::addr::DA_BEEPER), 0);
18809
18810 cpu.write_reg(Register::A7, TEST_SP);
18811 bus.write_long(TEST_SP, sound_proc);
18812 let result = disp.dispatch_dialog(true, 0x18C, &mut cpu, &mut bus);
18813 assert!(result.unwrap().is_ok());
18814 assert_eq!(
18815 bus.read_long(crate::memory::globals::addr::DA_BEEPER),
18816 sound_proc
18817 );
18818
18819 cpu.write_reg(Register::A7, TEST_SP);
18820 bus.write_long(TEST_SP, 0);
18821 let result = disp.dispatch_dialog(true, 0x18C, &mut cpu, &mut bus);
18822 assert!(result.unwrap().is_ok());
18823 assert_eq!(bus.read_long(crate::memory::globals::addr::DA_BEEPER), 0);
18824 }
18825
18826 #[test]
18829 fn is_dialog_event_returns_false() {
18830 let (mut disp, mut cpu, mut bus) = setup();
18831 bus.write_long(TEST_SP, 0x300000);
18833
18834 let result = disp.dispatch_dialog(true, 0x17F, &mut cpu, &mut bus);
18835 assert!(result.unwrap().is_ok());
18836 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 4);
18837 assert_eq!(bus.read_word(TEST_SP + 4), 0);
18838 }
18839
18840 #[test]
18841 fn is_dialog_event_true_for_mouse_down_in_front_dialog() {
18842 let (mut disp, mut cpu, mut bus) = setup();
18845 let dialog_ptr = bus.alloc(170);
18846 let event_ptr = 0x300000u32;
18847
18848 disp.front_window = dialog_ptr;
18849 disp.enable_input_trace_capture();
18850 bus.write_word(dialog_ptr + 8, (-100i16) as u16);
18851 bus.write_word(dialog_ptr + 10, (-200i16) as u16);
18852 bus.write_word(dialog_ptr + 16, 0);
18853 bus.write_word(dialog_ptr + 18, 0);
18854 bus.write_word(dialog_ptr + 20, 80);
18855 bus.write_word(dialog_ptr + 22, 120);
18856 disp.dialog_items.insert(dialog_ptr, Vec::new());
18857
18858 bus.write_word(event_ptr, 1);
18859 bus.write_long(event_ptr + 2, 0);
18860 bus.write_long(event_ptr + 6, 0);
18861 bus.write_word(event_ptr + 10, 120);
18862 bus.write_word(event_ptr + 12, 240);
18863 bus.write_word(event_ptr + 14, 0x0080);
18864 bus.write_long(TEST_SP, event_ptr);
18865
18866 let result = disp.dispatch_dialog(true, 0x17F, &mut cpu, &mut bus);
18867 assert!(result.unwrap().is_ok());
18868 assert_eq!(bus.read_word(TEST_SP + 4), 0xFFFF);
18869 let trace = disp.input_trace_text();
18870 assert!(trace.contains("A97F action=guest_event:mouseDown"));
18871 assert!(trace.contains("tracking=menu:idle dialog:idle control:idle"));
18872 assert!(trace.contains("result=true outcome=is_dialog_event"));
18873 }
18874
18875 #[test]
18876 fn is_dialog_event_false_for_mouse_down_outside_front_dialog() {
18877 let (mut disp, mut cpu, mut bus) = setup();
18880 let dialog_ptr = bus.alloc(170);
18881 let event_ptr = 0x300000u32;
18882
18883 disp.front_window = dialog_ptr;
18884 disp.enable_input_trace_capture();
18885 bus.write_word(dialog_ptr + 8, (-100i16) as u16);
18886 bus.write_word(dialog_ptr + 10, (-200i16) as u16);
18887 bus.write_word(dialog_ptr + 16, 0);
18888 bus.write_word(dialog_ptr + 18, 0);
18889 bus.write_word(dialog_ptr + 20, 80);
18890 bus.write_word(dialog_ptr + 22, 120);
18891 disp.dialog_items.insert(dialog_ptr, Vec::new());
18892
18893 bus.write_word(event_ptr, 1); bus.write_long(event_ptr + 2, 0);
18895 bus.write_long(event_ptr + 6, 0);
18896 bus.write_word(event_ptr + 10, 40); bus.write_word(event_ptr + 12, 40);
18898 bus.write_word(event_ptr + 14, 0x0080);
18899 bus.write_long(TEST_SP, event_ptr);
18900
18901 let result = disp.dispatch_dialog(true, 0x17F, &mut cpu, &mut bus);
18902 assert!(result.unwrap().is_ok());
18903 assert_eq!(bus.read_word(TEST_SP + 4), 0);
18904 }
18905
18906 #[test]
18907 fn is_dialog_event_true_for_update_event_targeting_dialog_window() {
18908 let (mut disp, mut cpu, mut bus) = setup();
18911 let dialog_ptr = bus.alloc(170);
18912 let event_ptr = 0x300000u32;
18913
18914 disp.dialog_items.insert(dialog_ptr, Vec::new());
18915 bus.write_word(event_ptr, 6); bus.write_long(event_ptr + 2, dialog_ptr);
18917 bus.write_long(TEST_SP, event_ptr);
18918
18919 let result = disp.dispatch_dialog(true, 0x17F, &mut cpu, &mut bus);
18920 assert!(result.unwrap().is_ok());
18921 assert_eq!(bus.read_word(TEST_SP + 4), 0xFFFF);
18922 }
18923
18924 #[test]
18925 fn is_dialog_event_true_for_activate_event_targeting_dialog_window() {
18926 let (mut disp, mut cpu, mut bus) = setup();
18929 let dialog_ptr = bus.alloc(170);
18930 let event_ptr = 0x300000u32;
18931
18932 disp.dialog_items.insert(dialog_ptr, Vec::new());
18933 bus.write_word(event_ptr, 8); bus.write_long(event_ptr + 2, dialog_ptr);
18935 bus.write_long(TEST_SP, event_ptr);
18936
18937 let result = disp.dispatch_dialog(true, 0x17F, &mut cpu, &mut bus);
18938 assert!(result.unwrap().is_ok());
18939 assert_eq!(bus.read_word(TEST_SP + 4), 0xFFFF);
18940 }
18941
18942 #[test]
18945 fn dialog_select_returns_false() {
18946 let (mut disp, mut cpu, mut bus) = setup();
18947
18948 let result = disp.dispatch_dialog(true, 0x180, &mut cpu, &mut bus);
18949 assert!(result.unwrap().is_ok());
18950 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 12);
18951 assert_eq!(bus.read_word(TEST_SP + 12), 0);
18952 }
18953
18954 #[test]
18955 fn dialog_select_returns_hit_for_enabled_user_item() {
18956 let (mut disp, mut cpu, mut bus) = setup();
18957 let dialog_ptr = bus.alloc(170);
18958 let event_ptr = 0x300000u32;
18959 let dialog_out_ptr = 0x300100u32;
18960 let item_hit_ptr = 0x300104u32;
18961
18962 disp.front_window = dialog_ptr;
18963 disp.enable_input_trace_capture();
18964 bus.write_word(dialog_ptr + 8, (-100i16) as u16);
18965 bus.write_word(dialog_ptr + 10, (-200i16) as u16);
18966 bus.write_word(dialog_ptr + 16, 0);
18967 bus.write_word(dialog_ptr + 18, 0);
18968 bus.write_word(dialog_ptr + 20, 100);
18969 bus.write_word(dialog_ptr + 22, 160);
18970 disp.dialog_items.insert(
18971 dialog_ptr,
18972 vec![DialogItem {
18973 item_type: 0,
18974 rect: (20, 30, 60, 110),
18975 text: String::new(),
18976 resource_id: 0,
18977 proc_ptr: 0,
18978 sel_start: 0,
18979 sel_end: 0,
18980 }],
18981 );
18982
18983 bus.write_word(event_ptr, 1);
18984 bus.write_long(event_ptr + 2, 0);
18985 bus.write_long(event_ptr + 6, 0);
18986 bus.write_word(event_ptr + 10, 130);
18987 bus.write_word(event_ptr + 12, 240);
18988 bus.write_word(event_ptr + 14, 0x0080);
18989
18990 bus.write_long(TEST_SP, item_hit_ptr);
18991 bus.write_long(TEST_SP + 4, dialog_out_ptr);
18992 bus.write_long(TEST_SP + 8, event_ptr);
18993
18994 let result = disp.dispatch_dialog(true, 0x180, &mut cpu, &mut bus);
18995 assert!(result.unwrap().is_ok());
18996 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 12);
18997 assert_eq!(bus.read_word(TEST_SP + 12), 0xFFFF);
18998 assert_eq!(bus.read_long(dialog_out_ptr), dialog_ptr);
18999 assert_eq!(bus.read_word(item_hit_ptr), 1);
19000 let trace = disp.input_trace_text();
19001 assert!(trace.contains("A980 action=guest_event:mouseDown"));
19002 assert!(trace.contains("item_hit=1 item_type=$00 disabled=false outcome=enabled_item"));
19003 }
19004
19005 fn dialog_select_enabled_user_item_hit_for_theme(theme_id: UiThemeId) -> (u16, u32, u16, u32) {
19006 let (mut disp, mut cpu, mut bus) = setup();
19007 disp.set_ui_theme_id(theme_id);
19008 let dialog_ptr = bus.alloc(170);
19009 let event_ptr = 0x300000u32;
19010 let dialog_out_ptr = 0x300100u32;
19011 let item_hit_ptr = 0x300104u32;
19012
19013 disp.front_window = dialog_ptr;
19014 bus.write_word(dialog_ptr + 8, (-100i16) as u16);
19015 bus.write_word(dialog_ptr + 10, (-200i16) as u16);
19016 bus.write_word(dialog_ptr + 16, 0);
19017 bus.write_word(dialog_ptr + 18, 0);
19018 bus.write_word(dialog_ptr + 20, 100);
19019 bus.write_word(dialog_ptr + 22, 160);
19020 disp.dialog_items.insert(
19021 dialog_ptr,
19022 vec![DialogItem {
19023 item_type: 0,
19024 rect: (20, 30, 60, 110),
19025 text: String::new(),
19026 resource_id: 0,
19027 proc_ptr: 0,
19028 sel_start: 0,
19029 sel_end: 0,
19030 }],
19031 );
19032
19033 bus.write_word(event_ptr, 1);
19034 bus.write_long(event_ptr + 2, 0);
19035 bus.write_long(event_ptr + 6, 0);
19036 bus.write_word(event_ptr + 10, 130);
19037 bus.write_word(event_ptr + 12, 240);
19038 bus.write_word(event_ptr + 14, 0x0080);
19039 bus.write_long(TEST_SP, item_hit_ptr);
19040 bus.write_long(TEST_SP + 4, dialog_out_ptr);
19041 bus.write_long(TEST_SP + 8, event_ptr);
19042
19043 disp.dispatch_dialog(true, 0x180, &mut cpu, &mut bus)
19044 .unwrap()
19045 .unwrap();
19046
19047 (
19048 bus.read_word(TEST_SP + 12),
19049 bus.read_long(dialog_out_ptr),
19050 bus.read_word(item_hit_ptr),
19051 cpu.read_reg(Register::A7),
19052 )
19053 }
19054
19055 fn dialog_select_enabled_checkbox_hit_for_theme(
19056 theme_id: UiThemeId,
19057 ) -> (u16, u32, u16, u32, u16, u16) {
19058 let (mut disp, mut cpu, mut bus) = setup();
19059 disp.set_ui_theme_id(theme_id);
19060 let dialog_ptr = bus.alloc(170);
19061 let event_ptr = 0x300000u32;
19062 let dialog_out_ptr = 0x300100u32;
19063 let item_hit_ptr = 0x300104u32;
19064 let box_ptr = 0x300108u32;
19065 let item_ptr = 0x300110u32;
19066 let type_ptr = 0x300114u32;
19067
19068 disp.front_window = dialog_ptr;
19069 bus.write_word(dialog_ptr + 8, 0);
19070 bus.write_word(dialog_ptr + 10, 0);
19071 bus.write_word(dialog_ptr + 16, 0);
19072 bus.write_word(dialog_ptr + 18, 0);
19073 bus.write_word(dialog_ptr + 20, 100);
19074 bus.write_word(dialog_ptr + 22, 160);
19075 disp.dialog_items.insert(
19076 dialog_ptr,
19077 vec![DialogItem {
19078 item_type: 5,
19079 rect: (20, 30, 40, 130),
19080 text: "Enabled".to_string(),
19081 resource_id: 0,
19082 proc_ptr: 0,
19083 sel_start: 0,
19084 sel_end: 0,
19085 }],
19086 );
19087 disp.dialog_control_values.insert((dialog_ptr, 1), 1);
19088
19089 bus.write_long(TEST_SP, box_ptr);
19090 bus.write_long(TEST_SP + 4, item_ptr);
19091 bus.write_long(TEST_SP + 8, type_ptr);
19092 bus.write_word(TEST_SP + 12, 1);
19093 bus.write_long(TEST_SP + 14, dialog_ptr);
19094 disp.dispatch_dialog(true, 0x18D, &mut cpu, &mut bus)
19095 .unwrap()
19096 .unwrap();
19097 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 18);
19098 let ctrl_handle = bus.read_long(item_ptr);
19099 let ctrl_ptr = bus.read_long(ctrl_handle);
19100 assert_ne!(ctrl_ptr, 0);
19101 assert_eq!(bus.read_word(type_ptr), 5);
19102 assert_eq!(bus.read_word(ctrl_ptr + 18), 1);
19103
19104 cpu.write_reg(Register::A7, TEST_SP);
19105 bus.write_word(event_ptr, 1);
19106 bus.write_long(event_ptr + 2, 0);
19107 bus.write_long(event_ptr + 6, 0);
19108 bus.write_word(event_ptr + 10, 30);
19109 bus.write_word(event_ptr + 12, 40);
19110 bus.write_word(event_ptr + 14, 0x0080);
19111 bus.write_long(TEST_SP, item_hit_ptr);
19112 bus.write_long(TEST_SP + 4, dialog_out_ptr);
19113 bus.write_long(TEST_SP + 8, event_ptr);
19114
19115 disp.dispatch_dialog(true, 0x180, &mut cpu, &mut bus)
19116 .unwrap()
19117 .unwrap();
19118
19119 (
19120 bus.read_word(TEST_SP + 12),
19121 bus.read_long(dialog_out_ptr),
19122 bus.read_word(item_hit_ptr),
19123 cpu.read_reg(Register::A7),
19124 bus.read_word(ctrl_ptr + 18),
19125 disp.dialog_control_values
19126 .get(&(dialog_ptr, 1))
19127 .copied()
19128 .unwrap_or(-1) as u16,
19129 )
19130 }
19131
19132 fn findditem_results_for_theme(theme_id: UiThemeId) -> Vec<(i16, u32)> {
19133 let (mut disp, mut cpu, mut bus) = setup();
19134 disp.set_ui_theme_id(theme_id);
19135 let dialog_ptr = 0x220400u32;
19136 disp.dialog_items.insert(
19137 dialog_ptr,
19138 vec![
19139 DialogItem {
19140 item_type: 0x80 | 4,
19141 rect: (10, 20, 40, 60),
19142 text: "DisabledFirst".into(),
19143 resource_id: 0,
19144 proc_ptr: 0,
19145 sel_start: 0,
19146 sel_end: 0,
19147 },
19148 DialogItem {
19149 item_type: 4,
19150 rect: (20, 30, 55, 80),
19151 text: "EnabledSecond".into(),
19152 resource_id: 0,
19153 proc_ptr: 0,
19154 sel_start: 0,
19155 sel_end: 0,
19156 },
19157 DialogItem {
19158 item_type: 16,
19159 rect: (60, 16384 + 20, 80, 16384 + 100),
19160 text: "HiddenEdit".into(),
19161 resource_id: 0,
19162 proc_ptr: 0,
19163 sel_start: 0,
19164 sel_end: 0,
19165 },
19166 ],
19167 );
19168
19169 let mut out = Vec::new();
19170 for (pt_v, pt_h) in [(25i16, 35i16), (45, 70), (65, 40), (90, 110)] {
19171 cpu.write_reg(Register::A7, TEST_SP);
19172 bus.write_word(TEST_SP, pt_v as u16);
19173 bus.write_word(TEST_SP + 2, pt_h as u16);
19174 bus.write_long(TEST_SP + 4, dialog_ptr);
19175 bus.write_word(TEST_SP + 8, 0xBEEF);
19176 disp.dispatch_dialog(true, 0x184, &mut cpu, &mut bus)
19177 .unwrap()
19178 .unwrap();
19179 out.push((
19180 bus.read_word(TEST_SP + 8) as i16,
19181 cpu.read_reg(Register::A7),
19182 ));
19183 }
19184 out
19185 }
19186
19187 #[derive(Debug, PartialEq, Eq)]
19188 struct DItemGetSnapshot {
19189 item_type: u16,
19190 item: u32,
19191 rect: (u16, u16, u16, u16),
19192 stack_after: u32,
19193 }
19194
19195 #[derive(Debug, PartialEq, Eq)]
19196 struct DItemUserItemRecordSnapshot {
19197 initial_get: DItemGetSnapshot,
19198 set_stack_after: u32,
19199 stored_type: u8,
19200 stored_proc_ptr: u32,
19201 stored_rect: (i16, i16, i16, i16),
19202 post_set_get: DItemGetSnapshot,
19203 }
19204
19205 #[derive(Debug, PartialEq, Eq)]
19206 struct DItemTextControlRecordSnapshot {
19207 text_initial_get: DItemGetSnapshot,
19208 text_set_stack_after: u32,
19209 text_old_handle_mapped_after: bool,
19210 text_new_handle_map_value_after: Option<(u32, usize)>,
19211 text_ditl_handle_after: u32,
19212 text_ditl_rect_after: (u16, u16, u16, u16),
19213 text_ditl_type_after: u8,
19214 text_stored_type_after: u8,
19215 text_stored_rect_after: (i16, i16, i16, i16),
19216 text_cached_text_after: String,
19217 text_handle_bytes_after: Vec<u8>,
19218 text_post_set_get: DItemGetSnapshot,
19219 control_initial_get: DItemGetSnapshot,
19220 control_set_stack_after: u32,
19221 control_old_handle_mapped_after: bool,
19222 control_new_handle_map_value_after: Option<(u32, i16)>,
19223 control_ditl_handle_after: u32,
19224 control_ditl_rect_after: (u16, u16, u16, u16),
19225 control_ditl_type_after: u8,
19226 control_stored_type_after: u8,
19227 control_stored_rect_after: (i16, i16, i16, i16),
19228 control_record_rect_after: (u16, u16, u16, u16),
19229 control_value_after: Option<i16>,
19230 control_post_set_get: DItemGetSnapshot,
19231 }
19232
19233 #[derive(Debug, PartialEq, Eq)]
19234 struct DItemVisibilitySnapshot {
19235 initial_get: DItemGetSnapshot,
19236 hide_stack_after: u32,
19237 hidden_item_rect: (i16, i16, i16, i16),
19238 hidden_saved_rect: Option<(i16, i16, i16, i16)>,
19239 hidden_control_rect: (u16, u16, u16, u16),
19240 hidden_get: DItemGetSnapshot,
19241 hidden_find: (i16, u32),
19242 show_stack_after: u32,
19243 restored_item_rect: (i16, i16, i16, i16),
19244 restored_saved_rect: Option<(i16, i16, i16, i16)>,
19245 restored_control_rect: (u16, u16, u16, u16),
19246 restored_get: DItemGetSnapshot,
19247 restored_find: (i16, u32),
19248 }
19249
19250 #[derive(Debug, PartialEq, Eq)]
19251 struct ParamTextSnapshot {
19252 initial_slots: [Vec<u8>; 4],
19253 initial_da_handles: [u32; 4],
19254 initial_da_strings: [Vec<u8>; 4],
19255 initial_stack_after: u32,
19256 initial_substitution: String,
19257 nil_slots: [Vec<u8>; 4],
19258 nil_da_handles: [u32; 4],
19259 nil_da_strings: [Vec<u8>; 4],
19260 nil_stack_after: u32,
19261 nil_substitution: String,
19262 }
19263
19264 #[derive(Debug, PartialEq, Eq)]
19265 struct DialogItemTextSnapshot {
19266 initial_get_text: Vec<u8>,
19267 initial_get_stack_after: u32,
19268 set_stack_after: u32,
19269 handle_size_after_set: Option<u32>,
19270 handle_bytes_after_set: Vec<u8>,
19271 cached_text_after_set: String,
19272 post_set_get_text: Vec<u8>,
19273 post_set_get_stack_after: u32,
19274 }
19275
19276 #[derive(Debug, PartialEq, Eq)]
19277 struct DialogItemTextSelectionSnapshot {
19278 whole_selection: (i16, i16),
19279 whole_edit_field: u16,
19280 whole_te_selection: (u16, u16),
19281 whole_stack_after: u32,
19282 normalized_selection: (i16, i16),
19283 normalized_edit_field: u16,
19284 normalized_te_selection: (u16, u16),
19285 normalized_stack_after: u32,
19286 non_edit_selection: (i16, i16),
19287 non_edit_edit_field: u16,
19288 non_edit_te_selection: (u16, u16),
19289 non_edit_stack_after: u32,
19290 }
19291
19292 #[derive(Debug, PartialEq, Eq)]
19293 struct IsDialogEventSnapshot {
19294 null_event: (u16, u32),
19295 key_down: (u16, u32),
19296 mouse_inside: (u16, u32),
19297 mouse_outside: (u16, u32),
19298 update_target: (u16, u32),
19299 activate_target: (u16, u32),
19300 }
19301
19302 #[derive(Debug, PartialEq, Eq)]
19303 struct DialogSelectWindowEventSnapshot {
19304 dialog_ptr: u32,
19305 update_result: u16,
19306 update_dialog_out: u32,
19307 update_item_hit: u16,
19308 update_stack_after: u32,
19309 update_deferred_after: bool,
19310 update_region_after: Option<(i16, i16, i16, i16)>,
19311 update_vis_region_after: Option<(i16, i16, i16, i16)>,
19312 update_the_port_after: u32,
19313 update_current_port_after: u32,
19314 update_saved_vis_after: bool,
19315 update_queued_draw_procs: Vec<(u32, u32, i16)>,
19316 update_item_count_after: usize,
19317 activate_result: u16,
19318 activate_dialog_out: u32,
19319 activate_item_hit: u16,
19320 activate_stack_after: u32,
19321 }
19322
19323 #[derive(Debug, PartialEq, Eq)]
19324 struct DialogSelectEditTextMouseSnapshot {
19325 mouse_result: u16,
19326 mouse_dialog_out: u32,
19327 mouse_item_hit: u16,
19328 mouse_stack_after: u32,
19329 mouse_edit_field: u16,
19330 mouse_item_selection: (i16, i16),
19331 mouse_te_text: Vec<u8>,
19332 mouse_te_length: u16,
19333 mouse_te_selection: (u16, u16),
19334 null_result: u16,
19335 null_dialog_out: u32,
19336 null_item_hit: u16,
19337 null_stack_after: u32,
19338 null_edit_field: u16,
19339 null_te_text: Vec<u8>,
19340 null_te_length: u16,
19341 null_te_selection: (u16, u16),
19342 }
19343
19344 #[derive(Debug, PartialEq, Eq)]
19345 struct ModalDialogEditTextMouseSnapshot {
19346 item_hit: u16,
19347 stack_after: u32,
19348 tracking_finished: bool,
19349 retained_visible_snapshot: bool,
19350 saved_background_retained: bool,
19351 queued_mouse_up_consumed: bool,
19352 edit_field: u16,
19353 item_selection: (i16, i16),
19354 te_text: Vec<u8>,
19355 te_length: u16,
19356 te_selection: (u16, u16),
19357 handle_bytes: Vec<u8>,
19358 }
19359
19360 #[derive(Debug, PartialEq, Eq)]
19361 struct ModalDialogKeyboardButtonSnapshot {
19362 return_key: ModalDialogKeyboardButtonCase,
19363 enter_key: ModalDialogKeyboardButtonCase,
19364 escape_key: ModalDialogKeyboardButtonCase,
19365 command_period: ModalDialogKeyboardButtonCase,
19366 }
19367
19368 #[derive(Debug, PartialEq, Eq)]
19369 struct ModalDialogKeyboardButtonCase {
19370 flash_item_after_key: i16,
19371 stack_after_key: u32,
19372 item_hit_after_key: u16,
19373 item_hit_after_flash: u16,
19374 stack_after_flash: u32,
19375 tracking_finished: bool,
19376 }
19377
19378 #[derive(Debug, PartialEq, Eq)]
19379 struct ModalDialogUpdateEventSnapshot {
19380 dialog_ptr: u32,
19381 item_hit_after: u16,
19382 stack_after: u32,
19383 tracking_finished: bool,
19384 rendered_pixels_final: bool,
19385 retained_pixel_after: u8,
19386 retained_snapshot_pixel_after: u8,
19387 update_region_after: Option<(i16, i16, i16, i16)>,
19388 vis_region_after: Option<(i16, i16, i16, i16)>,
19389 the_port_after: u32,
19390 current_port_after: u32,
19391 saved_vis_after: bool,
19392 }
19393
19394 #[derive(Debug, PartialEq, Eq)]
19395 struct DialogLifecycleCleanupSnapshot {
19396 close_stack_after: u32,
19397 close_tracking_cleared: bool,
19398 close_retained_click_cleared: bool,
19399 close_visible_snapshot_cleared: bool,
19400 close_modal_entered_cleared: bool,
19401 close_saved_pixels_cleared: bool,
19402 close_window_list_contains_dialog: bool,
19403 close_front_window_after: u32,
19404 close_the_port_after: u32,
19405 close_current_port_after: u32,
19406 close_update_event_for_previous_window: bool,
19407 close_dialog_items_present_after: bool,
19408 dispose_stack_after: u32,
19409 dispose_tracking_cleared: bool,
19410 dispose_retained_click_cleared: bool,
19411 dispose_visible_snapshot_cleared: bool,
19412 dispose_modal_entered_cleared: bool,
19413 dispose_saved_pixels_cleared: bool,
19414 dispose_window_list_contains_dialog: bool,
19415 dispose_front_window_after: u32,
19416 dispose_the_port_after: u32,
19417 dispose_current_port_after: u32,
19418 dispose_update_event_for_previous_window: bool,
19419 dispose_dialog_items_present_after: bool,
19420 dispose_other_dialog_items_present_after: bool,
19421 dispose_dialog_item_handle_present_after: bool,
19422 dispose_other_dialog_item_handle_present_after: bool,
19423 dispose_dialog_control_handle_present_after: bool,
19424 dispose_other_dialog_control_handle_present_after: bool,
19425 dispose_dialog_control_value_present_after: bool,
19426 dispose_other_dialog_control_value_present_after: bool,
19427 dispose_hidden_rect_present_after: bool,
19428 dispose_other_hidden_rect_present_after: bool,
19429 dispose_cancel_item_present_after: bool,
19430 dispose_other_cancel_item_present_after: bool,
19431 dispose_pending_popup_cleared: bool,
19432 }
19433
19434 #[derive(Debug, PartialEq, Eq)]
19435 struct DialogCreationSnapshot {
19436 new_dialog_ptr: u32,
19437 new_stack_after: u32,
19438 new_result_slot: u32,
19439 new_window_list: Vec<u32>,
19440 new_front_window: u32,
19441 new_current_port: u32,
19442 new_the_port: u32,
19443 new_visible_byte: u8,
19444 new_goaway_byte: u8,
19445 new_refcon: u32,
19446 new_window_kind: i16,
19447 new_proc_id: i16,
19448 new_port_rect: (i16, i16, i16, i16),
19449 new_items_handle: u32,
19450 new_items_data_ptr: u32,
19451 new_first_item_handle_nonzero: bool,
19452 new_cached_items: Vec<(u8, (i16, i16, i16, i16), String, i16)>,
19453 new_update_region: Option<(i16, i16, i16, i16)>,
19454 new_update_event_queued: bool,
19455 new_edit_field: u16,
19456 new_default_item: u16,
19457 get_dialog_ptr: u32,
19458 get_stack_after: u32,
19459 get_result_slot: u32,
19460 get_window_list: Vec<u32>,
19461 get_front_window: u32,
19462 get_current_port: u32,
19463 get_the_port: u32,
19464 get_visible_byte: u8,
19465 get_refcon: u32,
19466 get_window_kind: i16,
19467 get_proc_id: i16,
19468 get_port_rect: (i16, i16, i16, i16),
19469 get_items_handle: u32,
19470 get_items_data_ptr: u32,
19471 get_uses_distinct_ditl_copy: bool,
19472 get_original_ditl_handle_field: u32,
19473 get_first_item_handle_nonzero: bool,
19474 get_cached_items: Vec<(u8, (i16, i16, i16, i16), String, i16)>,
19475 get_update_region: Option<(i16, i16, i16, i16)>,
19476 get_update_event_queued: bool,
19477 get_edit_field: u16,
19478 get_default_item: u16,
19479 }
19480
19481 #[derive(Debug, PartialEq, Eq)]
19482 struct AlertTrapCallSnapshot {
19483 trap_word: u16,
19484 result: i16,
19485 stack_after: u32,
19486 alert_stage_after: u16,
19487 anumber_after: u16,
19488 }
19489
19490 #[derive(Debug, PartialEq, Eq)]
19491 struct AlertResourcePrepSnapshot {
19492 could_present: (i16, u32),
19493 free_present: (i16, u32),
19494 could_missing: (i16, u32),
19495 free_missing: (i16, u32),
19496 }
19497
19498 #[derive(Debug, PartialEq, Eq)]
19499 struct AlertFamilySnapshot {
19500 staged_calls: Vec<AlertTrapCallSnapshot>,
19501 missing_call: AlertTrapCallSnapshot,
19502 resource_prep: AlertResourcePrepSnapshot,
19503 }
19504
19505 #[derive(Debug, PartialEq, Eq)]
19506 struct DialogInitSoundSnapshot {
19507 init_stack_after: u32,
19508 resume_proc_after_init: u32,
19509 da_beeper_after_init: u32,
19510 alert_stage_after_init: u16,
19511 anumber_after_init: u16,
19512 da_strings_after_init: [u32; 4],
19513 error_sound_stack_after: u32,
19514 da_beeper_after_error_sound: u32,
19515 nil_error_sound_stack_after: u32,
19516 da_beeper_after_nil_error_sound: u32,
19517 }
19518
19519 #[derive(Debug, PartialEq, Eq)]
19520 struct DialogDispatchDefaultCancelSnapshot {
19521 default_result: u16,
19522 default_stack_after: u32,
19523 default_adef_item: u16,
19524 default_tracking_item: i16,
19525 cancel_result: u16,
19526 cancel_stack_after: u32,
19527 cancel_map_item: Option<i16>,
19528 cancel_tracking_item: i16,
19529 tracks_result: u16,
19530 tracks_stack_after: u32,
19531 tracks_adef_item: u16,
19532 tracks_cancel_map_item: Option<i16>,
19533 tracks_tracking_items: (i16, i16),
19534 }
19535
19536 #[derive(Debug, PartialEq, Eq)]
19537 struct UpdtDialogUpdateRegionSnapshot {
19538 stack_after: u32,
19539 the_port_after: u32,
19540 current_port_after: u32,
19541 deferred_after: bool,
19542 queued_draw_procs: Vec<(u32, u32, i16)>,
19543 item_count_after: usize,
19544 inside_update_pixel_after: u8,
19545 outside_update_pixel_after: u8,
19546 outside_item_pixel_after: u8,
19547 }
19548
19549 #[derive(Debug, PartialEq, Eq)]
19550 struct DialogSelectEditTextKeySnapshot {
19551 keydown_result: u16,
19552 keydown_dialog_out: u32,
19553 keydown_item_hit: u16,
19554 keydown_stack_after: u32,
19555 keydown_handle_bytes: Vec<u8>,
19556 keydown_cached_text: String,
19557 keydown_item_selection: (i16, i16),
19558 keydown_te_text: Vec<u8>,
19559 keydown_te_length: u16,
19560 keydown_te_selection: (u16, u16),
19561 autokey_result: u16,
19562 autokey_dialog_out: u32,
19563 autokey_item_hit: u16,
19564 autokey_stack_after: u32,
19565 autokey_handle_bytes: Vec<u8>,
19566 autokey_cached_text: String,
19567 autokey_item_selection: (i16, i16),
19568 autokey_te_text: Vec<u8>,
19569 autokey_te_length: u16,
19570 autokey_te_selection: (u16, u16),
19571 }
19572
19573 #[derive(Debug, PartialEq, Eq)]
19574 struct TextEditScrapEditCase {
19575 text: Vec<u8>,
19576 selection: (u16, u16),
19577 scrap_length: u16,
19578 scrap_bytes: Vec<u8>,
19579 stack_after: u32,
19580 }
19581
19582 #[derive(Debug, PartialEq, Eq)]
19583 struct TextEditPrivateScrapEditingSnapshot {
19584 copy: TextEditScrapEditCase,
19585 cut: TextEditScrapEditCase,
19586 paste: TextEditScrapEditCase,
19587 delete: TextEditScrapEditCase,
19588 }
19589
19590 fn paramtext_da_strings(bus: &MacMemoryBus) -> ([u32; 4], [Vec<u8>; 4]) {
19591 let mut handles = [0u32; 4];
19592 let mut strings: [Vec<u8>; 4] = std::array::from_fn(|_| Vec::new());
19593 for i in 0..4 {
19594 let handle = bus.read_long(crate::memory::globals::addr::DA_STRINGS + i as u32 * 4);
19595 handles[i] = handle;
19596 if handle != 0 {
19597 let data_ptr = bus.read_long(handle);
19598 if data_ptr != 0 {
19599 strings[i] = bus.read_pstring(data_ptr);
19600 }
19601 }
19602 }
19603 (handles, strings)
19604 }
19605
19606 fn paramtext_results_for_theme(theme_id: UiThemeId) -> ParamTextSnapshot {
19607 let (mut disp, mut cpu, mut bus) = setup();
19608 disp.set_ui_theme_id(theme_id);
19609 let p0 = bus.alloc(32);
19610 let p1 = bus.alloc(32);
19611 let p2 = bus.alloc(32);
19612 let p3 = bus.alloc(32);
19613
19614 bus.write_pstring(p0, b"Doc");
19615 bus.write_pstring(p1, b"42");
19616 bus.write_pstring(p2, b"");
19617 bus.write_pstring(p3, b"Tail");
19618
19619 cpu.write_reg(Register::A7, TEST_SP);
19620 bus.write_long(TEST_SP, p3);
19621 bus.write_long(TEST_SP + 4, p2);
19622 bus.write_long(TEST_SP + 8, p1);
19623 bus.write_long(TEST_SP + 12, p0);
19624 disp.dispatch_dialog(true, 0x18B, &mut cpu, &mut bus)
19625 .unwrap()
19626 .unwrap();
19627
19628 let initial_slots = [
19629 disp.param_text[0].clone(),
19630 disp.param_text[1].clone(),
19631 disp.param_text[2].clone(),
19632 disp.param_text[3].clone(),
19633 ];
19634 let (initial_da_handles, initial_da_strings) = paramtext_da_strings(&bus);
19635 let initial_stack_after = cpu.read_reg(Register::A7);
19636 let initial_substitution = disp
19637 .apply_param_text("Cannot open ^0 (^1)^2^3 ^9")
19638 .into_owned();
19639
19640 let new0 = bus.alloc(32);
19641 bus.write_pstring(new0, b"NewDoc");
19642 cpu.write_reg(Register::A7, TEST_SP);
19643 bus.write_long(TEST_SP, 0); bus.write_long(TEST_SP + 4, 0); bus.write_long(TEST_SP + 8, 0); bus.write_long(TEST_SP + 12, new0);
19647 disp.dispatch_dialog(true, 0x18B, &mut cpu, &mut bus)
19648 .unwrap()
19649 .unwrap();
19650
19651 let nil_slots = [
19652 disp.param_text[0].clone(),
19653 disp.param_text[1].clone(),
19654 disp.param_text[2].clone(),
19655 disp.param_text[3].clone(),
19656 ];
19657 let (nil_da_handles, nil_da_strings) = paramtext_da_strings(&bus);
19658 let nil_stack_after = cpu.read_reg(Register::A7);
19659 let nil_substitution = disp
19660 .apply_param_text("Cannot open ^0 (^1)^2^3 ^9")
19661 .into_owned();
19662
19663 ParamTextSnapshot {
19664 initial_slots,
19665 initial_da_handles,
19666 initial_da_strings,
19667 initial_stack_after,
19668 initial_substitution,
19669 nil_slots,
19670 nil_da_handles,
19671 nil_da_strings,
19672 nil_stack_after,
19673 nil_substitution,
19674 }
19675 }
19676
19677 fn dialog_item_text_results_for_theme(theme_id: UiThemeId) -> DialogItemTextSnapshot {
19678 let (mut disp, mut cpu, mut bus) = setup();
19679 disp.set_ui_theme_id(theme_id);
19680 let dialog_ptr = bus.alloc(170);
19681 let text_handle = bus.alloc(4);
19682 let text_ptr = bus.alloc(3);
19683 let out_ptr = bus.alloc(256);
19684 let new_text_ptr = bus.alloc(32);
19685
19686 bus.write_long(text_handle, text_ptr);
19687 bus.write_bytes(text_ptr, b"Old");
19688 disp.dialog_items.insert(
19689 dialog_ptr,
19690 vec![DialogItem {
19691 item_type: 16,
19692 rect: (10, 20, 30, 120),
19693 text: "Old".to_string(),
19694 resource_id: 0,
19695 proc_ptr: 0,
19696 sel_start: 0,
19697 sel_end: 0,
19698 }],
19699 );
19700 disp.dialog_item_handles
19701 .insert(text_handle, (dialog_ptr, 0));
19702
19703 cpu.write_reg(Register::A7, TEST_SP);
19704 bus.write_long(TEST_SP, out_ptr);
19705 bus.write_long(TEST_SP + 4, text_handle);
19706 disp.dispatch_dialog(true, 0x190, &mut cpu, &mut bus)
19707 .unwrap()
19708 .unwrap();
19709 let initial_get_text = bus.read_pstring(out_ptr);
19710 let initial_get_stack_after = cpu.read_reg(Register::A7);
19711
19712 bus.write_pstring(new_text_ptr, b"Edited Text");
19713 cpu.write_reg(Register::A7, TEST_SP);
19714 bus.write_long(TEST_SP, new_text_ptr);
19715 bus.write_long(TEST_SP + 4, text_handle);
19716 disp.dispatch_dialog(true, 0x18F, &mut cpu, &mut bus)
19717 .unwrap()
19718 .unwrap();
19719 let set_stack_after = cpu.read_reg(Register::A7);
19720 let data_ptr_after_set = bus.read_long(text_handle);
19721 let handle_size_after_set = bus.get_alloc_size(data_ptr_after_set);
19722 let handle_bytes_after_set = bus.read_bytes(
19723 data_ptr_after_set,
19724 handle_size_after_set.unwrap_or(0) as usize,
19725 );
19726 let cached_text_after_set = disp.dialog_items.get(&dialog_ptr).unwrap()[0].text.clone();
19727
19728 cpu.write_reg(Register::A7, TEST_SP);
19729 bus.write_long(TEST_SP, out_ptr);
19730 bus.write_long(TEST_SP + 4, text_handle);
19731 disp.dispatch_dialog(true, 0x190, &mut cpu, &mut bus)
19732 .unwrap()
19733 .unwrap();
19734 let post_set_get_text = bus.read_pstring(out_ptr);
19735 let post_set_get_stack_after = cpu.read_reg(Register::A7);
19736
19737 DialogItemTextSnapshot {
19738 initial_get_text,
19739 initial_get_stack_after,
19740 set_stack_after,
19741 handle_size_after_set,
19742 handle_bytes_after_set,
19743 cached_text_after_set,
19744 post_set_get_text,
19745 post_set_get_stack_after,
19746 }
19747 }
19748
19749 fn select_dialog_item_text_results_for_theme(
19750 theme_id: UiThemeId,
19751 ) -> DialogItemTextSelectionSnapshot {
19752 let (mut disp, mut cpu, mut bus) = setup();
19753 disp.set_ui_theme_id(theme_id);
19754 let dialog_ptr = bus.alloc(170);
19755 let text_handle = bus.alloc(4);
19756 let te_ptr = bus.alloc(0x40);
19757
19758 bus.write_long(dialog_ptr + 160, text_handle);
19759 bus.write_long(text_handle, te_ptr);
19760 bus.write_word(dialog_ptr + 164, 0xFFFF);
19761 disp.dialog_items.insert(
19762 dialog_ptr,
19763 vec![
19764 DialogItem {
19765 item_type: 16,
19766 rect: (10, 20, 30, 120),
19767 text: "ABCDE".to_string(),
19768 resource_id: 0,
19769 proc_ptr: 0,
19770 sel_start: 1,
19771 sel_end: 1,
19772 },
19773 DialogItem {
19774 item_type: 8,
19775 rect: (34, 20, 50, 120),
19776 text: "STATIC".to_string(),
19777 resource_id: 0,
19778 proc_ptr: 0,
19779 sel_start: 1,
19780 sel_end: 3,
19781 },
19782 ],
19783 );
19784
19785 cpu.write_reg(Register::A7, TEST_SP);
19786 bus.write_word(TEST_SP, 32767); bus.write_word(TEST_SP + 2, 0); bus.write_word(TEST_SP + 4, 1); bus.write_long(TEST_SP + 6, dialog_ptr); disp.dispatch_dialog(true, 0x17E, &mut cpu, &mut bus)
19791 .unwrap()
19792 .unwrap();
19793 let item = &disp.dialog_items[&dialog_ptr][0];
19794 let whole_selection = (item.sel_start, item.sel_end);
19795 let whole_edit_field = bus.read_word(dialog_ptr + 164);
19796 let whole_te_selection = (
19797 bus.read_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET),
19798 bus.read_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET),
19799 );
19800 let whole_stack_after = cpu.read_reg(Register::A7);
19801
19802 cpu.write_reg(Register::A7, TEST_SP);
19803 bus.write_word(TEST_SP, 2); bus.write_word(TEST_SP + 2, 9); bus.write_word(TEST_SP + 4, 1); bus.write_long(TEST_SP + 6, dialog_ptr); disp.dispatch_dialog(true, 0x17E, &mut cpu, &mut bus)
19808 .unwrap()
19809 .unwrap();
19810 let item = &disp.dialog_items[&dialog_ptr][0];
19811 let normalized_selection = (item.sel_start, item.sel_end);
19812 let normalized_edit_field = bus.read_word(dialog_ptr + 164);
19813 let normalized_te_selection = (
19814 bus.read_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET),
19815 bus.read_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET),
19816 );
19817 let normalized_stack_after = cpu.read_reg(Register::A7);
19818
19819 cpu.write_reg(Register::A7, TEST_SP);
19820 bus.write_word(TEST_SP, 4); bus.write_word(TEST_SP + 2, 0); bus.write_word(TEST_SP + 4, 2); bus.write_long(TEST_SP + 6, dialog_ptr); disp.dispatch_dialog(true, 0x17E, &mut cpu, &mut bus)
19825 .unwrap()
19826 .unwrap();
19827 let item = &disp.dialog_items[&dialog_ptr][1];
19828 let non_edit_selection = (item.sel_start, item.sel_end);
19829 let non_edit_edit_field = bus.read_word(dialog_ptr + 164);
19830 let non_edit_te_selection = (
19831 bus.read_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET),
19832 bus.read_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET),
19833 );
19834 let non_edit_stack_after = cpu.read_reg(Register::A7);
19835
19836 DialogItemTextSelectionSnapshot {
19837 whole_selection,
19838 whole_edit_field,
19839 whole_te_selection,
19840 whole_stack_after,
19841 normalized_selection,
19842 normalized_edit_field,
19843 normalized_te_selection,
19844 normalized_stack_after,
19845 non_edit_selection,
19846 non_edit_edit_field,
19847 non_edit_te_selection,
19848 non_edit_stack_after,
19849 }
19850 }
19851
19852 fn is_dialog_event_results_for_theme(theme_id: UiThemeId) -> IsDialogEventSnapshot {
19853 fn dispatch_is_dialog_event(
19854 disp: &mut TrapDispatcher,
19855 cpu: &mut impl CpuOps,
19856 bus: &mut MacMemoryBus,
19857 event_ptr: u32,
19858 what: u16,
19859 message: u32,
19860 where_v: i16,
19861 where_h: i16,
19862 ) -> (u16, u32) {
19863 cpu.write_reg(Register::A7, TEST_SP);
19864 bus.write_word(event_ptr, what);
19865 bus.write_long(event_ptr + 2, message);
19866 bus.write_long(event_ptr + 6, 0);
19867 bus.write_word(event_ptr + 10, where_v as u16);
19868 bus.write_word(event_ptr + 12, where_h as u16);
19869 bus.write_word(event_ptr + 14, 0);
19870 bus.write_word(TEST_SP + 4, 0xBEEF);
19871 bus.write_long(TEST_SP, event_ptr);
19872
19873 disp.dispatch_dialog(true, 0x17F, cpu, bus)
19874 .unwrap()
19875 .unwrap();
19876 (bus.read_word(TEST_SP + 4), cpu.read_reg(Register::A7))
19877 }
19878
19879 let (mut disp, mut cpu, mut bus) = setup();
19880 disp.set_ui_theme_id(theme_id);
19881 let dialog_ptr = bus.alloc(170);
19882 let event_ptr = 0x300000u32;
19883
19884 disp.front_window = dialog_ptr;
19885 bus.write_word(dialog_ptr + 8, (-100i16) as u16);
19886 bus.write_word(dialog_ptr + 10, (-200i16) as u16);
19887 bus.write_word(dialog_ptr + 16, 0);
19888 bus.write_word(dialog_ptr + 18, 0);
19889 bus.write_word(dialog_ptr + 20, 80);
19890 bus.write_word(dialog_ptr + 22, 120);
19891 disp.dialog_items.insert(dialog_ptr, Vec::new());
19892
19893 let null_event =
19894 dispatch_is_dialog_event(&mut disp, &mut cpu, &mut bus, event_ptr, 0, 0, 0, 0);
19895 let key_down = dispatch_is_dialog_event(
19896 &mut disp,
19897 &mut cpu,
19898 &mut bus,
19899 event_ptr,
19900 3,
19901 u32::from(b'A'),
19902 0,
19903 0,
19904 );
19905 let mouse_inside =
19906 dispatch_is_dialog_event(&mut disp, &mut cpu, &mut bus, event_ptr, 1, 0, 120, 240);
19907 let mouse_outside =
19908 dispatch_is_dialog_event(&mut disp, &mut cpu, &mut bus, event_ptr, 1, 0, 40, 40);
19909 let update_target = dispatch_is_dialog_event(
19910 &mut disp, &mut cpu, &mut bus, event_ptr, 6, dialog_ptr, 0, 0,
19911 );
19912 let activate_target = dispatch_is_dialog_event(
19913 &mut disp, &mut cpu, &mut bus, event_ptr, 8, dialog_ptr, 0, 0,
19914 );
19915
19916 IsDialogEventSnapshot {
19917 null_event,
19918 key_down,
19919 mouse_inside,
19920 mouse_outside,
19921 update_target,
19922 activate_target,
19923 }
19924 }
19925
19926 fn text_handle_bytes(bus: &MacMemoryBus, handle: u32) -> Vec<u8> {
19927 let ptr = bus.read_long(handle);
19928 if ptr == 0 {
19929 return Vec::new();
19930 }
19931 let len = bus.get_alloc_size(ptr).unwrap_or(0) as usize;
19932 bus.read_bytes(ptr, len)
19933 }
19934
19935 fn textedit_scrap_contents(bus: &MacMemoryBus) -> (u16, Vec<u8>) {
19936 let scrap_length = bus.read_word(crate::memory::globals::addr::TE_SCRP_LENGTH);
19937 let scrap_handle = bus.read_long(crate::memory::globals::addr::TE_SCRP_HANDLE);
19938 if scrap_length == 0 || scrap_handle == 0 {
19939 return (scrap_length, Vec::new());
19940 }
19941 let scrap_ptr = bus.read_long(scrap_handle);
19942 if scrap_ptr == 0 {
19943 return (scrap_length, Vec::new());
19944 }
19945 (
19946 scrap_length,
19947 bus.read_bytes(scrap_ptr, scrap_length as usize),
19948 )
19949 }
19950
19951 fn textedit_scrap_edit_case(
19952 bus: &MacMemoryBus,
19953 te_handle: u32,
19954 stack_after: u32,
19955 ) -> TextEditScrapEditCase {
19956 let te_ptr = bus.read_long(te_handle);
19957 let (scrap_length, scrap_bytes) = textedit_scrap_contents(bus);
19958 TextEditScrapEditCase {
19959 text: TrapDispatcher::te_text_bytes(bus, te_handle),
19960 selection: (
19961 bus.read_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET),
19962 bus.read_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET),
19963 ),
19964 scrap_length,
19965 scrap_bytes,
19966 stack_after,
19967 }
19968 }
19969
19970 fn dialog_select_window_event_results_for_theme(
19971 theme_id: UiThemeId,
19972 ) -> DialogSelectWindowEventSnapshot {
19973 fn install_region(
19974 bus: &mut MacMemoryBus,
19975 handle: u32,
19976 data: u32,
19977 rect: (i16, i16, i16, i16),
19978 ) {
19979 bus.write_long(handle, data);
19980 bus.write_word(data, 10);
19981 bus.write_word(data + 2, rect.0 as u16);
19982 bus.write_word(data + 4, rect.1 as u16);
19983 bus.write_word(data + 6, rect.2 as u16);
19984 bus.write_word(data + 8, rect.3 as u16);
19985 }
19986
19987 fn dispatch_dialog_select(
19988 disp: &mut TrapDispatcher,
19989 cpu: &mut impl CpuOps,
19990 bus: &mut MacMemoryBus,
19991 event_ptr: u32,
19992 dialog_out_ptr: u32,
19993 item_hit_ptr: u32,
19994 what: u16,
19995 message: u32,
19996 ) -> (u16, u32, u16, u32) {
19997 cpu.write_reg(Register::A7, TEST_SP);
19998 bus.write_word(event_ptr, what);
19999 bus.write_long(event_ptr + 2, message);
20000 bus.write_long(event_ptr + 6, 0);
20001 bus.write_word(event_ptr + 10, 0);
20002 bus.write_word(event_ptr + 12, 0);
20003 bus.write_word(event_ptr + 14, 0);
20004 bus.write_word(TEST_SP + 12, 0xBEEF);
20005 bus.write_long(dialog_out_ptr, 0xDEAD_BEEF);
20006 bus.write_word(item_hit_ptr, 0xCAFE);
20007 bus.write_long(TEST_SP, item_hit_ptr);
20008 bus.write_long(TEST_SP + 4, dialog_out_ptr);
20009 bus.write_long(TEST_SP + 8, event_ptr);
20010
20011 disp.dispatch_dialog(true, 0x180, cpu, bus)
20012 .unwrap()
20013 .unwrap();
20014
20015 (
20016 bus.read_word(TEST_SP + 12),
20017 bus.read_long(dialog_out_ptr),
20018 bus.read_word(item_hit_ptr),
20019 cpu.read_reg(Register::A7),
20020 )
20021 }
20022
20023 let (mut disp, mut cpu, mut bus) = setup_with_port();
20024 disp.set_ui_theme_id(theme_id);
20025 let initial_port = 0x181000;
20026 disp.set_current_port_state(&mut bus, &mut cpu, initial_port, None);
20027
20028 let screen_base = bus.alloc(100 * 100);
20029 bus.write_long(0x0824, screen_base);
20030 disp.screen_mode = (screen_base, 100, 100, 100, 8);
20031
20032 let dialog_ptr = bus.alloc(256);
20033 let event_ptr = 0x300000u32;
20034 let dialog_out_ptr = 0x300100u32;
20035 let item_hit_ptr = 0x300104u32;
20036
20037 let vis_rgn = bus.alloc(10);
20038 let vis_rgn_handle = bus.alloc(4);
20039 install_region(&mut bus, vis_rgn_handle, vis_rgn, (0, 0, 100, 100));
20040 let clip_rgn = bus.alloc(10);
20041 let clip_rgn_handle = bus.alloc(4);
20042 install_region(&mut bus, clip_rgn_handle, clip_rgn, (0, 0, 100, 100));
20043 let update_rgn = bus.alloc(10);
20044 let update_rgn_handle = bus.alloc(4);
20045 install_region(&mut bus, update_rgn_handle, update_rgn, (0, 0, 40, 40));
20046
20047 disp.front_window = dialog_ptr;
20048 disp.window_list.push(dialog_ptr);
20049 bus.write_word(dialog_ptr, 0);
20050 bus.write_long(dialog_ptr + 2, screen_base);
20051 bus.write_word(dialog_ptr + 6, 100);
20052 bus.write_word(dialog_ptr + 8, 0);
20053 bus.write_word(dialog_ptr + 10, 0);
20054 bus.write_word(dialog_ptr + 12, 100);
20055 bus.write_word(dialog_ptr + 14, 100);
20056 bus.write_word(dialog_ptr + 16, 0);
20057 bus.write_word(dialog_ptr + 18, 0);
20058 bus.write_word(dialog_ptr + 20, 100);
20059 bus.write_word(dialog_ptr + 22, 100);
20060 bus.write_long(dialog_ptr + 24, vis_rgn_handle);
20061 bus.write_long(dialog_ptr + 28, clip_rgn_handle);
20062 bus.write_word(dialog_ptr + 108, 2);
20063 bus.write_long(dialog_ptr + 122, update_rgn_handle);
20064 disp.dialog_items.insert(
20065 dialog_ptr,
20066 vec![
20067 DialogItem {
20068 item_type: 0,
20069 rect: (8, 8, 24, 32),
20070 proc_ptr: 0x500000,
20071 ..Default::default()
20072 },
20073 DialogItem {
20074 item_type: 0,
20075 rect: (60, 60, 80, 80),
20076 proc_ptr: 0x600000,
20077 ..Default::default()
20078 },
20079 DialogItem {
20080 item_type: 8,
20081 rect: (90, 90, 96, 96),
20082 text: "x".to_string(),
20083 ..Default::default()
20084 },
20085 ],
20086 );
20087 disp.dialog_initial_draw_deferred.insert(dialog_ptr);
20088
20089 let (update_result, update_dialog_out, update_item_hit, update_stack_after) =
20090 dispatch_dialog_select(
20091 &mut disp,
20092 &mut cpu,
20093 &mut bus,
20094 event_ptr,
20095 dialog_out_ptr,
20096 item_hit_ptr,
20097 6,
20098 dialog_ptr,
20099 );
20100 let update_deferred_after = disp.dialog_initial_draw_deferred.contains(&dialog_ptr);
20101 let update_region_after =
20102 TrapDispatcher::region_handle_rect(&bus, bus.read_long(dialog_ptr + 122));
20103 let update_vis_region_after =
20104 TrapDispatcher::region_handle_rect(&bus, bus.read_long(dialog_ptr + 24));
20105 let update_the_port_after = bus.read_long(crate::memory::globals::addr::THE_PORT);
20106 let update_current_port_after = disp.current_port;
20107 let update_saved_vis_after = disp.saved_vis_regions.contains_key(&dialog_ptr);
20108 let update_queued_draw_procs = disp
20109 .modeless_dialog_draw_proc_queue
20110 .iter()
20111 .copied()
20112 .collect();
20113 let update_item_count_after = disp
20114 .dialog_items
20115 .get(&dialog_ptr)
20116 .map(Vec::len)
20117 .unwrap_or_default();
20118
20119 let (activate_result, activate_dialog_out, activate_item_hit, activate_stack_after) =
20120 dispatch_dialog_select(
20121 &mut disp,
20122 &mut cpu,
20123 &mut bus,
20124 event_ptr,
20125 dialog_out_ptr,
20126 item_hit_ptr,
20127 8,
20128 dialog_ptr,
20129 );
20130
20131 DialogSelectWindowEventSnapshot {
20132 dialog_ptr,
20133 update_result,
20134 update_dialog_out,
20135 update_item_hit,
20136 update_stack_after,
20137 update_deferred_after,
20138 update_region_after,
20139 update_vis_region_after,
20140 update_the_port_after,
20141 update_current_port_after,
20142 update_saved_vis_after,
20143 update_queued_draw_procs,
20144 update_item_count_after,
20145 activate_result,
20146 activate_dialog_out,
20147 activate_item_hit,
20148 activate_stack_after,
20149 }
20150 }
20151
20152 fn dialog_select_edit_text_mouse_results_for_theme(
20153 theme_id: UiThemeId,
20154 ) -> DialogSelectEditTextMouseSnapshot {
20155 fn dispatch_dialog_select(
20156 disp: &mut TrapDispatcher,
20157 cpu: &mut impl CpuOps,
20158 bus: &mut MacMemoryBus,
20159 event_ptr: u32,
20160 dialog_out_ptr: u32,
20161 item_hit_ptr: u32,
20162 what: u16,
20163 where_v: i16,
20164 where_h: i16,
20165 ) -> (u16, u32, u16, u32) {
20166 cpu.write_reg(Register::A7, TEST_SP);
20167 bus.write_word(event_ptr, what);
20168 bus.write_long(event_ptr + 2, 0);
20169 bus.write_long(event_ptr + 6, 0);
20170 bus.write_word(event_ptr + 10, where_v as u16);
20171 bus.write_word(event_ptr + 12, where_h as u16);
20172 bus.write_word(event_ptr + 14, 0);
20173 bus.write_word(TEST_SP + 12, 0xBEEF);
20174 bus.write_long(dialog_out_ptr, 0xDEAD_BEEF);
20175 bus.write_word(item_hit_ptr, 0xCAFE);
20176 bus.write_long(TEST_SP, item_hit_ptr);
20177 bus.write_long(TEST_SP + 4, dialog_out_ptr);
20178 bus.write_long(TEST_SP + 8, event_ptr);
20179
20180 disp.dispatch_dialog(true, 0x180, cpu, bus)
20181 .unwrap()
20182 .unwrap();
20183
20184 (
20185 bus.read_word(TEST_SP + 12),
20186 bus.read_long(dialog_out_ptr),
20187 bus.read_word(item_hit_ptr),
20188 cpu.read_reg(Register::A7),
20189 )
20190 }
20191
20192 fn write_ditl_item(
20193 bus: &mut MacMemoryBus,
20194 ditl_ptr: u32,
20195 offset: u32,
20196 handle: u32,
20197 rect: (i16, i16, i16, i16),
20198 item_type: u8,
20199 ) {
20200 bus.write_long(ditl_ptr + offset, handle);
20201 bus.write_word(ditl_ptr + offset + 4, rect.0 as u16);
20202 bus.write_word(ditl_ptr + offset + 6, rect.1 as u16);
20203 bus.write_word(ditl_ptr + offset + 8, rect.2 as u16);
20204 bus.write_word(ditl_ptr + offset + 10, rect.3 as u16);
20205 bus.write_byte(ditl_ptr + offset + 12, item_type);
20206 bus.write_byte(ditl_ptr + offset + 13, 0);
20207 }
20208
20209 let (mut disp, mut cpu, mut bus) = setup();
20210 disp.set_ui_theme_id(theme_id);
20211 let dialog_ptr = bus.alloc(170);
20212 let event_ptr = 0x300000u32;
20213 let dialog_out_ptr = 0x300100u32;
20214 let item_hit_ptr = 0x300104u32;
20215 let items_handle = bus.alloc(4);
20216 let ditl_ptr = bus.alloc(30);
20217 let item1_text_handle = bus.alloc(4);
20218 let item1_text_ptr = bus.alloc(5);
20219 let item2_text_handle = bus.alloc(4);
20220 let item2_text_ptr = bus.alloc(6);
20221 let text_h = TrapDispatcher::allocate_te_handle(&mut bus);
20222
20223 disp.front_window = dialog_ptr;
20224 bus.write_word(dialog_ptr + 8, (-100i16) as u16);
20225 bus.write_word(dialog_ptr + 10, (-200i16) as u16);
20226 bus.write_word(dialog_ptr + 16, 0);
20227 bus.write_word(dialog_ptr + 18, 0);
20228 bus.write_word(dialog_ptr + 20, 100);
20229 bus.write_word(dialog_ptr + 22, 160);
20230 bus.write_long(dialog_ptr + 156, items_handle);
20231 bus.write_long(dialog_ptr + 160, text_h);
20232 bus.write_word(dialog_ptr + 164, 0); bus.write_long(items_handle, ditl_ptr);
20235 bus.write_word(ditl_ptr, 1); write_ditl_item(
20237 &mut bus,
20238 ditl_ptr,
20239 2,
20240 item1_text_handle,
20241 (20, 20, 38, 120),
20242 16,
20243 );
20244 write_ditl_item(
20245 &mut bus,
20246 ditl_ptr,
20247 16,
20248 item2_text_handle,
20249 (42, 20, 62, 120),
20250 16,
20251 );
20252 bus.write_long(item1_text_handle, item1_text_ptr);
20253 bus.write_bytes(item1_text_ptr, b"First");
20254 bus.write_long(item2_text_handle, item2_text_ptr);
20255 bus.write_bytes(item2_text_ptr, b"Second");
20256
20257 disp.te_set_text_contents(&mut bus, text_h, b"First");
20258 let te_ptr = bus.read_long(text_h);
20259 bus.write_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET, 1);
20260 bus.write_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET, 1);
20261 disp.dialog_items.insert(
20262 dialog_ptr,
20263 vec![
20264 DialogItem {
20265 item_type: 16,
20266 rect: (20, 20, 38, 120),
20267 text: "First".to_string(),
20268 resource_id: 0,
20269 proc_ptr: 0,
20270 sel_start: 1,
20271 sel_end: 1,
20272 },
20273 DialogItem {
20274 item_type: 16,
20275 rect: (42, 20, 62, 120),
20276 text: "Second".to_string(),
20277 resource_id: 0,
20278 proc_ptr: 0,
20279 sel_start: 2,
20280 sel_end: 4,
20281 },
20282 ],
20283 );
20284
20285 let (mouse_result, mouse_dialog_out, mouse_item_hit, mouse_stack_after) =
20286 dispatch_dialog_select(
20287 &mut disp,
20288 &mut cpu,
20289 &mut bus,
20290 event_ptr,
20291 dialog_out_ptr,
20292 item_hit_ptr,
20293 1,
20294 150,
20295 240,
20296 );
20297 let mouse_edit_field = bus.read_word(dialog_ptr + 164);
20298 let mouse_item = &disp.dialog_items[&dialog_ptr][1];
20299 let mouse_item_selection = (mouse_item.sel_start, mouse_item.sel_end);
20300 let mouse_te_text = TrapDispatcher::te_text_bytes(&bus, text_h);
20301 let mouse_te_length = bus.read_word(te_ptr + TrapDispatcher::TE_LENGTH_OFFSET);
20302 let mouse_te_selection = (
20303 bus.read_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET),
20304 bus.read_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET),
20305 );
20306
20307 let (null_result, null_dialog_out, null_item_hit, null_stack_after) =
20308 dispatch_dialog_select(
20309 &mut disp,
20310 &mut cpu,
20311 &mut bus,
20312 event_ptr,
20313 dialog_out_ptr,
20314 item_hit_ptr,
20315 0,
20316 0,
20317 0,
20318 );
20319 let null_edit_field = bus.read_word(dialog_ptr + 164);
20320 let null_te_text = TrapDispatcher::te_text_bytes(&bus, text_h);
20321 let null_te_length = bus.read_word(te_ptr + TrapDispatcher::TE_LENGTH_OFFSET);
20322 let null_te_selection = (
20323 bus.read_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET),
20324 bus.read_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET),
20325 );
20326
20327 DialogSelectEditTextMouseSnapshot {
20328 mouse_result,
20329 mouse_dialog_out,
20330 mouse_item_hit,
20331 mouse_stack_after,
20332 mouse_edit_field,
20333 mouse_item_selection,
20334 mouse_te_text,
20335 mouse_te_length,
20336 mouse_te_selection,
20337 null_result,
20338 null_dialog_out,
20339 null_item_hit,
20340 null_stack_after,
20341 null_edit_field,
20342 null_te_text,
20343 null_te_length,
20344 null_te_selection,
20345 }
20346 }
20347
20348 fn modal_dialog_edit_text_mouse_results_for_theme(
20349 theme_id: UiThemeId,
20350 ) -> ModalDialogEditTextMouseSnapshot {
20351 let (mut disp, mut cpu, mut bus) = setup();
20352 disp.set_ui_theme_id(theme_id);
20353 let dialog_ptr = bus.alloc(170);
20354 let item_hit_ptr = 0x300100u32;
20355 let items_handle = bus.alloc(4);
20356 let ditl_ptr = bus.alloc(30);
20357 let item1_text_handle = bus.alloc(4);
20358 let item1_text_ptr = bus.alloc(5);
20359 let item2_text_handle = bus.alloc(4);
20360 let item2_text_ptr = bus.alloc(6);
20361 let text_h = TrapDispatcher::allocate_te_handle(&mut bus);
20362
20363 bus.write_word(dialog_ptr + 8, (-100i16) as u16);
20364 bus.write_word(dialog_ptr + 10, (-200i16) as u16);
20365 bus.write_word(dialog_ptr + 16, 0);
20366 bus.write_word(dialog_ptr + 18, 0);
20367 bus.write_word(dialog_ptr + 20, 100);
20368 bus.write_word(dialog_ptr + 22, 160);
20369 bus.write_long(dialog_ptr + 156, items_handle);
20370 bus.write_long(dialog_ptr + 160, text_h);
20371 bus.write_word(dialog_ptr + 164, 0); bus.write_long(items_handle, ditl_ptr);
20374 bus.write_word(ditl_ptr, 1); bus.write_long(ditl_ptr + 2, item1_text_handle);
20376 bus.write_word(ditl_ptr + 6, 20);
20377 bus.write_word(ditl_ptr + 8, 20);
20378 bus.write_word(ditl_ptr + 10, 38);
20379 bus.write_word(ditl_ptr + 12, 120);
20380 bus.write_byte(ditl_ptr + 14, 16);
20381 bus.write_byte(ditl_ptr + 15, 0);
20382 bus.write_long(ditl_ptr + 16, item2_text_handle);
20383 bus.write_word(ditl_ptr + 20, 42);
20384 bus.write_word(ditl_ptr + 22, 20);
20385 bus.write_word(ditl_ptr + 24, 62);
20386 bus.write_word(ditl_ptr + 26, 120);
20387 bus.write_byte(ditl_ptr + 28, 16);
20388 bus.write_byte(ditl_ptr + 29, 0);
20389
20390 bus.write_long(item1_text_handle, item1_text_ptr);
20391 bus.write_bytes(item1_text_ptr, b"First");
20392 bus.write_long(item2_text_handle, item2_text_ptr);
20393 bus.write_bytes(item2_text_ptr, b"Second");
20394 disp.te_set_text_contents(&mut bus, text_h, b"First");
20395 let te_ptr = bus.read_long(text_h);
20396 bus.write_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET, 1);
20397 bus.write_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET, 1);
20398
20399 let items = vec![
20400 DialogItem {
20401 item_type: 16,
20402 rect: (20, 20, 38, 120),
20403 text: "First".to_string(),
20404 resource_id: 0,
20405 proc_ptr: 0,
20406 sel_start: 1,
20407 sel_end: 1,
20408 },
20409 DialogItem {
20410 item_type: 16,
20411 rect: (42, 20, 62, 120),
20412 text: "Second".to_string(),
20413 resource_id: 0,
20414 proc_ptr: 0,
20415 sel_start: 2,
20416 sel_end: 4,
20417 },
20418 ];
20419 disp.dialog_items.insert(dialog_ptr, items.clone());
20420 disp.dialog_tracking = Some(crate::trap::dispatch::DialogTrackingState {
20421 dialog_ptr,
20422 bounds: (100, 200, 200, 360),
20423 title: String::new(),
20424 proc_id: 2,
20425 items,
20426 default_item: 0,
20427 cancel_item: 0,
20428 edit_text: "First".to_string(),
20429 edit_item: 1,
20430 saved_pixels: vec![0x11, 0x22, 0x33],
20431 stack_ptr: TEST_SP,
20432 item_hit_ptr,
20433 rendered_pixels: Vec::new(),
20434 flash_remaining: 0,
20435 flash_delay: 0,
20436 flash_item: 0,
20437 edit_text_modified: false,
20438 draw_proc_queue: VecDeque::new(),
20439 draw_procs_done: true,
20440 rendered_pixels_final: true,
20441 filter_proc: 0,
20442 game_managed: false,
20443 last_filter_event: None,
20444 popup_draws: Vec::new(),
20445 active_popup: None,
20446 active_button: None,
20447 active_user_item: None,
20448 });
20449 bus.write_word(item_hit_ptr, 0xCAFE);
20450 cpu.write_reg(Register::A7, TEST_SP);
20451 disp.mouse_button = true;
20452 disp.mouse_pos = (150, 240);
20453 disp.event_queue
20454 .push_back(crate::trap::dispatch::QueuedEvent {
20455 what: 1,
20456 message: 0,
20457 where_v: 150,
20458 where_h: 240,
20459 modifiers: 0,
20460 });
20461 disp.event_queue
20462 .push_back(crate::trap::dispatch::QueuedEvent {
20463 what: 2,
20464 message: 0,
20465 where_v: 150,
20466 where_h: 240,
20467 modifiers: 0,
20468 });
20469
20470 disp.dispatch_dialog(true, 0x191, &mut cpu, &mut bus)
20471 .unwrap()
20472 .unwrap();
20473 let item = &disp.dialog_items[&dialog_ptr][1];
20474
20475 ModalDialogEditTextMouseSnapshot {
20476 item_hit: bus.read_word(item_hit_ptr),
20477 stack_after: cpu.read_reg(Register::A7),
20478 tracking_finished: disp.dialog_tracking.is_none(),
20479 retained_visible_snapshot: disp.dialog_visible_snapshots.contains_key(&dialog_ptr),
20480 saved_background_retained: disp.dialog_saved_pixels.contains_key(&dialog_ptr),
20481 queued_mouse_up_consumed: !disp.event_queue.iter().any(|event| event.what == 2),
20482 edit_field: bus.read_word(dialog_ptr + 164),
20483 item_selection: (item.sel_start, item.sel_end),
20484 te_text: TrapDispatcher::te_text_bytes(&bus, text_h),
20485 te_length: bus.read_word(te_ptr + TrapDispatcher::TE_LENGTH_OFFSET),
20486 te_selection: (
20487 bus.read_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET),
20488 bus.read_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET),
20489 ),
20490 handle_bytes: text_handle_bytes(&bus, item2_text_handle),
20491 }
20492 }
20493
20494 fn modal_dialog_keyboard_button_results_for_theme(
20495 theme_id: UiThemeId,
20496 ) -> ModalDialogKeyboardButtonSnapshot {
20497 fn run_key(
20498 theme_id: UiThemeId,
20499 key_code: u8,
20500 char_code: u8,
20501 modifiers: u16,
20502 ) -> ModalDialogKeyboardButtonCase {
20503 let (mut disp, mut cpu, mut bus) = setup_with_port();
20504 disp.set_ui_theme_id(theme_id);
20505 let dialog_ptr = 0x200000u32;
20506 let item_hit_ptr = 0x300000u32;
20507 let items = vec![
20508 DialogItem {
20509 item_type: 4,
20510 rect: (20, 30, 60, 110),
20511 text: "OK".to_string(),
20512 resource_id: 0,
20513 proc_ptr: 0,
20514 sel_start: 0,
20515 sel_end: 0,
20516 },
20517 DialogItem {
20518 item_type: 4,
20519 rect: (20, 124, 60, 214),
20520 text: "Cancel".to_string(),
20521 resource_id: 0,
20522 proc_ptr: 0,
20523 sel_start: 0,
20524 sel_end: 0,
20525 },
20526 ];
20527 disp.dialog_tracking = Some(crate::trap::dispatch::DialogTrackingState {
20528 dialog_ptr,
20529 bounds: (100, 200, 200, 440),
20530 title: String::new(),
20531 proc_id: 2,
20532 items,
20533 default_item: 1,
20534 cancel_item: 2,
20535 edit_text: String::new(),
20536 edit_item: 0,
20537 saved_pixels: Vec::new(),
20538 stack_ptr: TEST_SP,
20539 item_hit_ptr,
20540 rendered_pixels: Vec::new(),
20541 flash_remaining: 0,
20542 flash_delay: 0,
20543 flash_item: 0,
20544 edit_text_modified: false,
20545 draw_proc_queue: VecDeque::new(),
20546 draw_procs_done: true,
20547 rendered_pixels_final: true,
20548 filter_proc: 0,
20549 game_managed: false,
20550 last_filter_event: None,
20551 popup_draws: Vec::new(),
20552 active_popup: None,
20553 active_button: None,
20554 active_user_item: None,
20555 });
20556 disp.event_queue
20557 .push_back(crate::trap::dispatch::QueuedEvent {
20558 what: 3,
20559 message: (u32::from(key_code) << 8) | u32::from(char_code),
20560 where_v: 0,
20561 where_h: 0,
20562 modifiers,
20563 });
20564 bus.write_word(item_hit_ptr, 0xCAFE);
20565 cpu.write_reg(Register::A7, TEST_SP);
20566
20567 disp.dispatch_dialog(true, 0x191, &mut cpu, &mut bus)
20568 .unwrap()
20569 .unwrap();
20570 let flash_item_after_key = disp
20571 .dialog_tracking
20572 .as_ref()
20573 .map(|tracking| tracking.flash_item)
20574 .unwrap_or(0);
20575 let stack_after_key = cpu.read_reg(Register::A7);
20576 let item_hit_after_key = bus.read_word(item_hit_ptr);
20577
20578 {
20579 let tracking = disp.dialog_tracking.as_mut().unwrap();
20580 tracking.flash_remaining = 1;
20581 tracking.flash_delay = 0;
20582 }
20583 disp.dispatch_dialog(true, 0x191, &mut cpu, &mut bus)
20584 .unwrap()
20585 .unwrap();
20586
20587 ModalDialogKeyboardButtonCase {
20588 flash_item_after_key,
20589 stack_after_key,
20590 item_hit_after_key,
20591 item_hit_after_flash: bus.read_word(item_hit_ptr),
20592 stack_after_flash: cpu.read_reg(Register::A7),
20593 tracking_finished: disp.dialog_tracking.is_none(),
20594 }
20595 }
20596
20597 ModalDialogKeyboardButtonSnapshot {
20598 return_key: run_key(theme_id, 0x24, 0x0D, 0),
20599 enter_key: run_key(theme_id, 0x4C, 0x03, 0),
20600 escape_key: run_key(theme_id, 0x35, 0x1B, 0),
20601 command_period: run_key(theme_id, 0x2F, b'.', 0x0100),
20602 }
20603 }
20604
20605 fn modal_dialog_update_event_results_for_theme(
20606 theme_id: UiThemeId,
20607 ) -> ModalDialogUpdateEventSnapshot {
20608 fn install_region(
20609 bus: &mut MacMemoryBus,
20610 handle: u32,
20611 data: u32,
20612 rect: (i16, i16, i16, i16),
20613 ) {
20614 bus.write_long(handle, data);
20615 bus.write_word(data, 10);
20616 bus.write_word(data + 2, rect.0 as u16);
20617 bus.write_word(data + 4, rect.1 as u16);
20618 bus.write_word(data + 6, rect.2 as u16);
20619 bus.write_word(data + 8, rect.3 as u16);
20620 }
20621
20622 let (mut disp, mut cpu, mut bus) = setup_with_port();
20623 disp.set_ui_theme_id(theme_id);
20624
20625 let initial_port = 0x181000;
20626 disp.set_current_port_state(&mut bus, &mut cpu, initial_port, None);
20627
20628 let screen_base = bus.alloc(80 * 80);
20629 bus.write_long(0x0824, screen_base);
20630 disp.screen_mode = (screen_base, 80, 80, 80, 8);
20631
20632 let dialog_ptr = bus.alloc(256);
20633 let item_hit_ptr = 0x300100u32;
20634 let bounds = (10, 10, 40, 40);
20635 let snapshot_width = (bounds.3 - bounds.1 + TrapDispatcher::DBOX_FRAME_MARGIN * 2) as usize;
20636 let snapshot_height =
20637 (bounds.2 - bounds.0 + TrapDispatcher::DBOX_FRAME_MARGIN * 2) as usize;
20638 let snapshot_index = (17 - (bounds.0 - TrapDispatcher::DBOX_FRAME_MARGIN)) as usize
20639 * snapshot_width
20640 + (19 - (bounds.1 - TrapDispatcher::DBOX_FRAME_MARGIN)) as usize;
20641 let mut rendered_pixels = vec![0x00; snapshot_width * snapshot_height];
20642 rendered_pixels[snapshot_index] = 0x44;
20643
20644 let vis_rgn = bus.alloc(10);
20645 let vis_rgn_handle = bus.alloc(4);
20646 install_region(&mut bus, vis_rgn_handle, vis_rgn, (0, 0, 80, 80));
20647 let clip_rgn = bus.alloc(10);
20648 let clip_rgn_handle = bus.alloc(4);
20649 install_region(&mut bus, clip_rgn_handle, clip_rgn, (0, 0, 80, 80));
20650 let update_rgn = bus.alloc(10);
20651 let update_rgn_handle = bus.alloc(4);
20652 install_region(&mut bus, update_rgn_handle, update_rgn, (0, 0, 40, 40));
20653
20654 bus.write_word(dialog_ptr, 0);
20655 bus.write_long(dialog_ptr + 2, screen_base);
20656 bus.write_word(dialog_ptr + 6, 80);
20657 bus.write_word(dialog_ptr + 8, 0);
20658 bus.write_word(dialog_ptr + 10, 0);
20659 bus.write_word(dialog_ptr + 12, 80);
20660 bus.write_word(dialog_ptr + 14, 80);
20661 bus.write_word(dialog_ptr + 16, 0);
20662 bus.write_word(dialog_ptr + 18, 0);
20663 bus.write_word(dialog_ptr + 20, 80);
20664 bus.write_word(dialog_ptr + 22, 80);
20665 bus.write_long(dialog_ptr + 24, vis_rgn_handle);
20666 bus.write_long(dialog_ptr + 28, clip_rgn_handle);
20667 bus.write_long(dialog_ptr + 122, update_rgn_handle);
20668
20669 bus.write_word(item_hit_ptr, 0xCAFE);
20670 disp.front_window = dialog_ptr;
20671 disp.window_bounds = bounds;
20672 disp.dialog_tracking = Some(crate::trap::dispatch::DialogTrackingState {
20673 dialog_ptr,
20674 bounds,
20675 title: String::new(),
20676 proc_id: 2,
20677 items: Vec::new(),
20678 default_item: 0,
20679 cancel_item: 0,
20680 edit_text: String::new(),
20681 edit_item: 0,
20682 saved_pixels: Vec::new(),
20683 stack_ptr: TEST_SP,
20684 item_hit_ptr,
20685 rendered_pixels,
20686 flash_remaining: 0,
20687 flash_delay: 0,
20688 flash_item: 0,
20689 edit_text_modified: false,
20690 draw_proc_queue: VecDeque::new(),
20691 draw_procs_done: true,
20692 rendered_pixels_final: true,
20693 filter_proc: 0,
20694 game_managed: false,
20695 last_filter_event: None,
20696 popup_draws: Vec::new(),
20697 active_popup: None,
20698 active_button: None,
20699 active_user_item: None,
20700 });
20701 disp.event_queue
20702 .push_back(crate::trap::dispatch::QueuedEvent {
20703 what: 6,
20704 message: dialog_ptr,
20705 where_v: 0,
20706 where_h: 0,
20707 modifiers: 0,
20708 });
20709 cpu.write_reg(Register::A7, TEST_SP);
20710
20711 disp.dispatch_dialog(true, 0x191, &mut cpu, &mut bus)
20712 .unwrap()
20713 .unwrap();
20714
20715 let tracking = disp.dialog_tracking.as_ref().unwrap();
20716 ModalDialogUpdateEventSnapshot {
20717 dialog_ptr,
20718 item_hit_after: bus.read_word(item_hit_ptr),
20719 stack_after: cpu.read_reg(Register::A7),
20720 tracking_finished: disp.dialog_tracking.is_none(),
20721 rendered_pixels_final: tracking.rendered_pixels_final,
20722 retained_pixel_after: bus.read_byte(screen_base + 17 * 80 + 19),
20723 retained_snapshot_pixel_after: tracking.rendered_pixels[snapshot_index],
20724 update_region_after: TrapDispatcher::region_handle_rect(
20725 &bus,
20726 bus.read_long(dialog_ptr + 122),
20727 ),
20728 vis_region_after: TrapDispatcher::region_handle_rect(
20729 &bus,
20730 bus.read_long(dialog_ptr + 24),
20731 ),
20732 the_port_after: bus.read_long(crate::memory::globals::addr::THE_PORT),
20733 current_port_after: disp.current_port,
20734 saved_vis_after: disp.saved_vis_regions.contains_key(&dialog_ptr),
20735 }
20736 }
20737
20738 fn dialog_select_edit_text_key_results_for_theme(
20739 theme_id: UiThemeId,
20740 ) -> DialogSelectEditTextKeySnapshot {
20741 let (mut disp, mut cpu, mut bus) = setup();
20742 disp.set_ui_theme_id(theme_id);
20743 let dialog_ptr = bus.alloc(170);
20744 let event_ptr = 0x300000u32;
20745 let dialog_out_ptr = 0x300100u32;
20746 let item_hit_ptr = 0x300104u32;
20747 let items_handle = bus.alloc(4);
20748 let ditl_ptr = bus.alloc(16);
20749 let item_text_handle = bus.alloc(4);
20750 let item_text_ptr = bus.alloc(5);
20751 let text_h = TrapDispatcher::allocate_te_handle(&mut bus);
20752
20753 disp.front_window = dialog_ptr;
20754 bus.write_word(dialog_ptr + 8, 0);
20755 bus.write_word(dialog_ptr + 10, 0);
20756 bus.write_word(dialog_ptr + 16, 0);
20757 bus.write_word(dialog_ptr + 18, 0);
20758 bus.write_word(dialog_ptr + 20, 100);
20759 bus.write_word(dialog_ptr + 22, 160);
20760 bus.write_long(dialog_ptr + 156, items_handle);
20761 bus.write_long(dialog_ptr + 160, text_h);
20762 bus.write_word(dialog_ptr + 164, 0); bus.write_long(items_handle, ditl_ptr);
20764 bus.write_word(ditl_ptr, 0); bus.write_long(ditl_ptr + 2, item_text_handle);
20766 bus.write_word(ditl_ptr + 6, 20);
20767 bus.write_word(ditl_ptr + 8, 30);
20768 bus.write_word(ditl_ptr + 10, 60);
20769 bus.write_word(ditl_ptr + 12, 110);
20770 bus.write_byte(ditl_ptr + 14, 16); bus.write_byte(ditl_ptr + 15, 0);
20772 bus.write_long(item_text_handle, item_text_ptr);
20773 bus.write_bytes(item_text_ptr, b"Hello");
20774
20775 disp.te_set_text_contents(&mut bus, text_h, b"Hello");
20776 let te_ptr = bus.read_long(text_h);
20777 bus.write_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET, 1);
20778 bus.write_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET, 4);
20779 disp.dialog_items.insert(
20780 dialog_ptr,
20781 vec![DialogItem {
20782 item_type: 16,
20783 rect: (20, 30, 60, 110),
20784 text: "Hello".to_string(),
20785 resource_id: 0,
20786 proc_ptr: 0,
20787 sel_start: 1,
20788 sel_end: 4,
20789 }],
20790 );
20791
20792 cpu.write_reg(Register::A7, TEST_SP);
20793 bus.write_word(event_ptr, 3); bus.write_long(event_ptr + 2, u32::from(b'Y'));
20795 bus.write_long(event_ptr + 6, 0);
20796 bus.write_word(event_ptr + 10, 0);
20797 bus.write_word(event_ptr + 12, 0);
20798 bus.write_word(event_ptr + 14, 0);
20799 bus.write_word(TEST_SP + 12, 0xBEEF);
20800 bus.write_long(dialog_out_ptr, 0xDEAD_BEEF);
20801 bus.write_word(item_hit_ptr, 0xCAFE);
20802 bus.write_long(TEST_SP, item_hit_ptr);
20803 bus.write_long(TEST_SP + 4, dialog_out_ptr);
20804 bus.write_long(TEST_SP + 8, event_ptr);
20805 disp.dispatch_dialog(true, 0x180, &mut cpu, &mut bus)
20806 .unwrap()
20807 .unwrap();
20808 let keydown_result = bus.read_word(TEST_SP + 12);
20809 let keydown_dialog_out = bus.read_long(dialog_out_ptr);
20810 let keydown_item_hit = bus.read_word(item_hit_ptr);
20811 let keydown_stack_after = cpu.read_reg(Register::A7);
20812 let keydown_handle_bytes = text_handle_bytes(&bus, item_text_handle);
20813 let keydown_cached_text = disp.dialog_items[&dialog_ptr][0].text.clone();
20814 let keydown_item_selection = (
20815 disp.dialog_items[&dialog_ptr][0].sel_start,
20816 disp.dialog_items[&dialog_ptr][0].sel_end,
20817 );
20818 let keydown_te_text = TrapDispatcher::te_text_bytes(&bus, text_h);
20819 let keydown_te_length = bus.read_word(te_ptr + TrapDispatcher::TE_LENGTH_OFFSET);
20820 let keydown_te_selection = (
20821 bus.read_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET),
20822 bus.read_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET),
20823 );
20824
20825 cpu.write_reg(Register::A7, TEST_SP);
20826 bus.write_word(event_ptr, 5); bus.write_long(event_ptr + 2, 0x0000_0008); bus.write_word(TEST_SP + 12, 0xBEEF);
20829 bus.write_long(dialog_out_ptr, 0xDEAD_BEEF);
20830 bus.write_word(item_hit_ptr, 0xCAFE);
20831 bus.write_long(TEST_SP, item_hit_ptr);
20832 bus.write_long(TEST_SP + 4, dialog_out_ptr);
20833 bus.write_long(TEST_SP + 8, event_ptr);
20834 disp.dispatch_dialog(true, 0x180, &mut cpu, &mut bus)
20835 .unwrap()
20836 .unwrap();
20837 let autokey_result = bus.read_word(TEST_SP + 12);
20838 let autokey_dialog_out = bus.read_long(dialog_out_ptr);
20839 let autokey_item_hit = bus.read_word(item_hit_ptr);
20840 let autokey_stack_after = cpu.read_reg(Register::A7);
20841 let autokey_handle_bytes = text_handle_bytes(&bus, item_text_handle);
20842 let autokey_cached_text = disp.dialog_items[&dialog_ptr][0].text.clone();
20843 let autokey_item_selection = (
20844 disp.dialog_items[&dialog_ptr][0].sel_start,
20845 disp.dialog_items[&dialog_ptr][0].sel_end,
20846 );
20847 let autokey_te_text = TrapDispatcher::te_text_bytes(&bus, text_h);
20848 let autokey_te_length = bus.read_word(te_ptr + TrapDispatcher::TE_LENGTH_OFFSET);
20849 let autokey_te_selection = (
20850 bus.read_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET),
20851 bus.read_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET),
20852 );
20853
20854 DialogSelectEditTextKeySnapshot {
20855 keydown_result,
20856 keydown_dialog_out,
20857 keydown_item_hit,
20858 keydown_stack_after,
20859 keydown_handle_bytes,
20860 keydown_cached_text,
20861 keydown_item_selection,
20862 keydown_te_text,
20863 keydown_te_length,
20864 keydown_te_selection,
20865 autokey_result,
20866 autokey_dialog_out,
20867 autokey_item_hit,
20868 autokey_stack_after,
20869 autokey_handle_bytes,
20870 autokey_cached_text,
20871 autokey_item_selection,
20872 autokey_te_text,
20873 autokey_te_length,
20874 autokey_te_selection,
20875 }
20876 }
20877
20878 fn getsetditem_useritem_record_results_for_theme(
20879 theme_id: UiThemeId,
20880 ) -> DItemUserItemRecordSnapshot {
20881 let (mut disp, mut cpu, mut bus) = setup();
20882 disp.set_ui_theme_id(theme_id);
20883 let dialog_ptr = bus.alloc(170);
20884 let items_handle = bus.alloc(4);
20885 let ditl_ptr = bus.alloc(16);
20886 let initial_proc_ptr = 0x0012_3456u32;
20887 let new_proc_ptr = 0x00AB_CDEFu32;
20888 let set_box_ptr = bus.alloc(8);
20889 let get_box_ptr = bus.alloc(8);
20890 let get_item_ptr = bus.alloc(4);
20891 let get_type_ptr = bus.alloc(2);
20892
20893 bus.write_long(items_handle, ditl_ptr);
20894 bus.write_long(dialog_ptr + 156, items_handle);
20895 bus.write_word(ditl_ptr, 0); bus.write_long(ditl_ptr + 2, initial_proc_ptr);
20897 bus.write_word(ditl_ptr + 6, 12);
20898 bus.write_word(ditl_ptr + 8, 24);
20899 bus.write_word(ditl_ptr + 10, 36);
20900 bus.write_word(ditl_ptr + 12, 48);
20901 bus.write_byte(ditl_ptr + 14, 0);
20902 bus.write_byte(ditl_ptr + 15, 0);
20903 disp.dialog_items.insert(
20904 dialog_ptr,
20905 vec![DialogItem {
20906 item_type: 0,
20907 rect: (12, 24, 36, 48),
20908 text: String::new(),
20909 resource_id: 0,
20910 proc_ptr: initial_proc_ptr,
20911 sel_start: 0,
20912 sel_end: 0,
20913 }],
20914 );
20915
20916 cpu.write_reg(Register::A7, TEST_SP);
20917 bus.write_long(TEST_SP, get_box_ptr);
20918 bus.write_long(TEST_SP + 4, get_item_ptr);
20919 bus.write_long(TEST_SP + 8, get_type_ptr);
20920 bus.write_word(TEST_SP + 12, 1);
20921 bus.write_long(TEST_SP + 14, dialog_ptr);
20922 disp.dispatch_dialog(true, 0x18D, &mut cpu, &mut bus)
20923 .unwrap()
20924 .unwrap();
20925 let initial_get = DItemGetSnapshot {
20926 item_type: bus.read_word(get_type_ptr),
20927 item: bus.read_long(get_item_ptr),
20928 rect: (
20929 bus.read_word(get_box_ptr),
20930 bus.read_word(get_box_ptr + 2),
20931 bus.read_word(get_box_ptr + 4),
20932 bus.read_word(get_box_ptr + 6),
20933 ),
20934 stack_after: cpu.read_reg(Register::A7),
20935 };
20936
20937 cpu.write_reg(Register::A7, TEST_SP);
20938 bus.write_word(set_box_ptr, 50);
20939 bus.write_word(set_box_ptr + 2, 60);
20940 bus.write_word(set_box_ptr + 4, 70);
20941 bus.write_word(set_box_ptr + 6, 80);
20942 bus.write_long(TEST_SP, set_box_ptr);
20943 bus.write_long(TEST_SP + 4, new_proc_ptr);
20944 bus.write_word(TEST_SP + 8, 0);
20945 bus.write_word(TEST_SP + 10, 1);
20946 bus.write_long(TEST_SP + 12, dialog_ptr);
20947 disp.dispatch_dialog(true, 0x18E, &mut cpu, &mut bus)
20948 .unwrap()
20949 .unwrap();
20950 let set_stack_after = cpu.read_reg(Register::A7);
20951 let stored_item = disp
20952 .dialog_items
20953 .get(&dialog_ptr)
20954 .and_then(|items| items.first())
20955 .cloned()
20956 .unwrap();
20957
20958 cpu.write_reg(Register::A7, TEST_SP);
20959 bus.write_long(TEST_SP, get_box_ptr);
20960 bus.write_long(TEST_SP + 4, get_item_ptr);
20961 bus.write_long(TEST_SP + 8, get_type_ptr);
20962 bus.write_word(TEST_SP + 12, 1);
20963 bus.write_long(TEST_SP + 14, dialog_ptr);
20964 disp.dispatch_dialog(true, 0x18D, &mut cpu, &mut bus)
20965 .unwrap()
20966 .unwrap();
20967 let post_set_get = DItemGetSnapshot {
20968 item_type: bus.read_word(get_type_ptr),
20969 item: bus.read_long(get_item_ptr),
20970 rect: (
20971 bus.read_word(get_box_ptr),
20972 bus.read_word(get_box_ptr + 2),
20973 bus.read_word(get_box_ptr + 4),
20974 bus.read_word(get_box_ptr + 6),
20975 ),
20976 stack_after: cpu.read_reg(Register::A7),
20977 };
20978
20979 DItemUserItemRecordSnapshot {
20980 initial_get,
20981 set_stack_after,
20982 stored_type: stored_item.item_type,
20983 stored_proc_ptr: stored_item.proc_ptr,
20984 stored_rect: stored_item.rect,
20985 post_set_get,
20986 }
20987 }
20988
20989 fn get_ditem_snapshot_for_test(
20990 disp: &mut TrapDispatcher,
20991 cpu: &mut MockCpu,
20992 bus: &mut MacMemoryBus,
20993 dialog_ptr: u32,
20994 item_no: i16,
20995 box_ptr: u32,
20996 item_ptr: u32,
20997 type_ptr: u32,
20998 ) -> DItemGetSnapshot {
20999 cpu.write_reg(Register::A7, TEST_SP);
21000 bus.write_long(TEST_SP, box_ptr);
21001 bus.write_long(TEST_SP + 4, item_ptr);
21002 bus.write_long(TEST_SP + 8, type_ptr);
21003 bus.write_word(TEST_SP + 12, item_no as u16);
21004 bus.write_long(TEST_SP + 14, dialog_ptr);
21005 disp.dispatch_dialog(true, 0x18D, cpu, bus)
21006 .unwrap()
21007 .unwrap();
21008
21009 DItemGetSnapshot {
21010 item_type: bus.read_word(type_ptr),
21011 item: bus.read_long(item_ptr),
21012 rect: (
21013 bus.read_word(box_ptr),
21014 bus.read_word(box_ptr + 2),
21015 bus.read_word(box_ptr + 4),
21016 bus.read_word(box_ptr + 6),
21017 ),
21018 stack_after: cpu.read_reg(Register::A7),
21019 }
21020 }
21021
21022 fn write_control_record_for_ditem_test(
21023 bus: &mut MacMemoryBus,
21024 handle: u32,
21025 ctrl_ptr: u32,
21026 dialog_ptr: u32,
21027 rect: (i16, i16, i16, i16),
21028 value: i16,
21029 title: &[u8],
21030 ) {
21031 bus.write_long(handle, ctrl_ptr);
21032 bus.write_long(ctrl_ptr, 0);
21033 bus.write_long(ctrl_ptr + 4, dialog_ptr);
21034 bus.write_word(ctrl_ptr + 8, rect.0 as u16);
21035 bus.write_word(ctrl_ptr + 10, rect.1 as u16);
21036 bus.write_word(ctrl_ptr + 12, rect.2 as u16);
21037 bus.write_word(ctrl_ptr + 14, rect.3 as u16);
21038 bus.write_byte(ctrl_ptr + 16, 255);
21039 bus.write_byte(ctrl_ptr + 17, 0);
21040 bus.write_word(ctrl_ptr + 18, value as u16);
21041 bus.write_word(ctrl_ptr + 20, 0);
21042 bus.write_word(ctrl_ptr + 22, 1);
21043 bus.write_byte(ctrl_ptr + 40, title.len() as u8);
21044 bus.write_bytes(ctrl_ptr + 41, title);
21045 }
21046
21047 fn getsetditem_text_control_record_results_for_theme(
21048 theme_id: UiThemeId,
21049 ) -> DItemTextControlRecordSnapshot {
21050 let (mut disp, mut cpu, mut bus) = setup();
21051 disp.set_ui_theme_id(theme_id);
21052 let dialog_ptr = bus.alloc(170);
21053 let items_handle = bus.alloc(4);
21054 let ditl_ptr = bus.alloc(32);
21055 let old_text_handle = bus.alloc(4);
21056 let old_text_ptr = bus.alloc(3);
21057 let new_text_handle = bus.alloc(4);
21058 let new_text_ptr = bus.alloc(7);
21059 let old_control_handle = bus.alloc(4);
21060 let old_control_ptr = bus.alloc(44);
21061 let new_control_handle = bus.alloc(4);
21062 let new_control_ptr = bus.alloc(47);
21063 let text_set_box_ptr = bus.alloc(8);
21064 let control_set_box_ptr = bus.alloc(8);
21065 let get_box_ptr = bus.alloc(8);
21066 let get_item_ptr = bus.alloc(4);
21067 let get_type_ptr = bus.alloc(2);
21068 let text_entry = ditl_ptr + 2;
21069 let control_entry = ditl_ptr + 16;
21070
21071 bus.write_bytes(old_text_ptr, b"Old");
21072 bus.write_bytes(new_text_ptr, b"Updated");
21073 bus.write_long(old_text_handle, old_text_ptr);
21074 bus.write_long(new_text_handle, new_text_ptr);
21075 write_control_record_for_ditem_test(
21076 &mut bus,
21077 old_control_handle,
21078 old_control_ptr,
21079 dialog_ptr,
21080 (30, 40, 50, 140),
21081 1,
21082 b"OK",
21083 );
21084 write_control_record_for_ditem_test(
21085 &mut bus,
21086 new_control_handle,
21087 new_control_ptr,
21088 dialog_ptr,
21089 (1, 2, 3, 4),
21090 2,
21091 b"Apply",
21092 );
21093
21094 bus.write_long(items_handle, ditl_ptr);
21095 bus.write_long(dialog_ptr + 156, items_handle);
21096 bus.write_word(ditl_ptr, 1); bus.write_long(text_entry, old_text_handle);
21098 bus.write_word(text_entry + 4, 10);
21099 bus.write_word(text_entry + 6, 20);
21100 bus.write_word(text_entry + 8, 24);
21101 bus.write_word(text_entry + 10, 160);
21102 bus.write_byte(text_entry + 12, 16);
21103 bus.write_byte(text_entry + 13, 0);
21104 bus.write_long(control_entry, old_control_handle);
21105 bus.write_word(control_entry + 4, 30);
21106 bus.write_word(control_entry + 6, 40);
21107 bus.write_word(control_entry + 8, 50);
21108 bus.write_word(control_entry + 10, 140);
21109 bus.write_byte(control_entry + 12, 4);
21110 bus.write_byte(control_entry + 13, 2);
21111 bus.write_bytes(control_entry + 14, b"OK");
21112
21113 disp.dialog_items.insert(
21114 dialog_ptr,
21115 vec![
21116 DialogItem {
21117 item_type: 16,
21118 rect: (10, 20, 24, 160),
21119 text: "Old".to_string(),
21120 resource_id: 0,
21121 proc_ptr: 0,
21122 sel_start: 0,
21123 sel_end: 0,
21124 },
21125 DialogItem {
21126 item_type: 4,
21127 rect: (30, 40, 50, 140),
21128 text: "OK".to_string(),
21129 resource_id: 0,
21130 proc_ptr: 0,
21131 sel_start: 0,
21132 sel_end: 0,
21133 },
21134 ],
21135 );
21136 disp.dialog_item_handles
21137 .insert(old_text_handle, (dialog_ptr, 0));
21138 disp.dialog_control_handles
21139 .insert(old_control_handle, (dialog_ptr, 2));
21140 disp.dialog_control_values.insert((dialog_ptr, 2), 1);
21141
21142 let text_initial_get = get_ditem_snapshot_for_test(
21143 &mut disp,
21144 &mut cpu,
21145 &mut bus,
21146 dialog_ptr,
21147 1,
21148 get_box_ptr,
21149 get_item_ptr,
21150 get_type_ptr,
21151 );
21152 let control_initial_get = get_ditem_snapshot_for_test(
21153 &mut disp,
21154 &mut cpu,
21155 &mut bus,
21156 dialog_ptr,
21157 2,
21158 get_box_ptr,
21159 get_item_ptr,
21160 get_type_ptr,
21161 );
21162
21163 cpu.write_reg(Register::A7, TEST_SP);
21164 bus.write_word(text_set_box_ptr, 12);
21165 bus.write_word(text_set_box_ptr + 2, 22);
21166 bus.write_word(text_set_box_ptr + 4, 28);
21167 bus.write_word(text_set_box_ptr + 6, 168);
21168 bus.write_long(TEST_SP, text_set_box_ptr);
21169 bus.write_long(TEST_SP + 4, new_text_handle);
21170 bus.write_word(TEST_SP + 8, 16);
21171 bus.write_word(TEST_SP + 10, 1);
21172 bus.write_long(TEST_SP + 12, dialog_ptr);
21173 disp.dispatch_dialog(true, 0x18E, &mut cpu, &mut bus)
21174 .unwrap()
21175 .unwrap();
21176 let text_set_stack_after = cpu.read_reg(Register::A7);
21177 let text_stored_item = disp.dialog_items.get(&dialog_ptr).unwrap()[0].clone();
21178 let text_post_set_get = get_ditem_snapshot_for_test(
21179 &mut disp,
21180 &mut cpu,
21181 &mut bus,
21182 dialog_ptr,
21183 1,
21184 get_box_ptr,
21185 get_item_ptr,
21186 get_type_ptr,
21187 );
21188
21189 cpu.write_reg(Register::A7, TEST_SP);
21190 bus.write_word(control_set_box_ptr, 42);
21191 bus.write_word(control_set_box_ptr + 2, 52);
21192 bus.write_word(control_set_box_ptr + 4, 62);
21193 bus.write_word(control_set_box_ptr + 6, 172);
21194 bus.write_long(TEST_SP, control_set_box_ptr);
21195 bus.write_long(TEST_SP + 4, new_control_handle);
21196 bus.write_word(TEST_SP + 8, 5);
21197 bus.write_word(TEST_SP + 10, 2);
21198 bus.write_long(TEST_SP + 12, dialog_ptr);
21199 disp.dispatch_dialog(true, 0x18E, &mut cpu, &mut bus)
21200 .unwrap()
21201 .unwrap();
21202 let control_set_stack_after = cpu.read_reg(Register::A7);
21203 let control_stored_item = disp.dialog_items.get(&dialog_ptr).unwrap()[1].clone();
21204 let control_post_set_get = get_ditem_snapshot_for_test(
21205 &mut disp,
21206 &mut cpu,
21207 &mut bus,
21208 dialog_ptr,
21209 2,
21210 get_box_ptr,
21211 get_item_ptr,
21212 get_type_ptr,
21213 );
21214
21215 DItemTextControlRecordSnapshot {
21216 text_initial_get,
21217 text_set_stack_after,
21218 text_old_handle_mapped_after: disp.dialog_item_handles.contains_key(&old_text_handle),
21219 text_new_handle_map_value_after: disp
21220 .dialog_item_handles
21221 .get(&new_text_handle)
21222 .copied(),
21223 text_ditl_handle_after: bus.read_long(text_entry),
21224 text_ditl_rect_after: (
21225 bus.read_word(text_entry + 4),
21226 bus.read_word(text_entry + 6),
21227 bus.read_word(text_entry + 8),
21228 bus.read_word(text_entry + 10),
21229 ),
21230 text_ditl_type_after: bus.read_byte(text_entry + 12),
21231 text_stored_type_after: text_stored_item.item_type,
21232 text_stored_rect_after: text_stored_item.rect,
21233 text_cached_text_after: text_stored_item.text,
21234 text_handle_bytes_after: text_handle_bytes(&bus, new_text_handle),
21235 text_post_set_get,
21236 control_initial_get,
21237 control_set_stack_after,
21238 control_old_handle_mapped_after: disp
21239 .dialog_control_handles
21240 .contains_key(&old_control_handle),
21241 control_new_handle_map_value_after: disp
21242 .dialog_control_handles
21243 .get(&new_control_handle)
21244 .copied(),
21245 control_ditl_handle_after: bus.read_long(control_entry),
21246 control_ditl_rect_after: (
21247 bus.read_word(control_entry + 4),
21248 bus.read_word(control_entry + 6),
21249 bus.read_word(control_entry + 8),
21250 bus.read_word(control_entry + 10),
21251 ),
21252 control_ditl_type_after: bus.read_byte(control_entry + 12),
21253 control_stored_type_after: control_stored_item.item_type,
21254 control_stored_rect_after: control_stored_item.rect,
21255 control_record_rect_after: (
21256 bus.read_word(new_control_ptr + 8),
21257 bus.read_word(new_control_ptr + 10),
21258 bus.read_word(new_control_ptr + 12),
21259 bus.read_word(new_control_ptr + 14),
21260 ),
21261 control_value_after: disp.dialog_control_values.get(&(dialog_ptr, 2)).copied(),
21262 control_post_set_get,
21263 }
21264 }
21265
21266 fn hide_show_ditem_visibility_results_for_theme(
21267 theme_id: UiThemeId,
21268 ) -> DItemVisibilitySnapshot {
21269 let (mut disp, mut cpu, mut bus) = setup();
21270 disp.set_ui_theme_id(theme_id);
21271 let dialog_ptr = bus.alloc(170);
21272 let items_handle = bus.alloc(4);
21273 let ditl_ptr = bus.alloc(18);
21274 let get_box_ptr = bus.alloc(8);
21275 let get_item_ptr = bus.alloc(4);
21276 let get_type_ptr = bus.alloc(2);
21277
21278 bus.write_long(items_handle, ditl_ptr);
21279 bus.write_long(dialog_ptr + 156, items_handle);
21280 bus.write_word(ditl_ptr, 0); bus.write_long(ditl_ptr + 2, 0);
21282 bus.write_word(ditl_ptr + 6, 10);
21283 bus.write_word(ditl_ptr + 8, 20);
21284 bus.write_word(ditl_ptr + 10, 30);
21285 bus.write_word(ditl_ptr + 12, 120);
21286 bus.write_byte(ditl_ptr + 14, 4);
21287 bus.write_byte(ditl_ptr + 15, 2);
21288 bus.write_bytes(ditl_ptr + 16, b"OK");
21289 disp.dialog_items.insert(
21290 dialog_ptr,
21291 vec![DialogItem {
21292 item_type: 4,
21293 rect: (10, 20, 30, 120),
21294 text: "OK".into(),
21295 resource_id: 0,
21296 proc_ptr: 0,
21297 sel_start: 0,
21298 sel_end: 0,
21299 }],
21300 );
21301
21302 cpu.write_reg(Register::A7, TEST_SP);
21303 bus.write_long(TEST_SP, get_box_ptr);
21304 bus.write_long(TEST_SP + 4, get_item_ptr);
21305 bus.write_long(TEST_SP + 8, get_type_ptr);
21306 bus.write_word(TEST_SP + 12, 1);
21307 bus.write_long(TEST_SP + 14, dialog_ptr);
21308 disp.dispatch_dialog(true, 0x18D, &mut cpu, &mut bus)
21309 .unwrap()
21310 .unwrap();
21311 let initial_get = DItemGetSnapshot {
21312 item_type: bus.read_word(get_type_ptr),
21313 item: bus.read_long(get_item_ptr),
21314 rect: (
21315 bus.read_word(get_box_ptr),
21316 bus.read_word(get_box_ptr + 2),
21317 bus.read_word(get_box_ptr + 4),
21318 bus.read_word(get_box_ptr + 6),
21319 ),
21320 stack_after: cpu.read_reg(Register::A7),
21321 };
21322 assert_ne!(initial_get.item, 0);
21323 let ctrl_ptr = bus.read_long(initial_get.item);
21324 assert_ne!(ctrl_ptr, 0);
21325
21326 cpu.write_reg(Register::A7, TEST_SP);
21327 bus.write_word(TEST_SP, 1);
21328 bus.write_long(TEST_SP + 2, dialog_ptr);
21329 disp.dispatch_dialog(true, 0x027, &mut cpu, &mut bus)
21330 .unwrap()
21331 .unwrap();
21332 let hide_stack_after = cpu.read_reg(Register::A7);
21333 let hidden_item_rect = disp.dialog_items.get(&dialog_ptr).unwrap()[0].rect;
21334 let hidden_saved_rect = disp.hidden_dialog_item_rects.get(&(dialog_ptr, 1)).copied();
21335 let hidden_control_rect = (
21336 bus.read_word(ctrl_ptr + 8),
21337 bus.read_word(ctrl_ptr + 10),
21338 bus.read_word(ctrl_ptr + 12),
21339 bus.read_word(ctrl_ptr + 14),
21340 );
21341
21342 cpu.write_reg(Register::A7, TEST_SP);
21343 bus.write_long(TEST_SP, get_box_ptr);
21344 bus.write_long(TEST_SP + 4, get_item_ptr);
21345 bus.write_long(TEST_SP + 8, get_type_ptr);
21346 bus.write_word(TEST_SP + 12, 1);
21347 bus.write_long(TEST_SP + 14, dialog_ptr);
21348 disp.dispatch_dialog(true, 0x18D, &mut cpu, &mut bus)
21349 .unwrap()
21350 .unwrap();
21351 let hidden_get = DItemGetSnapshot {
21352 item_type: bus.read_word(get_type_ptr),
21353 item: bus.read_long(get_item_ptr),
21354 rect: (
21355 bus.read_word(get_box_ptr),
21356 bus.read_word(get_box_ptr + 2),
21357 bus.read_word(get_box_ptr + 4),
21358 bus.read_word(get_box_ptr + 6),
21359 ),
21360 stack_after: cpu.read_reg(Register::A7),
21361 };
21362
21363 cpu.write_reg(Register::A7, TEST_SP);
21364 bus.write_word(TEST_SP, 20);
21365 bus.write_word(TEST_SP + 2, 40);
21366 bus.write_long(TEST_SP + 4, dialog_ptr);
21367 bus.write_word(TEST_SP + 8, 0xBEEF);
21368 disp.dispatch_dialog(true, 0x184, &mut cpu, &mut bus)
21369 .unwrap()
21370 .unwrap();
21371 let hidden_find = (
21372 bus.read_word(TEST_SP + 8) as i16,
21373 cpu.read_reg(Register::A7),
21374 );
21375
21376 cpu.write_reg(Register::A7, TEST_SP);
21377 bus.write_word(TEST_SP, 1);
21378 bus.write_long(TEST_SP + 2, dialog_ptr);
21379 disp.dispatch_dialog(true, 0x028, &mut cpu, &mut bus)
21380 .unwrap()
21381 .unwrap();
21382 let show_stack_after = cpu.read_reg(Register::A7);
21383 let restored_item_rect = disp.dialog_items.get(&dialog_ptr).unwrap()[0].rect;
21384 let restored_saved_rect = disp.hidden_dialog_item_rects.get(&(dialog_ptr, 1)).copied();
21385 let restored_control_rect = (
21386 bus.read_word(ctrl_ptr + 8),
21387 bus.read_word(ctrl_ptr + 10),
21388 bus.read_word(ctrl_ptr + 12),
21389 bus.read_word(ctrl_ptr + 14),
21390 );
21391
21392 cpu.write_reg(Register::A7, TEST_SP);
21393 bus.write_long(TEST_SP, get_box_ptr);
21394 bus.write_long(TEST_SP + 4, get_item_ptr);
21395 bus.write_long(TEST_SP + 8, get_type_ptr);
21396 bus.write_word(TEST_SP + 12, 1);
21397 bus.write_long(TEST_SP + 14, dialog_ptr);
21398 disp.dispatch_dialog(true, 0x18D, &mut cpu, &mut bus)
21399 .unwrap()
21400 .unwrap();
21401 let restored_get = DItemGetSnapshot {
21402 item_type: bus.read_word(get_type_ptr),
21403 item: bus.read_long(get_item_ptr),
21404 rect: (
21405 bus.read_word(get_box_ptr),
21406 bus.read_word(get_box_ptr + 2),
21407 bus.read_word(get_box_ptr + 4),
21408 bus.read_word(get_box_ptr + 6),
21409 ),
21410 stack_after: cpu.read_reg(Register::A7),
21411 };
21412
21413 cpu.write_reg(Register::A7, TEST_SP);
21414 bus.write_word(TEST_SP, 20);
21415 bus.write_word(TEST_SP + 2, 40);
21416 bus.write_long(TEST_SP + 4, dialog_ptr);
21417 bus.write_word(TEST_SP + 8, 0xBEEF);
21418 disp.dispatch_dialog(true, 0x184, &mut cpu, &mut bus)
21419 .unwrap()
21420 .unwrap();
21421 let restored_find = (
21422 bus.read_word(TEST_SP + 8) as i16,
21423 cpu.read_reg(Register::A7),
21424 );
21425
21426 DItemVisibilitySnapshot {
21427 initial_get,
21428 hide_stack_after,
21429 hidden_item_rect,
21430 hidden_saved_rect,
21431 hidden_control_rect,
21432 hidden_get,
21433 hidden_find,
21434 show_stack_after,
21435 restored_item_rect,
21436 restored_saved_rect,
21437 restored_control_rect,
21438 restored_get,
21439 restored_find,
21440 }
21441 }
21442
21443 fn drawdialog_useritem_pixel_results_for_theme(theme_id: UiThemeId) -> (u8, u32, u32) {
21444 let (mut disp, mut cpu, mut bus) = setup();
21445 disp.set_ui_theme_id(theme_id);
21446 let dialog_ptr = bus.alloc(170);
21447 let (screen_base, row_bytes, _w, _h, pixel_size) = disp.screen_mode;
21448 assert_eq!(pixel_size, 8);
21449
21450 bus.write_word(dialog_ptr + 8, 0);
21451 bus.write_word(dialog_ptr + 10, 0);
21452 bus.write_word(dialog_ptr + 16, 0);
21453 bus.write_word(dialog_ptr + 18, 0);
21454 bus.write_word(dialog_ptr + 20, 40);
21455 bus.write_word(dialog_ptr + 22, 80);
21456 disp.dialog_items.insert(
21457 dialog_ptr,
21458 vec![
21459 DialogItem {
21460 item_type: 0, rect: (8, 8, 20, 32),
21462 text: String::new(),
21463 resource_id: 0,
21464 proc_ptr: 0x00C0_FFEE,
21465 sel_start: 0,
21466 sel_end: 0,
21467 },
21468 DialogItem {
21469 item_type: 4,
21470 rect: (24, 8, 36, 40),
21471 text: "OK".to_string(),
21472 resource_id: 0,
21473 proc_ptr: 0,
21474 sel_start: 0,
21475 sel_end: 0,
21476 },
21477 ],
21478 );
21479
21480 let user_x = 10u32;
21481 let user_y = 10u32;
21482 let user_pixel = screen_base + user_y * row_bytes + user_x;
21483 bus.write_byte(user_pixel, 0xA5);
21484 cpu.write_reg(Register::A7, TEST_SP);
21485 bus.write_long(TEST_SP, dialog_ptr);
21486 bus.write_long(TEST_SP + 4, 0xCAFE_BABE);
21487
21488 disp.dispatch_dialog(true, 0x181, &mut cpu, &mut bus)
21489 .unwrap()
21490 .unwrap();
21491
21492 (
21493 bus.read_byte(user_pixel),
21494 cpu.read_reg(Register::A7),
21495 bus.read_long(TEST_SP + 4),
21496 )
21497 }
21498
21499 fn monochrome_icon_resource_with_points(points: &[(u8, u8)]) -> Vec<u8> {
21500 let mut icon = vec![0u8; 128];
21501 for &(x, y) in points {
21502 let byte = y as usize * 4 + x as usize / 8;
21503 icon[byte] |= 0x80 >> (x & 7);
21504 }
21505 icon
21506 }
21507
21508 fn write_be_word(data: &mut [u8], offset: usize, value: u16) {
21509 data[offset] = (value >> 8) as u8;
21510 data[offset + 1] = value as u8;
21511 }
21512
21513 fn cicn_resource_with_points(width: u16, height: u16, points: &[(u8, u8, u8)]) -> Vec<u8> {
21514 let pixel_row_bytes = u32::from(width);
21515 let mask_row_bytes = u32::from(width).div_ceil(8);
21516 let mask_size = mask_row_bytes * u32::from(height);
21517 let bmap_size = mask_row_bytes * u32::from(height);
21518 let ctab_size = 16u32;
21519 let pixel_size = pixel_row_bytes * u32::from(height);
21520 let bmap_offset = 82 + mask_size as usize;
21521 let ctab_offset = bmap_offset + bmap_size as usize;
21522 let pixel_offset = ctab_offset + ctab_size as usize;
21523 let mut data = vec![0u8; pixel_offset + pixel_size as usize];
21524
21525 write_be_word(&mut data, 4, pixel_row_bytes as u16);
21526 write_be_word(&mut data, 10, height);
21527 write_be_word(&mut data, 12, width);
21528 write_be_word(&mut data, 32, 8);
21529
21530 write_be_word(&mut data, 54, mask_row_bytes as u16);
21531 write_be_word(&mut data, 60, height);
21532 write_be_word(&mut data, 62, width);
21533
21534 write_be_word(&mut data, 68, mask_row_bytes as u16);
21535 write_be_word(&mut data, 74, height);
21536 write_be_word(&mut data, 76, width);
21537
21538 write_be_word(&mut data, ctab_offset + 6, 0);
21539
21540 for &(x, y, pixel) in points {
21541 let mask_row = 82 + y as usize * mask_row_bytes as usize;
21542 data[mask_row + x as usize / 8] |= 0x80 >> (x & 7);
21543 let pixel_row = pixel_offset + y as usize * pixel_row_bytes as usize;
21544 data[pixel_row + x as usize] = pixel;
21545 }
21546 data
21547 }
21548
21549 fn drawdialog_icon_item_pixel_results_for_theme(theme_id: UiThemeId) -> ([u8; 3], u32, u32) {
21550 let (mut disp, mut cpu, mut bus) = setup();
21551 disp.set_ui_theme_id(theme_id);
21552 let dialog_ptr = bus.alloc(170);
21553 let (screen_base, row_bytes, _w, _h, pixel_size) = disp.screen_mode;
21554 assert_eq!(pixel_size, 8);
21555
21556 bus.write_word(dialog_ptr + 8, 0);
21557 bus.write_word(dialog_ptr + 10, 0);
21558 bus.write_word(dialog_ptr + 16, 0);
21559 bus.write_word(dialog_ptr + 18, 0);
21560 bus.write_word(dialog_ptr + 20, 56);
21561 bus.write_word(dialog_ptr + 22, 88);
21562 disp.install_test_resource(
21563 &mut bus,
21564 *b"ICON",
21565 421,
21566 &monochrome_icon_resource_with_points(&[(0, 0), (15, 15), (31, 31)]),
21567 );
21568 disp.dialog_items.insert(
21569 dialog_ptr,
21570 vec![
21571 DialogItem {
21572 item_type: 32, rect: (8, 8, 40, 40),
21574 text: String::new(),
21575 resource_id: 421,
21576 proc_ptr: 0,
21577 sel_start: 0,
21578 sel_end: 0,
21579 },
21580 DialogItem {
21581 item_type: 4,
21582 rect: (44, 8, 54, 40),
21583 text: "OK".to_string(),
21584 resource_id: 0,
21585 proc_ptr: 0,
21586 sel_start: 0,
21587 sel_end: 0,
21588 },
21589 ],
21590 );
21591
21592 for (x, y) in [(8u32, 8u32), (23, 23), (39, 39)] {
21593 bus.write_byte(screen_base + y * row_bytes + x, 0x42);
21594 }
21595 cpu.write_reg(Register::A7, TEST_SP);
21596 bus.write_long(TEST_SP, dialog_ptr);
21597 bus.write_long(TEST_SP + 4, 0xCAFE_BABE);
21598
21599 disp.dispatch_dialog(true, 0x181, &mut cpu, &mut bus)
21600 .unwrap()
21601 .unwrap();
21602
21603 (
21604 [
21605 bus.read_byte(screen_base + 8 * row_bytes + 8),
21606 bus.read_byte(screen_base + 23 * row_bytes + 23),
21607 bus.read_byte(screen_base + 39 * row_bytes + 39),
21608 ],
21609 cpu.read_reg(Register::A7),
21610 bus.read_long(TEST_SP + 4),
21611 )
21612 }
21613
21614 fn drawdialog_cicn_item_pixel_results_for_theme(theme_id: UiThemeId) -> ([u8; 3], u32, u32) {
21615 let (mut disp, mut cpu, mut bus) = setup();
21616 disp.set_ui_theme_id(theme_id);
21617 let dialog_ptr = bus.alloc(170);
21618 let (screen_base, row_bytes, _w, _h, pixel_size) = disp.screen_mode;
21619 assert_eq!(pixel_size, 8);
21620
21621 bus.write_word(dialog_ptr + 8, 0);
21622 bus.write_word(dialog_ptr + 10, 0);
21623 bus.write_word(dialog_ptr + 16, 0);
21624 bus.write_word(dialog_ptr + 18, 0);
21625 bus.write_word(dialog_ptr + 20, 56);
21626 bus.write_word(dialog_ptr + 22, 88);
21627 let sample_points = [(0, 0), (15, 15), (31, 31)];
21628 disp.install_test_resource(
21629 &mut bus,
21630 *b"ICON",
21631 422,
21632 &monochrome_icon_resource_with_points(&sample_points),
21633 );
21634 disp.install_test_resource(
21635 &mut bus,
21636 *b"cicn",
21637 422,
21638 &cicn_resource_with_points(32, 32, &[(0, 0, 0x33), (15, 15, 0x55), (31, 31, 0x77)]),
21639 );
21640 disp.dialog_items.insert(
21641 dialog_ptr,
21642 vec![
21643 DialogItem {
21644 item_type: 32, rect: (8, 8, 40, 40),
21646 text: String::new(),
21647 resource_id: 422,
21648 proc_ptr: 0,
21649 sel_start: 0,
21650 sel_end: 0,
21651 },
21652 DialogItem {
21653 item_type: 4,
21654 rect: (44, 8, 54, 40),
21655 text: "OK".to_string(),
21656 resource_id: 0,
21657 proc_ptr: 0,
21658 sel_start: 0,
21659 sel_end: 0,
21660 },
21661 ],
21662 );
21663
21664 for (x, y) in [(8u32, 8u32), (23, 23), (39, 39)] {
21665 bus.write_byte(screen_base + y * row_bytes + x, 0x42);
21666 }
21667 cpu.write_reg(Register::A7, TEST_SP);
21668 bus.write_long(TEST_SP, dialog_ptr);
21669 bus.write_long(TEST_SP + 4, 0xCAFE_BABE);
21670
21671 disp.dispatch_dialog(true, 0x181, &mut cpu, &mut bus)
21672 .unwrap()
21673 .unwrap();
21674
21675 (
21676 [
21677 bus.read_byte(screen_base + 8 * row_bytes + 8),
21678 bus.read_byte(screen_base + 23 * row_bytes + 23),
21679 bus.read_byte(screen_base + 39 * row_bytes + 39),
21680 ],
21681 cpu.read_reg(Register::A7),
21682 bus.read_long(TEST_SP + 4),
21683 )
21684 }
21685
21686 fn solid_fill_pict_resource(width: i16, height: i16) -> Vec<u8> {
21687 let mut data = Vec::new();
21688 data.extend_from_slice(&0u16.to_be_bytes()); for value in [0i16, 0, height, width] {
21690 data.extend_from_slice(&(value as u16).to_be_bytes());
21691 }
21692 data.push(0x11); data.push(0x01); data.push(0x0A); data.extend_from_slice(&[0xFF; 8]);
21696 data.push(0x34); for value in [0i16, 0, height, width] {
21698 data.extend_from_slice(&(value as u16).to_be_bytes());
21699 }
21700 data.push(0xFF); let size = data.len() as u16;
21702 data[0..2].copy_from_slice(&size.to_be_bytes());
21703 data
21704 }
21705
21706 fn drawdialog_picture_item_pixel_results_for_theme(theme_id: UiThemeId) -> ([u8; 3], u32, u32) {
21707 let (mut disp, mut cpu, mut bus) = setup();
21708 disp.set_ui_theme_id(theme_id);
21709 let dialog_ptr = bus.alloc(170);
21710 let (screen_base, row_bytes, _w, _h, pixel_size) = disp.screen_mode;
21711 assert_eq!(pixel_size, 8);
21712
21713 bus.write_word(dialog_ptr + 8, 0);
21714 bus.write_word(dialog_ptr + 10, 0);
21715 bus.write_word(dialog_ptr + 16, 0);
21716 bus.write_word(dialog_ptr + 18, 0);
21717 bus.write_word(dialog_ptr + 20, 48);
21718 bus.write_word(dialog_ptr + 22, 80);
21719 disp.install_test_resource(&mut bus, *b"PICT", 420, &solid_fill_pict_resource(16, 16));
21720 disp.dialog_items.insert(
21721 dialog_ptr,
21722 vec![
21723 DialogItem {
21724 item_type: 64, rect: (8, 8, 24, 24),
21726 text: String::new(),
21727 resource_id: 420,
21728 proc_ptr: 0,
21729 sel_start: 0,
21730 sel_end: 0,
21731 },
21732 DialogItem {
21733 item_type: 4,
21734 rect: (30, 8, 42, 40),
21735 text: "OK".to_string(),
21736 resource_id: 0,
21737 proc_ptr: 0,
21738 sel_start: 0,
21739 sel_end: 0,
21740 },
21741 ],
21742 );
21743
21744 for (x, y) in [(9u32, 9u32), (16, 16), (22, 22)] {
21745 bus.write_byte(screen_base + y * row_bytes + x, 0x42);
21746 }
21747 cpu.write_reg(Register::A7, TEST_SP);
21748 bus.write_long(TEST_SP, dialog_ptr);
21749 bus.write_long(TEST_SP + 4, 0xCAFE_BABE);
21750
21751 disp.dispatch_dialog(true, 0x181, &mut cpu, &mut bus)
21752 .unwrap()
21753 .unwrap();
21754
21755 (
21756 [
21757 bus.read_byte(screen_base + 9 * row_bytes + 9),
21758 bus.read_byte(screen_base + 16 * row_bytes + 16),
21759 bus.read_byte(screen_base + 22 * row_bytes + 22),
21760 ],
21761 cpu.read_reg(Register::A7),
21762 bus.read_long(TEST_SP + 4),
21763 )
21764 }
21765
21766 fn dialog_tracking_state_for_test(dialog_ptr: u32) -> DialogTrackingState {
21767 DialogTrackingState {
21768 dialog_ptr,
21769 bounds: (0, 0, 0, 0),
21770 title: String::new(),
21771 proc_id: 0,
21772 items: Vec::new(),
21773 default_item: 1,
21774 cancel_item: 2,
21775 edit_text: String::new(),
21776 edit_item: 0,
21777 saved_pixels: Vec::new(),
21778 stack_ptr: TEST_SP,
21779 item_hit_ptr: 0,
21780 rendered_pixels: Vec::new(),
21781 flash_remaining: 0,
21782 flash_delay: 0,
21783 flash_item: 0,
21784 edit_text_modified: false,
21785 draw_proc_queue: VecDeque::new(),
21786 draw_procs_done: true,
21787 rendered_pixels_final: true,
21788 filter_proc: 0,
21789 game_managed: false,
21790 last_filter_event: None,
21791 popup_draws: Vec::new(),
21792 active_popup: None,
21793 active_button: None,
21794 active_user_item: None,
21795 }
21796 }
21797
21798 fn dialog_lifecycle_cleanup_results_for_theme(
21799 theme_id: UiThemeId,
21800 ) -> DialogLifecycleCleanupSnapshot {
21801 let bounds = (100, 100, 150, 200);
21802 let previous_bounds = (0, 0, 342, 512);
21803 let saved_pixels = vec![0x33; 66 * 116];
21804
21805 let (mut close_disp, mut close_cpu, mut close_bus) = setup();
21806 close_disp.set_ui_theme_id(theme_id);
21807 close_disp.set_screen_mode_for_test(0x300000, 640, 640, 480, 8);
21808 let close_dialog_ptr = close_bus.alloc(170);
21809 let close_previous_window = close_bus.alloc(170);
21810 seed_window_regions(&mut close_bus, close_dialog_ptr, bounds);
21811 seed_window_regions(&mut close_bus, close_previous_window, previous_bounds);
21812 close_bus.write_word(close_dialog_ptr + 108, 2);
21813 close_disp.front_window = close_dialog_ptr;
21814 close_disp.current_port = close_dialog_ptr;
21815 close_disp.window_bounds = bounds;
21816 close_disp.window_proc_id = 2;
21817 close_disp.window_list = vec![close_dialog_ptr, close_previous_window];
21818 close_disp.window_stack.push((
21819 close_previous_window,
21820 previous_bounds,
21821 0,
21822 "Previous".to_string(),
21823 ));
21824 close_disp.dialog_items.insert(
21825 close_dialog_ptr,
21826 vec![DialogItem {
21827 item_type: 4,
21828 rect: (20, 40, 40, 100),
21829 text: "OK".to_string(),
21830 ..Default::default()
21831 }],
21832 );
21833 close_disp.dialog_tracking = Some(dialog_tracking_state_for_test(close_dialog_ptr));
21834 close_disp.retained_modal_dialog_click = Some(RetainedModalDialogClickState {
21835 dialog_ptr: close_dialog_ptr,
21836 item_no: 1,
21837 rect: (20, 40, 40, 100),
21838 title: "OK".to_string(),
21839 is_default: true,
21840 highlighted: true,
21841 delivered_to_app: false,
21842 });
21843 close_disp.dialog_visible_snapshots.insert(
21844 close_dialog_ptr,
21845 PersistentDialogSnapshot {
21846 bounds,
21847 pixels: saved_pixels.clone(),
21848 },
21849 );
21850 close_disp.dialog_modal_entered.insert(close_dialog_ptr);
21851 close_disp
21852 .dialog_saved_pixels
21853 .insert(close_dialog_ptr, saved_pixels.clone());
21854 close_bus.write_long(crate::memory::globals::addr::THE_PORT, close_dialog_ptr);
21855 let close_global_ptr = close_bus.read_long(close_cpu.read_reg(Register::A5));
21856 close_bus.write_long(close_global_ptr, close_dialog_ptr);
21857 close_cpu.write_reg(Register::A7, TEST_SP);
21858 close_bus.write_long(TEST_SP, close_dialog_ptr);
21859 close_disp
21860 .dispatch_dialog(true, 0x182, &mut close_cpu, &mut close_bus)
21861 .unwrap()
21862 .unwrap();
21863
21864 let close_stack_after = close_cpu.read_reg(Register::A7);
21865 let close_tracking_cleared = close_disp.dialog_tracking.is_none();
21866 let close_retained_click_cleared = close_disp.retained_modal_dialog_click.is_none();
21867 let close_visible_snapshot_cleared = !close_disp
21868 .dialog_visible_snapshots
21869 .contains_key(&close_dialog_ptr);
21870 let close_modal_entered_cleared =
21871 !close_disp.dialog_modal_entered.contains(&close_dialog_ptr);
21872 let close_saved_pixels_cleared = !close_disp
21873 .dialog_saved_pixels
21874 .contains_key(&close_dialog_ptr);
21875 let close_window_list_contains_dialog = close_disp.window_list.contains(&close_dialog_ptr);
21876 let close_front_window_after = close_disp.front_window;
21877 let close_the_port_after = close_bus.read_long(crate::memory::globals::addr::THE_PORT);
21878 let close_current_port_after = close_disp.current_port;
21879 let close_update_event_for_previous_window = close_disp
21880 .event_queue
21881 .iter()
21882 .any(|event| event.what == 6 && event.message == close_previous_window);
21883 let close_dialog_items_present_after =
21884 close_disp.dialog_items.contains_key(&close_dialog_ptr);
21885
21886 let (mut dispose_disp, mut dispose_cpu, mut dispose_bus) = setup();
21887 dispose_disp.set_ui_theme_id(theme_id);
21888 dispose_disp.set_screen_mode_for_test(0x300000, 640, 640, 480, 8);
21889 let dispose_dialog_ptr = dispose_bus.alloc(170);
21890 let dispose_other_dialog_ptr = dispose_bus.alloc(170);
21891 let dispose_previous_window = dispose_bus.alloc(170);
21892 let dispose_text_handle = dispose_bus.alloc(4);
21893 let dispose_other_text_handle = dispose_bus.alloc(4);
21894 let dispose_ctrl_handle = dispose_bus.alloc(4);
21895 let dispose_other_ctrl_handle = dispose_bus.alloc(4);
21896 seed_window_regions(&mut dispose_bus, dispose_dialog_ptr, bounds);
21897 seed_window_regions(&mut dispose_bus, dispose_previous_window, previous_bounds);
21898 dispose_bus.write_word(dispose_dialog_ptr + 108, 2);
21899 dispose_disp.front_window = dispose_dialog_ptr;
21900 dispose_disp.current_port = dispose_dialog_ptr;
21901 dispose_disp.window_bounds = bounds;
21902 dispose_disp.window_proc_id = 2;
21903 dispose_disp.window_list = vec![
21904 dispose_dialog_ptr,
21905 dispose_previous_window,
21906 dispose_other_dialog_ptr,
21907 ];
21908 dispose_disp.window_stack.push((
21909 dispose_previous_window,
21910 previous_bounds,
21911 0,
21912 "Previous".to_string(),
21913 ));
21914 dispose_disp
21915 .dialog_items
21916 .insert(dispose_dialog_ptr, vec![DialogItem::default()]);
21917 dispose_disp
21918 .dialog_items
21919 .insert(dispose_other_dialog_ptr, vec![DialogItem::default()]);
21920 dispose_disp.dialog_tracking = Some(dialog_tracking_state_for_test(dispose_dialog_ptr));
21921 dispose_disp.retained_modal_dialog_click = Some(RetainedModalDialogClickState {
21922 dialog_ptr: dispose_dialog_ptr,
21923 item_no: 1,
21924 rect: (20, 40, 40, 100),
21925 title: "OK".to_string(),
21926 is_default: true,
21927 highlighted: true,
21928 delivered_to_app: false,
21929 });
21930 dispose_disp.dialog_visible_snapshots.insert(
21931 dispose_dialog_ptr,
21932 PersistentDialogSnapshot {
21933 bounds,
21934 pixels: saved_pixels.clone(),
21935 },
21936 );
21937 dispose_disp.dialog_modal_entered.insert(dispose_dialog_ptr);
21938 dispose_disp
21939 .dialog_saved_pixels
21940 .insert(dispose_dialog_ptr, saved_pixels);
21941 dispose_disp
21942 .dialog_item_handles
21943 .insert(dispose_text_handle, (dispose_dialog_ptr, 0));
21944 dispose_disp
21945 .dialog_item_handles
21946 .insert(dispose_other_text_handle, (dispose_other_dialog_ptr, 0));
21947 dispose_disp
21948 .dialog_control_handles
21949 .insert(dispose_ctrl_handle, (dispose_dialog_ptr, 1));
21950 dispose_disp
21951 .dialog_control_handles
21952 .insert(dispose_other_ctrl_handle, (dispose_other_dialog_ptr, 1));
21953 dispose_disp
21954 .dialog_control_values
21955 .insert((dispose_dialog_ptr, 1), 1);
21956 dispose_disp
21957 .dialog_control_values
21958 .insert((dispose_other_dialog_ptr, 1), 1);
21959 dispose_disp
21960 .hidden_dialog_item_rects
21961 .insert((dispose_dialog_ptr, 1), (10, 20, 30, 40));
21962 dispose_disp
21963 .hidden_dialog_item_rects
21964 .insert((dispose_other_dialog_ptr, 1), (50, 60, 70, 80));
21965 dispose_disp
21966 .dialog_cancel_items
21967 .insert(dispose_dialog_ptr, 2);
21968 dispose_disp
21969 .dialog_cancel_items
21970 .insert(dispose_other_dialog_ptr, 3);
21971 dispose_disp.pending_dialog_popup_menu = Some(PendingDialogPopupMenu {
21972 dialog_ptr: dispose_dialog_ptr,
21973 item_no: 1,
21974 menu_id: 900,
21975 rect: (10, 20, 30, 130),
21976 });
21977 dispose_bus.write_long(crate::memory::globals::addr::THE_PORT, dispose_dialog_ptr);
21978 let dispose_global_ptr = dispose_bus.read_long(dispose_cpu.read_reg(Register::A5));
21979 dispose_bus.write_long(dispose_global_ptr, dispose_dialog_ptr);
21980 dispose_cpu.write_reg(Register::A7, TEST_SP);
21981 dispose_bus.write_long(TEST_SP, dispose_dialog_ptr);
21982 dispose_disp
21983 .dispatch_dialog(true, 0x183, &mut dispose_cpu, &mut dispose_bus)
21984 .unwrap()
21985 .unwrap();
21986
21987 DialogLifecycleCleanupSnapshot {
21988 close_stack_after,
21989 close_tracking_cleared,
21990 close_retained_click_cleared,
21991 close_visible_snapshot_cleared,
21992 close_modal_entered_cleared,
21993 close_saved_pixels_cleared,
21994 close_window_list_contains_dialog,
21995 close_front_window_after,
21996 close_the_port_after,
21997 close_current_port_after,
21998 close_update_event_for_previous_window,
21999 close_dialog_items_present_after,
22000 dispose_stack_after: dispose_cpu.read_reg(Register::A7),
22001 dispose_tracking_cleared: dispose_disp.dialog_tracking.is_none(),
22002 dispose_retained_click_cleared: dispose_disp.retained_modal_dialog_click.is_none(),
22003 dispose_visible_snapshot_cleared: !dispose_disp
22004 .dialog_visible_snapshots
22005 .contains_key(&dispose_dialog_ptr),
22006 dispose_modal_entered_cleared: !dispose_disp
22007 .dialog_modal_entered
22008 .contains(&dispose_dialog_ptr),
22009 dispose_saved_pixels_cleared: !dispose_disp
22010 .dialog_saved_pixels
22011 .contains_key(&dispose_dialog_ptr),
22012 dispose_window_list_contains_dialog: dispose_disp
22013 .window_list
22014 .contains(&dispose_dialog_ptr),
22015 dispose_front_window_after: dispose_disp.front_window,
22016 dispose_the_port_after: dispose_bus.read_long(crate::memory::globals::addr::THE_PORT),
22017 dispose_current_port_after: dispose_disp.current_port,
22018 dispose_update_event_for_previous_window: dispose_disp
22019 .event_queue
22020 .iter()
22021 .any(|event| event.what == 6 && event.message == dispose_previous_window),
22022 dispose_dialog_items_present_after: dispose_disp
22023 .dialog_items
22024 .contains_key(&dispose_dialog_ptr),
22025 dispose_other_dialog_items_present_after: dispose_disp
22026 .dialog_items
22027 .contains_key(&dispose_other_dialog_ptr),
22028 dispose_dialog_item_handle_present_after: dispose_disp
22029 .dialog_item_handles
22030 .contains_key(&dispose_text_handle),
22031 dispose_other_dialog_item_handle_present_after: dispose_disp
22032 .dialog_item_handles
22033 .contains_key(&dispose_other_text_handle),
22034 dispose_dialog_control_handle_present_after: dispose_disp
22035 .dialog_control_handles
22036 .contains_key(&dispose_ctrl_handle),
22037 dispose_other_dialog_control_handle_present_after: dispose_disp
22038 .dialog_control_handles
22039 .contains_key(&dispose_other_ctrl_handle),
22040 dispose_dialog_control_value_present_after: dispose_disp
22041 .dialog_control_values
22042 .contains_key(&(dispose_dialog_ptr, 1)),
22043 dispose_other_dialog_control_value_present_after: dispose_disp
22044 .dialog_control_values
22045 .contains_key(&(dispose_other_dialog_ptr, 1)),
22046 dispose_hidden_rect_present_after: dispose_disp
22047 .hidden_dialog_item_rects
22048 .contains_key(&(dispose_dialog_ptr, 1)),
22049 dispose_other_hidden_rect_present_after: dispose_disp
22050 .hidden_dialog_item_rects
22051 .contains_key(&(dispose_other_dialog_ptr, 1)),
22052 dispose_cancel_item_present_after: dispose_disp
22053 .dialog_cancel_items
22054 .contains_key(&dispose_dialog_ptr),
22055 dispose_other_cancel_item_present_after: dispose_disp
22056 .dialog_cancel_items
22057 .contains_key(&dispose_other_dialog_ptr),
22058 dispose_pending_popup_cleared: dispose_disp.pending_dialog_popup_menu.is_none(),
22059 }
22060 }
22061
22062 fn cached_dialog_items(
22063 disp: &TrapDispatcher,
22064 dialog_ptr: u32,
22065 ) -> Vec<(u8, (i16, i16, i16, i16), String, i16)> {
22066 disp.dialog_items
22067 .get(&dialog_ptr)
22068 .into_iter()
22069 .flatten()
22070 .map(|item| {
22071 (
22072 item.item_type,
22073 item.rect,
22074 item.text.clone(),
22075 item.resource_id,
22076 )
22077 })
22078 .collect()
22079 }
22080
22081 fn dialog_port_rect(bus: &MacMemoryBus, dialog_ptr: u32) -> (i16, i16, i16, i16) {
22082 (
22083 bus.read_word(dialog_ptr + 16) as i16,
22084 bus.read_word(dialog_ptr + 18) as i16,
22085 bus.read_word(dialog_ptr + 20) as i16,
22086 bus.read_word(dialog_ptr + 22) as i16,
22087 )
22088 }
22089
22090 fn dialog_creation_results_for_theme(theme_id: UiThemeId) -> DialogCreationSnapshot {
22091 let (mut new_disp, mut new_cpu, mut new_bus) = setup();
22092 new_disp.set_ui_theme_id(theme_id);
22093 let new_screen_base = new_bus.alloc((640 * 480) as u32);
22094 new_bus.write_long(0x0824, new_screen_base);
22095 new_disp.screen_mode = (new_screen_base, 640, 640, 480, 8);
22096
22097 let new_ditl = build_test_ditl_items(&[
22098 (16, (10, 12, 28, 120), b"Alpha".as_slice()),
22099 (4, (44, 70, 66, 132), b"OK".as_slice()),
22100 ]);
22101 let new_items_data_ptr = new_bus.alloc(new_ditl.len() as u32);
22102 new_bus.write_bytes(new_items_data_ptr, &new_ditl);
22103 let new_items_handle = new_bus.alloc(4);
22104 new_bus.write_long(new_items_handle, new_items_data_ptr);
22105
22106 let new_title = new_bus.alloc(32);
22107 new_bus.write_pstring(new_title, b"Modeless");
22108 let new_bounds = new_bus.alloc(8);
22109 new_bus.write_word(new_bounds, 60);
22110 new_bus.write_word(new_bounds + 2, 70);
22111 new_bus.write_word(new_bounds + 4, 140);
22112 new_bus.write_word(new_bounds + 6, 220);
22113 let new_storage = new_bus.alloc(170);
22114 let new_sp = TEST_SP - 30;
22115 new_cpu.write_reg(Register::A7, new_sp);
22116 new_bus.write_long(new_sp, new_items_handle);
22117 new_bus.write_long(new_sp + 4, 0x1234_5678);
22118 new_bus.write_byte(new_sp + 8, 0xFF); new_bus.write_long(new_sp + 10, 0xFFFF_FFFF); new_bus.write_word(new_sp + 14, 4); new_bus.write_byte(new_sp + 16, 0xFF); new_bus.write_long(new_sp + 18, new_title);
22123 new_bus.write_long(new_sp + 22, new_bounds);
22124 new_bus.write_long(new_sp + 26, new_storage);
22125 new_bus.write_long(new_sp + 30, 0xDEAD_BEEF);
22126 new_disp
22127 .dispatch_dialog(true, 0x17D, &mut new_cpu, &mut new_bus)
22128 .unwrap()
22129 .unwrap();
22130 let new_dialog_ptr = new_bus.read_long(new_sp + 30);
22131 let new_update_region =
22132 TrapDispatcher::region_handle_rect(&new_bus, new_bus.read_long(new_dialog_ptr + 122));
22133 let new_update_event_queued = new_disp
22134 .event_queue
22135 .iter()
22136 .any(|event| event.what == 6 && event.message == new_dialog_ptr);
22137
22138 let (mut get_disp, mut get_cpu, mut get_bus) = setup();
22139 get_disp.set_ui_theme_id(theme_id);
22140 let get_screen_base = get_bus.alloc((640 * 480) as u32);
22141 get_bus.write_long(0x0824, get_screen_base);
22142 get_disp.screen_mode = (get_screen_base, 640, 640, 480, 8);
22143
22144 let mut dlog = build_test_dlog((80, 90, 160, 240), 1901, 0);
22145 dlog[10] = 1; let get_ditl = build_test_ditl_items(&[
22147 (16, (12, 18, 30, 128), b"Beta".as_slice()),
22148 (4, (46, 78, 68, 138), b"OK".as_slice()),
22149 ]);
22150 get_disp.install_test_resource(&mut get_bus, *b"DLOG", 1900, &dlog);
22151 let original_ditl_ptr =
22152 get_disp.install_test_resource(&mut get_bus, *b"DITL", 1901, &get_ditl);
22153 let get_storage = get_bus.alloc(170);
22154 get_cpu.write_reg(Register::A7, TEST_SP);
22155 get_bus.write_long(TEST_SP, 0xFFFF_FFFF); get_bus.write_long(TEST_SP + 4, get_storage);
22157 get_bus.write_word(TEST_SP + 8, 1900);
22158 get_bus.write_long(TEST_SP + 10, 0xDEAD_BEEF);
22159 get_disp
22160 .dispatch_dialog(true, 0x17C, &mut get_cpu, &mut get_bus)
22161 .unwrap()
22162 .unwrap();
22163 let get_dialog_ptr = get_bus.read_long(TEST_SP + 10);
22164 let get_items_handle = get_bus.read_long(get_dialog_ptr + 156);
22165 let get_items_data_ptr = get_bus.read_long(get_items_handle);
22166 let get_update_region =
22167 TrapDispatcher::region_handle_rect(&get_bus, get_bus.read_long(get_dialog_ptr + 122));
22168 let get_update_event_queued = get_disp
22169 .event_queue
22170 .iter()
22171 .any(|event| event.what == 6 && event.message == get_dialog_ptr);
22172
22173 DialogCreationSnapshot {
22174 new_dialog_ptr,
22175 new_stack_after: new_cpu.read_reg(Register::A7),
22176 new_result_slot: new_bus.read_long(new_sp + 30),
22177 new_window_list: new_disp.window_list.clone(),
22178 new_front_window: new_disp.front_window,
22179 new_current_port: new_disp.current_port,
22180 new_the_port: new_bus.read_long(crate::memory::globals::addr::THE_PORT),
22181 new_visible_byte: new_bus.read_byte(new_dialog_ptr + 110),
22182 new_goaway_byte: new_bus.read_byte(new_dialog_ptr + 112),
22183 new_refcon: new_bus.read_long(new_dialog_ptr + 152),
22184 new_window_kind: new_bus.read_word(new_dialog_ptr + 108) as i16,
22185 new_proc_id: new_disp
22186 .window_proc_ids
22187 .get(&new_dialog_ptr)
22188 .copied()
22189 .unwrap_or_default(),
22190 new_port_rect: dialog_port_rect(&new_bus, new_dialog_ptr),
22191 new_items_handle: new_bus.read_long(new_dialog_ptr + 156),
22192 new_items_data_ptr: new_bus.read_long(new_items_handle),
22193 new_first_item_handle_nonzero: new_bus.read_long(new_items_data_ptr + 2) != 0,
22194 new_cached_items: cached_dialog_items(&new_disp, new_dialog_ptr),
22195 new_update_region,
22196 new_update_event_queued,
22197 new_edit_field: new_bus.read_word(new_dialog_ptr + 164),
22198 new_default_item: new_bus.read_word(new_dialog_ptr + 168),
22199 get_dialog_ptr,
22200 get_stack_after: get_cpu.read_reg(Register::A7),
22201 get_result_slot: get_bus.read_long(TEST_SP + 10),
22202 get_window_list: get_disp.window_list.clone(),
22203 get_front_window: get_disp.front_window,
22204 get_current_port: get_disp.current_port,
22205 get_the_port: get_bus.read_long(crate::memory::globals::addr::THE_PORT),
22206 get_visible_byte: get_bus.read_byte(get_dialog_ptr + 110),
22207 get_refcon: get_bus.read_long(get_dialog_ptr + 152),
22208 get_window_kind: get_bus.read_word(get_dialog_ptr + 108) as i16,
22209 get_proc_id: get_disp
22210 .window_proc_ids
22211 .get(&get_dialog_ptr)
22212 .copied()
22213 .unwrap_or_default(),
22214 get_port_rect: dialog_port_rect(&get_bus, get_dialog_ptr),
22215 get_items_handle,
22216 get_items_data_ptr,
22217 get_uses_distinct_ditl_copy: get_items_data_ptr != original_ditl_ptr,
22218 get_original_ditl_handle_field: get_bus.read_long(original_ditl_ptr + 2),
22219 get_first_item_handle_nonzero: get_bus.read_long(get_items_data_ptr + 2) != 0,
22220 get_cached_items: cached_dialog_items(&get_disp, get_dialog_ptr),
22221 get_update_region,
22222 get_update_event_queued,
22223 get_edit_field: get_bus.read_word(get_dialog_ptr + 164),
22224 get_default_item: get_bus.read_word(get_dialog_ptr + 168),
22225 }
22226 }
22227
22228 fn alert_family_results_for_theme(theme_id: UiThemeId) -> AlertFamilySnapshot {
22229 let (mut disp, mut cpu, mut bus) = setup();
22230 disp.set_ui_theme_id(theme_id);
22231
22232 let ditl = build_test_ditl_items(&[
22233 (4, (48, 70, 70, 132), b"OK".as_slice()),
22234 (0x84, (48, 144, 70, 212), b"Cancel".as_slice()),
22237 ]);
22238 let alrt = build_alrt_template(2100, 0xC4C4);
22239 disp.install_test_resource(&mut bus, *b"DITL", 2100, &ditl);
22240 disp.install_test_resource(&mut bus, *b"ALRT", 2099, &alrt);
22241 bus.write_word(crate::memory::globals::addr::ALERT_STAGE, 0);
22242 bus.write_word(crate::memory::globals::addr::ANUMBER, 0xCAFE);
22243
22244 let mut staged_calls = Vec::new();
22245 for trap_word in [0x185u16, 0x186, 0x187, 0x188, 0x185] {
22246 cpu.write_reg(Register::A7, TEST_SP);
22247 bus.write_word(TEST_SP + 4, 2099);
22248 disp.dispatch_dialog(true, trap_word, &mut cpu, &mut bus)
22249 .unwrap()
22250 .unwrap();
22251 staged_calls.push(AlertTrapCallSnapshot {
22252 trap_word,
22253 result: bus.read_word(TEST_SP + 6) as i16,
22254 stack_after: cpu.read_reg(Register::A7),
22255 alert_stage_after: bus.read_word(crate::memory::globals::addr::ALERT_STAGE),
22256 anumber_after: bus.read_word(crate::memory::globals::addr::ANUMBER),
22257 });
22258 }
22259
22260 cpu.write_reg(Register::A7, TEST_SP);
22261 bus.write_word(crate::memory::globals::addr::ALERT_STAGE, 2);
22262 bus.write_word(crate::memory::globals::addr::ANUMBER, 0xBEEF);
22263 bus.write_word(TEST_SP + 4, 2999);
22264 disp.dispatch_dialog(true, 0x187, &mut cpu, &mut bus)
22265 .unwrap()
22266 .unwrap();
22267 let missing_call = AlertTrapCallSnapshot {
22268 trap_word: 0x187,
22269 result: bus.read_word(TEST_SP + 6) as i16,
22270 stack_after: cpu.read_reg(Register::A7),
22271 alert_stage_after: bus.read_word(crate::memory::globals::addr::ALERT_STAGE),
22272 anumber_after: bus.read_word(crate::memory::globals::addr::ANUMBER),
22273 };
22274
22275 let (mut prep_disp, mut prep_cpu, mut prep_bus) = setup();
22276 prep_disp.set_ui_theme_id(theme_id);
22277 let prep_alrt = build_alrt_template(2101, 0);
22278 prep_disp.install_test_resource(&mut prep_bus, *b"ALRT", 2102, &prep_alrt);
22279 let mut prep_call = |trap_word: u16, alert_id: i16| -> (i16, u32) {
22280 prep_cpu.write_reg(Register::A7, TEST_SP);
22281 prep_bus.write_word(0x0A60, 0x7FFF);
22282 prep_bus.write_word(TEST_SP, alert_id as u16);
22283 prep_disp
22284 .dispatch_dialog(true, trap_word, &mut prep_cpu, &mut prep_bus)
22285 .unwrap()
22286 .unwrap();
22287 (
22288 prep_bus.read_word(0x0A60) as i16,
22289 prep_cpu.read_reg(Register::A7),
22290 )
22291 };
22292 let resource_prep = AlertResourcePrepSnapshot {
22293 could_present: prep_call(0x189, 2102),
22294 free_present: prep_call(0x18A, 2102),
22295 could_missing: prep_call(0x189, 2998),
22296 free_missing: prep_call(0x18A, 2998),
22297 };
22298
22299 AlertFamilySnapshot {
22300 staged_calls,
22301 missing_call,
22302 resource_prep,
22303 }
22304 }
22305
22306 fn dialog_init_sound_results_for_theme(theme_id: UiThemeId) -> DialogInitSoundSnapshot {
22307 let (mut disp, mut cpu, mut bus) = setup();
22308 disp.set_ui_theme_id(theme_id);
22309 use crate::memory::globals::addr;
22310
22311 bus.write_long(addr::RESUME_PROC, 0xDEAD_BEEF);
22312 bus.write_long(addr::DA_BEEPER, 0x00AA_BBCC);
22313 bus.write_word(addr::ALERT_STAGE, 3);
22314 bus.write_word(addr::ANUMBER, 0xCAFE);
22315 for i in 0..4u32 {
22316 bus.write_long(addr::DA_STRINGS + i * 4, 0x00D0_0000 | i);
22317 }
22318
22319 cpu.write_reg(Register::A7, TEST_SP);
22320 bus.write_long(TEST_SP, 0x0012_3456);
22321 disp.dispatch_dialog(true, 0x17B, &mut cpu, &mut bus)
22322 .unwrap()
22323 .unwrap();
22324 let init_stack_after = cpu.read_reg(Register::A7);
22325 let resume_proc_after_init = bus.read_long(addr::RESUME_PROC);
22326 let da_beeper_after_init = bus.read_long(addr::DA_BEEPER);
22327 let alert_stage_after_init = bus.read_word(addr::ALERT_STAGE);
22328 let anumber_after_init = bus.read_word(addr::ANUMBER);
22329 let da_strings_after_init =
22330 std::array::from_fn(|i| bus.read_long(addr::DA_STRINGS + i as u32 * 4));
22331
22332 cpu.write_reg(Register::A7, TEST_SP);
22333 bus.write_long(TEST_SP, 0x00AB_CDEF);
22334 disp.dispatch_dialog(true, 0x18C, &mut cpu, &mut bus)
22335 .unwrap()
22336 .unwrap();
22337 let error_sound_stack_after = cpu.read_reg(Register::A7);
22338 let da_beeper_after_error_sound = bus.read_long(addr::DA_BEEPER);
22339
22340 cpu.write_reg(Register::A7, TEST_SP);
22341 bus.write_long(TEST_SP, 0);
22342 disp.dispatch_dialog(true, 0x18C, &mut cpu, &mut bus)
22343 .unwrap()
22344 .unwrap();
22345 let nil_error_sound_stack_after = cpu.read_reg(Register::A7);
22346 let da_beeper_after_nil_error_sound = bus.read_long(addr::DA_BEEPER);
22347
22348 DialogInitSoundSnapshot {
22349 init_stack_after,
22350 resume_proc_after_init,
22351 da_beeper_after_init,
22352 alert_stage_after_init,
22353 anumber_after_init,
22354 da_strings_after_init,
22355 error_sound_stack_after,
22356 da_beeper_after_error_sound,
22357 nil_error_sound_stack_after,
22358 da_beeper_after_nil_error_sound,
22359 }
22360 }
22361
22362 fn dialogdispatch_default_cancel_results_for_theme(
22363 theme_id: UiThemeId,
22364 ) -> DialogDispatchDefaultCancelSnapshot {
22365 let (mut disp, mut cpu, mut bus) = setup();
22366 disp.set_ui_theme_id(theme_id);
22367 let dialog_ptr = bus.alloc(256);
22368 bus.write_word(dialog_ptr + 168, 1);
22369 disp.dialog_tracking = Some(dialog_tracking_state_for_test(dialog_ptr));
22370
22371 cpu.write_reg(Register::A7, TEST_SP);
22372 bus.write_word(TEST_SP, 9); bus.write_long(TEST_SP + 2, dialog_ptr); bus.write_word(TEST_SP + 6, 0xBEEF); cpu.write_reg(Register::D0, 0x0304); disp.dispatch_dialog(true, 0x268, &mut cpu, &mut bus)
22377 .unwrap()
22378 .unwrap();
22379 let default_result = bus.read_word(TEST_SP + 6);
22380 let default_stack_after = cpu.read_reg(Register::A7);
22381 let default_adef_item = bus.read_word(dialog_ptr + 168);
22382 let default_tracking_item = disp
22383 .dialog_tracking
22384 .as_ref()
22385 .map(|tracking| tracking.default_item)
22386 .unwrap_or(-1);
22387
22388 cpu.write_reg(Register::A7, TEST_SP);
22389 bus.write_word(TEST_SP, 7); bus.write_long(TEST_SP + 2, dialog_ptr); bus.write_word(TEST_SP + 6, 0xCAFE); cpu.write_reg(Register::D0, 0x0305); disp.dispatch_dialog(true, 0x268, &mut cpu, &mut bus)
22394 .unwrap()
22395 .unwrap();
22396 let cancel_result = bus.read_word(TEST_SP + 6);
22397 let cancel_stack_after = cpu.read_reg(Register::A7);
22398 let cancel_map_item = disp.dialog_cancel_items.get(&dialog_ptr).copied();
22399 let cancel_tracking_item = disp
22400 .dialog_tracking
22401 .as_ref()
22402 .map(|tracking| tracking.cancel_item)
22403 .unwrap_or(-1);
22404
22405 cpu.write_reg(Register::A7, TEST_SP);
22406 bus.write_word(TEST_SP, 1); bus.write_long(TEST_SP + 2, dialog_ptr); bus.write_word(TEST_SP + 6, 0xCAFE); cpu.write_reg(Register::D0, 0x0306); disp.dispatch_dialog(true, 0x268, &mut cpu, &mut bus)
22411 .unwrap()
22412 .unwrap();
22413 let tracks_result = bus.read_word(TEST_SP + 6);
22414 let tracks_stack_after = cpu.read_reg(Register::A7);
22415 let tracks_adef_item = bus.read_word(dialog_ptr + 168);
22416 let tracks_cancel_map_item = disp.dialog_cancel_items.get(&dialog_ptr).copied();
22417 let tracks_tracking_items = disp
22418 .dialog_tracking
22419 .as_ref()
22420 .map(|tracking| (tracking.default_item, tracking.cancel_item))
22421 .unwrap_or((-1, -1));
22422
22423 DialogDispatchDefaultCancelSnapshot {
22424 default_result,
22425 default_stack_after,
22426 default_adef_item,
22427 default_tracking_item,
22428 cancel_result,
22429 cancel_stack_after,
22430 cancel_map_item,
22431 cancel_tracking_item,
22432 tracks_result,
22433 tracks_stack_after,
22434 tracks_adef_item,
22435 tracks_cancel_map_item,
22436 tracks_tracking_items,
22437 }
22438 }
22439
22440 fn updtdialog_update_region_results_for_theme(
22441 theme_id: UiThemeId,
22442 ) -> UpdtDialogUpdateRegionSnapshot {
22443 let (mut disp, mut cpu, mut bus) = setup_with_port();
22444 disp.set_ui_theme_id(theme_id);
22445
22446 let initial_port = 0x181000;
22447 disp.set_current_port_state(&mut bus, &mut cpu, initial_port, None);
22448
22449 let screen_base = bus.alloc(100 * 100);
22450 bus.write_long(0x0824, screen_base);
22451 disp.screen_mode = (screen_base, 100, 100, 100, 8);
22452
22453 let dialog_ptr = bus.alloc(256);
22454 bus.write_word(dialog_ptr, 0);
22455 bus.write_long(dialog_ptr + 2, screen_base);
22456 bus.write_word(dialog_ptr + 6, 100);
22457 bus.write_word(dialog_ptr + 8, 0);
22458 bus.write_word(dialog_ptr + 10, 0);
22459 bus.write_word(dialog_ptr + 12, 100);
22460 bus.write_word(dialog_ptr + 14, 100);
22461 bus.write_word(dialog_ptr + 16, 0);
22462 bus.write_word(dialog_ptr + 18, 0);
22463 bus.write_word(dialog_ptr + 20, 100);
22464 bus.write_word(dialog_ptr + 22, 100);
22465 bus.write_word(dialog_ptr + 108, 2);
22466 disp.window_list.push(dialog_ptr);
22467 disp.front_window = dialog_ptr;
22468 disp.dialog_initial_draw_deferred.insert(dialog_ptr);
22469 disp.dialog_items.insert(
22470 dialog_ptr,
22471 vec![
22472 DialogItem {
22473 item_type: 0,
22474 rect: (8, 8, 24, 32),
22475 proc_ptr: 0x500000,
22476 ..Default::default()
22477 },
22478 DialogItem {
22479 item_type: 0,
22480 rect: (60, 60, 80, 80),
22481 proc_ptr: 0x600000,
22482 ..Default::default()
22483 },
22484 DialogItem {
22485 item_type: 8,
22486 rect: (90, 90, 96, 96),
22487 text: "x".to_string(),
22488 ..Default::default()
22489 },
22490 DialogItem {
22491 item_type: 4,
22492 rect: (52, 8, 72, 44),
22493 text: "OK".to_string(),
22494 ..Default::default()
22495 },
22496 ],
22497 );
22498 let inside_update_pixel = screen_base + 4 * 100 + 4;
22499 let outside_update_pixel = screen_base + 70 * 100 + 70;
22500 let outside_item_pixel = screen_base + 52 * 100 + 9;
22501 bus.write_byte(inside_update_pixel, 0xA5);
22502 bus.write_byte(outside_update_pixel, 0x5A);
22503 bus.write_byte(outside_item_pixel, 0x3C);
22504
22505 let update_rgn_ptr = bus.alloc(10);
22506 let update_rgn = bus.alloc(4);
22507 bus.write_long(update_rgn, update_rgn_ptr);
22508 bus.write_word(update_rgn_ptr, 10);
22509 bus.write_word(update_rgn_ptr + 2, 0);
22510 bus.write_word(update_rgn_ptr + 4, 0);
22511 bus.write_word(update_rgn_ptr + 6, 40);
22512 bus.write_word(update_rgn_ptr + 8, 40);
22513
22514 cpu.write_reg(Register::A7, TEST_SP);
22515 bus.write_long(TEST_SP, update_rgn);
22516 bus.write_long(TEST_SP + 4, dialog_ptr);
22517 disp.dispatch_dialog(true, 0x178, &mut cpu, &mut bus)
22518 .unwrap()
22519 .unwrap();
22520
22521 UpdtDialogUpdateRegionSnapshot {
22522 stack_after: cpu.read_reg(Register::A7),
22523 the_port_after: bus.read_long(crate::memory::globals::addr::THE_PORT),
22524 current_port_after: disp.current_port,
22525 deferred_after: disp.dialog_initial_draw_deferred.contains(&dialog_ptr),
22526 queued_draw_procs: disp
22527 .modeless_dialog_draw_proc_queue
22528 .iter()
22529 .copied()
22530 .collect(),
22531 item_count_after: disp
22532 .dialog_items
22533 .get(&dialog_ptr)
22534 .map(Vec::len)
22535 .unwrap_or_default(),
22536 inside_update_pixel_after: bus.read_byte(inside_update_pixel),
22537 outside_update_pixel_after: bus.read_byte(outside_update_pixel),
22538 outside_item_pixel_after: bus.read_byte(outside_item_pixel),
22539 }
22540 }
22541
22542 #[test]
22543 fn systemless_theme_does_not_change_dialogselect_item_hits() {
22544 let classic = dialog_select_enabled_user_item_hit_for_theme(UiThemeId::ClassicSystem7);
22548 let themed = dialog_select_enabled_user_item_hit_for_theme(UiThemeId::SystemlessDefault);
22549
22550 assert_eq!(classic.0, 0xFFFF);
22551 assert_eq!(classic.2, 1);
22552 assert_eq!(classic.3, TEST_SP + 12);
22553 assert_eq!(
22554 themed, classic,
22555 "systemless-default must not change DialogSelect item-hit semantics"
22556 );
22557 }
22558
22559 #[test]
22560 fn systemless_theme_does_not_change_dialogselect_control_item_hits() {
22561 let classic = dialog_select_enabled_checkbox_hit_for_theme(UiThemeId::ClassicSystem7);
22566 let themed = dialog_select_enabled_checkbox_hit_for_theme(UiThemeId::SystemlessDefault);
22567
22568 assert_eq!(classic.0, 0xFFFF);
22569 assert_eq!(classic.2, 1);
22570 assert_eq!(classic.3, TEST_SP + 12);
22571 assert_eq!(classic.4, 1);
22572 assert_eq!(classic.5, 1);
22573 assert_eq!(
22574 themed, classic,
22575 "systemless-default must not change DialogSelect control item-hit or value semantics"
22576 );
22577 }
22578
22579 #[test]
22580 fn systemless_theme_does_not_change_dialogselect_window_events() {
22581 let classic = dialog_select_window_event_results_for_theme(UiThemeId::ClassicSystem7);
22591 let themed = dialog_select_window_event_results_for_theme(UiThemeId::SystemlessDefault);
22592
22593 assert_eq!(classic.update_result, 0);
22594 assert_eq!(classic.update_dialog_out, 0xDEAD_BEEF);
22595 assert_eq!(classic.update_item_hit, 0xCAFE);
22596 assert_eq!(classic.update_stack_after, TEST_SP + 12);
22597 assert!(!classic.update_deferred_after);
22598 assert_eq!(classic.update_region_after, None);
22599 assert_eq!(classic.update_vis_region_after, Some((0, 0, 100, 100)));
22600 assert_eq!(
22601 classic.update_the_port_after,
22602 classic.update_current_port_after
22603 );
22604 assert_eq!(classic.update_current_port_after, classic.dialog_ptr);
22605 assert!(!classic.update_saved_vis_after);
22606 assert_eq!(
22607 classic.update_queued_draw_procs,
22608 vec![(classic.dialog_ptr, 0x500000, 1)]
22609 );
22610 assert_eq!(classic.update_item_count_after, 3);
22611 assert_eq!(classic.activate_result, 0);
22612 assert_eq!(classic.activate_dialog_out, 0xDEAD_BEEF);
22613 assert_eq!(classic.activate_item_hit, 0xCAFE);
22614 assert_eq!(classic.activate_stack_after, TEST_SP + 12);
22615 assert_eq!(
22616 themed, classic,
22617 "systemless-default must not change DialogSelect update/activate handling"
22618 );
22619 }
22620
22621 #[test]
22622 fn systemless_theme_does_not_change_modaldialog_edit_text_mouse_handling() {
22623 let classic = modal_dialog_edit_text_mouse_results_for_theme(UiThemeId::ClassicSystem7);
22629 let themed = modal_dialog_edit_text_mouse_results_for_theme(UiThemeId::SystemlessDefault);
22630
22631 assert_eq!(classic.item_hit, 2);
22632 assert_eq!(classic.stack_after, TEST_SP + 8);
22633 assert!(classic.tracking_finished);
22634 assert!(classic.retained_visible_snapshot);
22635 assert!(classic.saved_background_retained);
22636 assert!(classic.queued_mouse_up_consumed);
22637 assert_eq!(classic.edit_field, 1);
22638 assert_eq!(classic.item_selection, (2, 4));
22639 assert_eq!(classic.te_text, b"Second".to_vec());
22640 assert_eq!(classic.te_length, 6);
22641 assert_eq!(classic.te_selection, (2, 4));
22642 assert_eq!(classic.handle_bytes, b"Second".to_vec());
22643 assert_eq!(
22644 themed, classic,
22645 "systemless-default must not change ModalDialog editText mouse handling"
22646 );
22647 }
22648
22649 #[test]
22650 fn systemless_theme_does_not_change_modaldialog_keyboard_default_cancel_items() {
22651 let classic = modal_dialog_keyboard_button_results_for_theme(UiThemeId::ClassicSystem7);
22656 let themed = modal_dialog_keyboard_button_results_for_theme(UiThemeId::SystemlessDefault);
22657
22658 for case in [&classic.return_key, &classic.enter_key] {
22659 assert_eq!(case.flash_item_after_key, 1);
22660 assert_eq!(case.stack_after_key, TEST_SP);
22661 assert_eq!(case.item_hit_after_key, 0xCAFE);
22662 assert_eq!(case.item_hit_after_flash, 1);
22663 assert_eq!(case.stack_after_flash, TEST_SP + 8);
22664 assert!(case.tracking_finished);
22665 }
22666 for case in [&classic.escape_key, &classic.command_period] {
22667 assert_eq!(case.flash_item_after_key, 2);
22668 assert_eq!(case.stack_after_key, TEST_SP);
22669 assert_eq!(case.item_hit_after_key, 0xCAFE);
22670 assert_eq!(case.item_hit_after_flash, 2);
22671 assert_eq!(case.stack_after_flash, TEST_SP + 8);
22672 assert!(case.tracking_finished);
22673 }
22674 assert_eq!(
22675 themed, classic,
22676 "systemless-default must not change ModalDialog keyboard default/cancel handling"
22677 );
22678 }
22679
22680 #[test]
22681 fn systemless_theme_does_not_change_modaldialog_update_events() {
22682 let classic = modal_dialog_update_event_results_for_theme(UiThemeId::ClassicSystem7);
22689 let themed = modal_dialog_update_event_results_for_theme(UiThemeId::SystemlessDefault);
22690
22691 assert_eq!(classic.item_hit_after, 0xCAFE);
22692 assert_eq!(classic.stack_after, TEST_SP);
22693 assert!(!classic.tracking_finished);
22694 assert!(classic.rendered_pixels_final);
22695 assert_eq!(classic.retained_pixel_after, 0x44);
22696 assert_eq!(classic.retained_snapshot_pixel_after, 0x44);
22697 assert_eq!(classic.update_region_after, None);
22698 assert_eq!(classic.vis_region_after, Some((0, 0, 80, 80)));
22699 assert_eq!(classic.the_port_after, classic.current_port_after);
22700 assert_eq!(classic.current_port_after, classic.dialog_ptr);
22701 assert!(!classic.saved_vis_after);
22702 assert_eq!(
22703 themed, classic,
22704 "systemless-default must not change ModalDialog update-event handling"
22705 );
22706 }
22707
22708 #[test]
22709 fn systemless_theme_does_not_change_dialog_lifecycle_cleanup() {
22710 let classic = dialog_lifecycle_cleanup_results_for_theme(UiThemeId::ClassicSystem7);
22718 let themed = dialog_lifecycle_cleanup_results_for_theme(UiThemeId::SystemlessDefault);
22719
22720 assert_eq!(classic.close_stack_after, TEST_SP + 4);
22721 assert!(classic.close_tracking_cleared);
22722 assert!(classic.close_retained_click_cleared);
22723 assert!(classic.close_visible_snapshot_cleared);
22724 assert!(classic.close_modal_entered_cleared);
22725 assert!(classic.close_saved_pixels_cleared);
22726 assert!(!classic.close_window_list_contains_dialog);
22727 assert_eq!(
22728 classic.close_front_window_after,
22729 classic.close_the_port_after
22730 );
22731 assert_eq!(
22732 classic.close_front_window_after,
22733 classic.close_current_port_after
22734 );
22735 assert!(classic.close_update_event_for_previous_window);
22736 assert!(!classic.close_dialog_items_present_after);
22737
22738 assert_eq!(classic.dispose_stack_after, TEST_SP + 4);
22739 assert!(classic.dispose_tracking_cleared);
22740 assert!(classic.dispose_retained_click_cleared);
22741 assert!(classic.dispose_visible_snapshot_cleared);
22742 assert!(classic.dispose_modal_entered_cleared);
22743 assert!(classic.dispose_saved_pixels_cleared);
22744 assert!(!classic.dispose_window_list_contains_dialog);
22745 assert_eq!(
22746 classic.dispose_front_window_after,
22747 classic.dispose_the_port_after
22748 );
22749 assert_eq!(
22750 classic.dispose_front_window_after,
22751 classic.dispose_current_port_after
22752 );
22753 assert!(classic.dispose_update_event_for_previous_window);
22754 assert!(!classic.dispose_dialog_items_present_after);
22755 assert!(classic.dispose_other_dialog_items_present_after);
22756 assert!(!classic.dispose_dialog_item_handle_present_after);
22757 assert!(classic.dispose_other_dialog_item_handle_present_after);
22758 assert!(!classic.dispose_dialog_control_handle_present_after);
22759 assert!(classic.dispose_other_dialog_control_handle_present_after);
22760 assert!(!classic.dispose_dialog_control_value_present_after);
22761 assert!(classic.dispose_other_dialog_control_value_present_after);
22762 assert!(!classic.dispose_hidden_rect_present_after);
22763 assert!(classic.dispose_other_hidden_rect_present_after);
22764 assert!(!classic.dispose_cancel_item_present_after);
22765 assert!(classic.dispose_other_cancel_item_present_after);
22766 assert!(classic.dispose_pending_popup_cleared);
22767 assert_eq!(
22768 themed, classic,
22769 "systemless-default must not change CloseDialog/DisposeDialog lifecycle cleanup"
22770 );
22771 }
22772
22773 #[test]
22774 fn systemless_theme_does_not_change_dialog_creation_records() {
22775 let classic = dialog_creation_results_for_theme(UiThemeId::ClassicSystem7);
22783 let themed = dialog_creation_results_for_theme(UiThemeId::SystemlessDefault);
22784
22785 assert_ne!(classic.new_dialog_ptr, 0);
22786 assert_eq!(classic.new_stack_after, TEST_SP);
22787 assert_eq!(classic.new_result_slot, classic.new_dialog_ptr);
22788 assert_eq!(classic.new_window_list, vec![classic.new_dialog_ptr]);
22789 assert_eq!(classic.new_front_window, classic.new_dialog_ptr);
22790 assert_eq!(classic.new_current_port, classic.new_dialog_ptr);
22791 assert_eq!(classic.new_the_port, classic.new_dialog_ptr);
22792 assert_eq!(classic.new_visible_byte, 0xFF);
22793 assert_eq!(classic.new_goaway_byte, 0xFF);
22794 assert_eq!(classic.new_refcon, 0x1234_5678);
22795 assert_eq!(classic.new_window_kind, 2);
22796 assert_eq!(classic.new_proc_id, 4);
22797 assert_eq!(classic.new_port_rect, (0, 0, 80, 150));
22798 assert_ne!(classic.new_items_handle, 0);
22799 assert_ne!(classic.new_items_data_ptr, 0);
22800 assert!(classic.new_first_item_handle_nonzero);
22801 assert_eq!(
22802 classic.new_cached_items,
22803 vec![
22804 (16, (10, 12, 28, 120), "Alpha".to_string(), 0),
22805 (4, (44, 70, 66, 132), "OK".to_string(), 0),
22806 ]
22807 );
22808 assert!(classic.new_update_region.is_some());
22809 assert!(classic.new_update_event_queued);
22810 assert_eq!(classic.new_edit_field, 0xFFFF);
22811 assert_eq!(classic.new_default_item, 1);
22812
22813 assert_ne!(classic.get_dialog_ptr, 0);
22814 assert_eq!(classic.get_stack_after, TEST_SP + 10);
22815 assert_eq!(classic.get_result_slot, classic.get_dialog_ptr);
22816 assert_eq!(classic.get_window_list, vec![classic.get_dialog_ptr]);
22817 assert_eq!(classic.get_front_window, classic.get_dialog_ptr);
22818 assert_eq!(classic.get_current_port, classic.get_dialog_ptr);
22819 assert_eq!(classic.get_the_port, classic.get_dialog_ptr);
22820 assert_eq!(classic.get_visible_byte, 0xFF);
22821 assert_eq!(classic.get_refcon, 0);
22822 assert_eq!(classic.get_window_kind, 2);
22823 assert_eq!(classic.get_proc_id, 2);
22824 assert_eq!(classic.get_port_rect, (0, 0, 80, 150));
22825 assert_ne!(classic.get_items_handle, 0);
22826 assert_ne!(classic.get_items_data_ptr, 0);
22827 assert!(classic.get_uses_distinct_ditl_copy);
22828 assert_eq!(classic.get_original_ditl_handle_field, 0);
22829 assert!(classic.get_first_item_handle_nonzero);
22830 assert_eq!(
22831 classic.get_cached_items,
22832 vec![
22833 (16, (12, 18, 30, 128), "Beta".to_string(), 0),
22834 (4, (46, 78, 68, 138), "OK".to_string(), 0),
22835 ]
22836 );
22837 assert!(classic.get_update_region.is_some());
22838 assert!(classic.get_update_event_queued);
22839 assert_eq!(classic.get_edit_field, 0xFFFF);
22840 assert_eq!(classic.get_default_item, 1);
22841 assert_eq!(
22842 themed, classic,
22843 "systemless-default must not change NewDialog/GetNewDialog creation semantics"
22844 );
22845 }
22846
22847 #[test]
22848 fn systemless_theme_does_not_change_alert_family_resource_state() {
22849 let classic = alert_family_results_for_theme(UiThemeId::ClassicSystem7);
22857 let themed = alert_family_results_for_theme(UiThemeId::SystemlessDefault);
22858
22859 assert_eq!(
22860 classic.staged_calls,
22861 vec![
22862 AlertTrapCallSnapshot {
22863 trap_word: 0x185,
22864 result: 1,
22865 stack_after: TEST_SP + 6,
22866 alert_stage_after: 1,
22867 anumber_after: 2099,
22868 },
22869 AlertTrapCallSnapshot {
22870 trap_word: 0x186,
22871 result: 2,
22872 stack_after: TEST_SP + 6,
22873 alert_stage_after: 2,
22874 anumber_after: 2099,
22875 },
22876 AlertTrapCallSnapshot {
22877 trap_word: 0x187,
22878 result: 1,
22879 stack_after: TEST_SP + 6,
22880 alert_stage_after: 3,
22881 anumber_after: 2099,
22882 },
22883 AlertTrapCallSnapshot {
22884 trap_word: 0x188,
22885 result: 2,
22886 stack_after: TEST_SP + 6,
22887 alert_stage_after: 3,
22888 anumber_after: 2099,
22889 },
22890 AlertTrapCallSnapshot {
22891 trap_word: 0x185,
22892 result: 2,
22893 stack_after: TEST_SP + 6,
22894 alert_stage_after: 3,
22895 anumber_after: 2099,
22896 },
22897 ]
22898 );
22899 assert_eq!(
22900 classic.missing_call,
22901 AlertTrapCallSnapshot {
22902 trap_word: 0x187,
22903 result: -1,
22904 stack_after: TEST_SP + 6,
22905 alert_stage_after: 2,
22906 anumber_after: 0xBEEF,
22907 }
22908 );
22909 assert_eq!(
22910 classic.resource_prep,
22911 AlertResourcePrepSnapshot {
22912 could_present: (0, TEST_SP + 2),
22913 free_present: (0, TEST_SP + 2),
22914 could_missing: (0, TEST_SP + 2),
22915 free_missing: (0, TEST_SP + 2),
22916 }
22917 );
22918 assert_eq!(
22919 themed, classic,
22920 "systemless-default must not change Alert family resource or low-memory semantics"
22921 );
22922 }
22923
22924 #[test]
22925 fn systemless_theme_does_not_change_initdialogs_errorsound_state() {
22926 let classic = dialog_init_sound_results_for_theme(UiThemeId::ClassicSystem7);
22933 let themed = dialog_init_sound_results_for_theme(UiThemeId::SystemlessDefault);
22934
22935 assert_eq!(classic.init_stack_after, TEST_SP + 4);
22936 assert_eq!(classic.resume_proc_after_init, 0x0012_3456);
22937 assert_eq!(classic.da_beeper_after_init, 0);
22938 assert_eq!(classic.alert_stage_after_init, 0);
22939 assert_eq!(classic.anumber_after_init, 0xCAFE);
22940 assert_eq!(classic.da_strings_after_init, [0, 0, 0, 0]);
22941 assert_eq!(classic.error_sound_stack_after, TEST_SP + 4);
22942 assert_eq!(classic.da_beeper_after_error_sound, 0x00AB_CDEF);
22943 assert_eq!(classic.nil_error_sound_stack_after, TEST_SP + 4);
22944 assert_eq!(classic.da_beeper_after_nil_error_sound, 0);
22945 assert_eq!(
22946 themed, classic,
22947 "systemless-default must not change InitDialogs/ErrorSound low-memory state"
22948 );
22949 }
22950
22951 #[test]
22952 fn systemless_theme_does_not_change_dialogdispatch_default_cancel_selectors() {
22953 let classic = dialogdispatch_default_cancel_results_for_theme(UiThemeId::ClassicSystem7);
22960 let themed = dialogdispatch_default_cancel_results_for_theme(UiThemeId::SystemlessDefault);
22961
22962 assert_eq!(classic.default_result, 0);
22963 assert_eq!(classic.default_stack_after, TEST_SP + 6);
22964 assert_eq!(classic.default_adef_item, 9);
22965 assert_eq!(classic.default_tracking_item, 9);
22966
22967 assert_eq!(classic.cancel_result, 0);
22968 assert_eq!(classic.cancel_stack_after, TEST_SP + 6);
22969 assert_eq!(classic.cancel_map_item, Some(7));
22970 assert_eq!(classic.cancel_tracking_item, 7);
22971
22972 assert_eq!(classic.tracks_result, 0);
22973 assert_eq!(classic.tracks_stack_after, TEST_SP + 6);
22974 assert_eq!(classic.tracks_adef_item, 9);
22975 assert_eq!(classic.tracks_cancel_map_item, Some(7));
22976 assert_eq!(classic.tracks_tracking_items, (9, 7));
22977 assert_eq!(
22978 themed, classic,
22979 "systemless-default must not change DialogDispatch default/cancel selector semantics"
22980 );
22981 }
22982
22983 #[test]
22984 fn systemless_theme_does_not_change_updtdialog_update_region_semantics() {
22985 let classic = updtdialog_update_region_results_for_theme(UiThemeId::ClassicSystem7);
22992 let themed = updtdialog_update_region_results_for_theme(UiThemeId::SystemlessDefault);
22993
22994 assert_eq!(classic.stack_after, TEST_SP + 8);
22995 assert_ne!(classic.the_port_after, 0x181000);
22996 assert_eq!(classic.the_port_after, classic.current_port_after);
22997 assert!(!classic.deferred_after);
22998 assert_eq!(classic.queued_draw_procs.len(), 1);
22999 assert_eq!(classic.queued_draw_procs[0].0, classic.the_port_after);
23000 assert_eq!(classic.queued_draw_procs[0].1, 0x500000);
23001 assert_eq!(classic.queued_draw_procs[0].2, 1);
23002 assert_eq!(classic.item_count_after, 4);
23003 assert_eq!(classic.inside_update_pixel_after, 0);
23004 assert_eq!(classic.outside_update_pixel_after, 0x5A);
23005 assert_eq!(classic.outside_item_pixel_after, 0x3C);
23006 assert_eq!(
23007 themed, classic,
23008 "systemless-default must not change UpdtDialog update-region semantics"
23009 );
23010 }
23011
23012 #[test]
23013 fn systemless_theme_does_not_change_dialogselect_edit_text_mouse_and_null_events() {
23014 let classic = dialog_select_edit_text_mouse_results_for_theme(UiThemeId::ClassicSystem7);
23021 let themed = dialog_select_edit_text_mouse_results_for_theme(UiThemeId::SystemlessDefault);
23022
23023 assert_eq!(classic.mouse_result, 0xFFFF);
23024 assert_ne!(classic.mouse_dialog_out, 0);
23025 assert_eq!(classic.mouse_item_hit, 2);
23026 assert_eq!(classic.mouse_stack_after, TEST_SP + 12);
23027 assert_eq!(classic.mouse_edit_field, 1);
23028 assert_eq!(classic.mouse_item_selection, (2, 4));
23029 assert_eq!(classic.mouse_te_text, b"Second".to_vec());
23030 assert_eq!(classic.mouse_te_length, 6);
23031 assert_eq!(classic.mouse_te_selection, (2, 4));
23032 assert_eq!(classic.null_result, 0);
23033 assert_eq!(classic.null_dialog_out, 0xDEAD_BEEF);
23034 assert_eq!(classic.null_item_hit, 0xCAFE);
23035 assert_eq!(classic.null_stack_after, TEST_SP + 12);
23036 assert_eq!(classic.null_edit_field, 1);
23037 assert_eq!(classic.null_te_text, b"Second".to_vec());
23038 assert_eq!(classic.null_te_length, 6);
23039 assert_eq!(classic.null_te_selection, (2, 4));
23040 assert_eq!(
23041 themed, classic,
23042 "systemless-default must not change DialogSelect editText mouse/null handling"
23043 );
23044 }
23045
23046 #[test]
23047 fn systemless_theme_does_not_change_dialogselect_edit_text_key_handling() {
23048 let classic = dialog_select_edit_text_key_results_for_theme(UiThemeId::ClassicSystem7);
23054 let themed = dialog_select_edit_text_key_results_for_theme(UiThemeId::SystemlessDefault);
23055
23056 assert_eq!(classic.keydown_result, 0xFFFF);
23057 assert_ne!(classic.keydown_dialog_out, 0);
23058 assert_eq!(classic.keydown_item_hit, 1);
23059 assert_eq!(classic.keydown_stack_after, TEST_SP + 12);
23060 assert_eq!(classic.keydown_handle_bytes, b"HYo".to_vec());
23061 assert_eq!(classic.keydown_cached_text, "HYo");
23062 assert_eq!(classic.keydown_item_selection, (2, 2));
23063 assert_eq!(classic.keydown_te_text, b"HYo".to_vec());
23064 assert_eq!(classic.keydown_te_length, 3);
23065 assert_eq!(classic.keydown_te_selection, (2, 2));
23066 assert_eq!(classic.autokey_result, 0xFFFF);
23067 assert_eq!(classic.autokey_dialog_out, classic.keydown_dialog_out);
23068 assert_eq!(classic.autokey_item_hit, 1);
23069 assert_eq!(classic.autokey_stack_after, TEST_SP + 12);
23070 assert_eq!(classic.autokey_handle_bytes, b"Ho".to_vec());
23071 assert_eq!(classic.autokey_cached_text, "Ho");
23072 assert_eq!(classic.autokey_item_selection, (1, 1));
23073 assert_eq!(classic.autokey_te_text, b"Ho".to_vec());
23074 assert_eq!(classic.autokey_te_length, 2);
23075 assert_eq!(classic.autokey_te_selection, (1, 1));
23076 assert_eq!(
23077 themed, classic,
23078 "systemless-default must not change DialogSelect editText key handling"
23079 );
23080 }
23081
23082 #[test]
23083 fn systemless_theme_does_not_change_isdialogevent_active_dialog_routing() {
23084 let classic = is_dialog_event_results_for_theme(UiThemeId::ClassicSystem7);
23090 let themed = is_dialog_event_results_for_theme(UiThemeId::SystemlessDefault);
23091
23092 assert_eq!(classic.null_event, (0xFFFF, TEST_SP + 4));
23093 assert_eq!(classic.key_down, (0xFFFF, TEST_SP + 4));
23094 assert_eq!(classic.mouse_inside, (0xFFFF, TEST_SP + 4));
23095 assert_eq!(classic.mouse_outside, (0, TEST_SP + 4));
23096 assert_eq!(classic.update_target, (0xFFFF, TEST_SP + 4));
23097 assert_eq!(classic.activate_target, (0xFFFF, TEST_SP + 4));
23098 assert_eq!(
23099 themed, classic,
23100 "systemless-default must not change IsDialogEvent routing"
23101 );
23102 }
23103
23104 #[test]
23105 fn systemless_theme_does_not_change_findditem_hit_testing() {
23106 let classic = findditem_results_for_theme(UiThemeId::ClassicSystem7);
23110 let themed = findditem_results_for_theme(UiThemeId::SystemlessDefault);
23111
23112 assert_eq!(
23113 classic,
23114 vec![
23115 (0, TEST_SP + 8),
23116 (1, TEST_SP + 8),
23117 (-1, TEST_SP + 8),
23118 (-1, TEST_SP + 8),
23119 ]
23120 );
23121 assert_eq!(
23122 themed, classic,
23123 "systemless-default must not change FindDItem hit-test semantics"
23124 );
23125 }
23126
23127 #[test]
23128 fn systemless_theme_does_not_change_getsetditem_useritem_records() {
23129 let classic = getsetditem_useritem_record_results_for_theme(UiThemeId::ClassicSystem7);
23134 let themed = getsetditem_useritem_record_results_for_theme(UiThemeId::SystemlessDefault);
23135
23136 assert_eq!(classic.initial_get.item_type, 0);
23137 assert_eq!(classic.initial_get.item, 0x0012_3456);
23138 assert_eq!(classic.initial_get.rect, (12, 24, 36, 48));
23139 assert_eq!(classic.initial_get.stack_after, TEST_SP + 18);
23140 assert_eq!(classic.set_stack_after, TEST_SP + 16);
23141 assert_eq!(classic.stored_type, 0);
23142 assert_eq!(classic.stored_proc_ptr, 0x00AB_CDEF);
23143 assert_eq!(classic.stored_rect, (50, 60, 70, 80));
23144 assert_eq!(classic.post_set_get.item_type, 0);
23145 assert_eq!(classic.post_set_get.item, 0x00AB_CDEF);
23146 assert_eq!(classic.post_set_get.rect, (50, 60, 70, 80));
23147 assert_eq!(classic.post_set_get.stack_after, TEST_SP + 18);
23148 assert_eq!(
23149 themed, classic,
23150 "systemless-default must not change GetDItem/SetDItem userItem record semantics"
23151 );
23152 }
23153
23154 #[test]
23155 fn systemless_theme_does_not_change_getsetditem_text_control_records() {
23156 let classic = getsetditem_text_control_record_results_for_theme(UiThemeId::ClassicSystem7);
23162 let themed =
23163 getsetditem_text_control_record_results_for_theme(UiThemeId::SystemlessDefault);
23164
23165 assert_eq!(classic.text_initial_get.item_type, 16);
23166 assert_ne!(classic.text_initial_get.item, 0);
23167 assert_eq!(classic.text_initial_get.rect, (10, 20, 24, 160));
23168 assert_eq!(classic.text_initial_get.stack_after, TEST_SP + 18);
23169 assert_eq!(classic.text_set_stack_after, TEST_SP + 16);
23170 assert!(!classic.text_old_handle_mapped_after);
23171 let mapped_dialog = classic.text_new_handle_map_value_after.unwrap().0;
23172 let text_new_handle = classic.text_post_set_get.item;
23173 assert_ne!(mapped_dialog, 0);
23174 assert_eq!(classic.text_new_handle_map_value_after.unwrap().1, 0);
23175 assert_ne!(text_new_handle, classic.text_initial_get.item);
23176 assert_eq!(classic.text_ditl_handle_after, text_new_handle);
23177 assert_eq!(classic.text_ditl_rect_after, (12, 22, 28, 168));
23178 assert_eq!(classic.text_ditl_type_after, 16);
23179 assert_eq!(classic.text_stored_type_after, 16);
23180 assert_eq!(classic.text_stored_rect_after, (12, 22, 28, 168));
23181 assert_eq!(classic.text_cached_text_after, "Updated");
23182 assert_eq!(classic.text_handle_bytes_after, b"Updated".to_vec());
23183 assert_eq!(classic.text_post_set_get.item_type, 16);
23184 assert_eq!(classic.text_post_set_get.item, text_new_handle);
23185 assert_eq!(classic.text_post_set_get.rect, (12, 22, 28, 168));
23186 assert_eq!(classic.text_post_set_get.stack_after, TEST_SP + 18);
23187
23188 assert_eq!(classic.control_initial_get.item_type, 4);
23189 assert_ne!(classic.control_initial_get.item, 0);
23190 assert_eq!(classic.control_initial_get.rect, (30, 40, 50, 140));
23191 assert_eq!(classic.control_initial_get.stack_after, TEST_SP + 18);
23192 assert_eq!(classic.control_set_stack_after, TEST_SP + 16);
23193 assert!(!classic.control_old_handle_mapped_after);
23194 let control_new_handle = classic.control_post_set_get.item;
23195 assert_eq!(
23196 classic.control_new_handle_map_value_after.unwrap().0,
23197 mapped_dialog
23198 );
23199 assert_eq!(classic.control_new_handle_map_value_after.unwrap().1, 2);
23200 assert_ne!(control_new_handle, classic.control_initial_get.item);
23201 assert_eq!(classic.control_ditl_handle_after, control_new_handle);
23202 assert_eq!(classic.control_ditl_rect_after, (42, 52, 62, 172));
23203 assert_eq!(classic.control_ditl_type_after, 5);
23204 assert_eq!(classic.control_stored_type_after, 5);
23205 assert_eq!(classic.control_stored_rect_after, (42, 52, 62, 172));
23206 assert_eq!(classic.control_record_rect_after, (42, 52, 62, 172));
23207 assert_eq!(classic.control_value_after, Some(2));
23208 assert_eq!(classic.control_post_set_get.item_type, 5);
23209 assert_eq!(classic.control_post_set_get.item, control_new_handle);
23210 assert_eq!(classic.control_post_set_get.rect, (42, 52, 62, 172));
23211 assert_eq!(classic.control_post_set_get.stack_after, TEST_SP + 18);
23212 assert_eq!(
23213 themed, classic,
23214 "systemless-default must not change GetDItem/SetDItem text/control record semantics"
23215 );
23216 }
23217
23218 #[test]
23219 fn systemless_theme_does_not_change_hide_show_ditem_visibility_records() {
23220 let classic = hide_show_ditem_visibility_results_for_theme(UiThemeId::ClassicSystem7);
23225 let themed = hide_show_ditem_visibility_results_for_theme(UiThemeId::SystemlessDefault);
23226
23227 assert_eq!(classic.initial_get.item_type, 4);
23228 assert_eq!(classic.initial_get.rect, (10, 20, 30, 120));
23229 assert_eq!(classic.initial_get.stack_after, TEST_SP + 18);
23230 assert_eq!(classic.hide_stack_after, TEST_SP + 6);
23231 assert_eq!(classic.hidden_item_rect, (10, 16404, 30, 16504));
23232 assert_eq!(classic.hidden_saved_rect, Some((10, 20, 30, 120)));
23233 assert_eq!(classic.hidden_control_rect, (10, 16404, 30, 16504));
23234 assert_eq!(classic.hidden_get.item_type, 4);
23235 assert_eq!(classic.hidden_get.item, classic.initial_get.item);
23236 assert_eq!(classic.hidden_get.rect, (10, 16404, 30, 16504));
23237 assert_eq!(classic.hidden_get.stack_after, TEST_SP + 18);
23238 assert_eq!(classic.hidden_find, (-1, TEST_SP + 8));
23239 assert_eq!(classic.show_stack_after, TEST_SP + 6);
23240 assert_eq!(classic.restored_item_rect, (10, 20, 30, 120));
23241 assert_eq!(classic.restored_saved_rect, None);
23242 assert_eq!(classic.restored_control_rect, (10, 20, 30, 120));
23243 assert_eq!(classic.restored_get.item_type, 4);
23244 assert_eq!(classic.restored_get.item, classic.initial_get.item);
23245 assert_eq!(classic.restored_get.rect, (10, 20, 30, 120));
23246 assert_eq!(classic.restored_get.stack_after, TEST_SP + 18);
23247 assert_eq!(classic.restored_find, (0, TEST_SP + 8));
23248 assert_eq!(
23249 themed, classic,
23250 "systemless-default must not change HideDItem/ShowDItem visibility semantics"
23251 );
23252 }
23253
23254 #[test]
23255 fn systemless_theme_does_not_change_paramtext_storage_and_substitution() {
23256 let classic = paramtext_results_for_theme(UiThemeId::ClassicSystem7);
23261 let themed = paramtext_results_for_theme(UiThemeId::SystemlessDefault);
23262
23263 assert_eq!(
23264 classic.initial_slots,
23265 [
23266 b"Doc".to_vec(),
23267 b"42".to_vec(),
23268 Vec::<u8>::new(),
23269 b"Tail".to_vec(),
23270 ]
23271 );
23272 assert!(classic.initial_da_handles.iter().all(|handle| *handle != 0));
23273 assert_eq!(classic.initial_da_strings, classic.initial_slots);
23274 assert_eq!(classic.initial_stack_after, TEST_SP + 16);
23275 assert_eq!(classic.initial_substitution, "Cannot open Doc (42)Tail ^9");
23276 assert_eq!(
23277 classic.nil_slots,
23278 [
23279 b"NewDoc".to_vec(),
23280 b"42".to_vec(),
23281 Vec::<u8>::new(),
23282 b"Tail".to_vec(),
23283 ]
23284 );
23285 assert!(classic.nil_da_handles.iter().all(|handle| *handle != 0));
23286 assert_eq!(classic.nil_da_strings, classic.nil_slots);
23287 assert_eq!(classic.nil_da_handles[1], classic.initial_da_handles[1]);
23288 assert_eq!(classic.nil_da_handles[2], classic.initial_da_handles[2]);
23289 assert_eq!(classic.nil_da_handles[3], classic.initial_da_handles[3]);
23290 assert_eq!(classic.nil_stack_after, TEST_SP + 16);
23291 assert_eq!(classic.nil_substitution, "Cannot open NewDoc (42)Tail ^9");
23292 assert_eq!(
23293 themed, classic,
23294 "systemless-default must not change ParamText storage or substitution semantics"
23295 );
23296 }
23297
23298 #[test]
23299 fn systemless_theme_does_not_change_dialog_item_text_storage() {
23300 let classic = dialog_item_text_results_for_theme(UiThemeId::ClassicSystem7);
23305 let themed = dialog_item_text_results_for_theme(UiThemeId::SystemlessDefault);
23306
23307 assert_eq!(classic.initial_get_text, b"Old".to_vec());
23308 assert_eq!(classic.initial_get_stack_after, TEST_SP + 8);
23309 assert_eq!(classic.set_stack_after, TEST_SP + 8);
23310 assert_eq!(classic.handle_size_after_set, Some(11));
23311 assert_eq!(classic.handle_bytes_after_set, b"Edited Text".to_vec());
23312 assert_eq!(classic.cached_text_after_set, "Edited Text");
23313 assert_eq!(classic.post_set_get_text, b"Edited Text".to_vec());
23314 assert_eq!(classic.post_set_get_stack_after, TEST_SP + 8);
23315 assert_eq!(
23316 themed, classic,
23317 "systemless-default must not change Get/SetDialogItemText storage semantics"
23318 );
23319 }
23320
23321 #[test]
23322 fn systemless_theme_does_not_change_select_dialog_item_text_selection() {
23323 let classic = select_dialog_item_text_results_for_theme(UiThemeId::ClassicSystem7);
23329 let themed = select_dialog_item_text_results_for_theme(UiThemeId::SystemlessDefault);
23330
23331 assert_eq!(classic.whole_selection, (0, 5));
23332 assert_eq!(classic.whole_edit_field, 0);
23333 assert_eq!(classic.whole_te_selection, (0, 5));
23334 assert_eq!(classic.whole_stack_after, TEST_SP + 10);
23335 assert_eq!(classic.normalized_selection, (2, 5));
23336 assert_eq!(classic.normalized_edit_field, 0);
23337 assert_eq!(classic.normalized_te_selection, (2, 5));
23338 assert_eq!(classic.normalized_stack_after, TEST_SP + 10);
23339 assert_eq!(classic.non_edit_selection, (1, 3));
23340 assert_eq!(classic.non_edit_edit_field, 0);
23341 assert_eq!(classic.non_edit_te_selection, (2, 5));
23342 assert_eq!(classic.non_edit_stack_after, TEST_SP + 10);
23343 assert_eq!(
23344 themed, classic,
23345 "systemless-default must not change SelectDialogItemText selection semantics"
23346 );
23347 }
23348
23349 #[test]
23350 fn systemless_theme_does_not_change_drawdialog_useritem_pixels() {
23351 let classic = drawdialog_useritem_pixel_results_for_theme(UiThemeId::ClassicSystem7);
23356 let themed = drawdialog_useritem_pixel_results_for_theme(UiThemeId::SystemlessDefault);
23357
23358 assert_eq!(classic.0, 0xA5);
23359 assert_eq!(classic.1, TEST_SP + 4);
23360 assert_eq!(classic.2, 0xCAFE_BABE);
23361 assert_eq!(
23362 themed, classic,
23363 "systemless-default must not change DrawDialog userItem pixel preservation"
23364 );
23365 }
23366
23367 #[test]
23368 fn systemless_theme_does_not_change_drawdialog_icon_item_pixels() {
23369 let classic = drawdialog_icon_item_pixel_results_for_theme(UiThemeId::ClassicSystem7);
23374 let themed = drawdialog_icon_item_pixel_results_for_theme(UiThemeId::SystemlessDefault);
23375
23376 assert_eq!(classic.0, [0xFF, 0xFF, 0xFF]);
23377 assert_eq!(classic.1, TEST_SP + 4);
23378 assert_eq!(classic.2, 0xCAFE_BABE);
23379 assert_eq!(
23380 themed, classic,
23381 "systemless-default must not change DrawDialog ICON item pixels"
23382 );
23383 }
23384
23385 #[test]
23386 fn systemless_theme_does_not_change_drawdialog_cicn_item_pixels() {
23387 let classic = drawdialog_cicn_item_pixel_results_for_theme(UiThemeId::ClassicSystem7);
23392 let themed = drawdialog_cicn_item_pixel_results_for_theme(UiThemeId::SystemlessDefault);
23393
23394 assert_eq!(classic.0, [0x33, 0x55, 0x77]);
23395 assert_eq!(classic.1, TEST_SP + 4);
23396 assert_eq!(classic.2, 0xCAFE_BABE);
23397 assert_eq!(
23398 themed, classic,
23399 "systemless-default must not change DrawDialog cicn item pixels"
23400 );
23401 }
23402
23403 #[test]
23404 fn systemless_theme_does_not_change_drawdialog_picture_item_pixels() {
23405 let classic = drawdialog_picture_item_pixel_results_for_theme(UiThemeId::ClassicSystem7);
23410 let themed = drawdialog_picture_item_pixel_results_for_theme(UiThemeId::SystemlessDefault);
23411
23412 assert_eq!(classic.0, [0xFF, 0xFF, 0xFF]);
23413 assert_eq!(classic.1, TEST_SP + 4);
23414 assert_eq!(classic.2, 0xCAFE_BABE);
23415 assert_eq!(
23416 themed, classic,
23417 "systemless-default must not change DrawDialog PICT item pixels"
23418 );
23419 }
23420
23421 #[test]
23422 fn dialog_select_disabled_item_hit_returns_false_and_leaves_outputs_unchanged() {
23423 let (mut disp, mut cpu, mut bus) = setup();
23426 let dialog_ptr = bus.alloc(170);
23427 let event_ptr = 0x300000u32;
23428 let dialog_out_ptr = 0x300100u32;
23429 let item_hit_ptr = 0x300104u32;
23430
23431 disp.front_window = dialog_ptr;
23432 bus.write_word(dialog_ptr + 8, 0);
23433 bus.write_word(dialog_ptr + 10, 0);
23434 bus.write_word(dialog_ptr + 16, 0);
23435 bus.write_word(dialog_ptr + 18, 0);
23436 bus.write_word(dialog_ptr + 20, 100);
23437 bus.write_word(dialog_ptr + 22, 160);
23438 disp.dialog_items.insert(
23439 dialog_ptr,
23440 vec![DialogItem {
23441 item_type: 0x80, rect: (20, 30, 60, 110),
23443 text: String::new(),
23444 resource_id: 0,
23445 proc_ptr: 0,
23446 sel_start: 0,
23447 sel_end: 0,
23448 }],
23449 );
23450
23451 bus.write_word(event_ptr, 1); bus.write_long(event_ptr + 2, 0);
23453 bus.write_long(event_ptr + 6, 0);
23454 bus.write_word(event_ptr + 10, 130);
23455 bus.write_word(event_ptr + 12, 240);
23456 bus.write_word(event_ptr + 14, 0x0080);
23457
23458 bus.write_long(dialog_out_ptr, 0x12345678);
23459 bus.write_word(item_hit_ptr, 0x7F7F);
23460
23461 bus.write_long(TEST_SP, item_hit_ptr);
23462 bus.write_long(TEST_SP + 4, dialog_out_ptr);
23463 bus.write_long(TEST_SP + 8, event_ptr);
23464
23465 let result = disp.dispatch_dialog(true, 0x180, &mut cpu, &mut bus);
23466 assert!(result.unwrap().is_ok());
23467 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 12);
23468 assert_eq!(bus.read_word(TEST_SP + 12), 0);
23469 assert_eq!(bus.read_long(dialog_out_ptr), 0x12345678);
23470 assert_eq!(bus.read_word(item_hit_ptr), 0x7F7F);
23471 }
23472
23473 #[test]
23474 fn dialog_select_keydown_without_edit_text_item_returns_false() {
23475 let (mut disp, mut cpu, mut bus) = setup();
23478 let dialog_ptr = bus.alloc(170);
23479 let event_ptr = 0x300000u32;
23480 let dialog_out_ptr = 0x300100u32;
23481 let item_hit_ptr = 0x300104u32;
23482
23483 disp.front_window = dialog_ptr;
23484 bus.write_word(dialog_ptr + 8, 0);
23485 bus.write_word(dialog_ptr + 10, 0);
23486 bus.write_word(dialog_ptr + 16, 0);
23487 bus.write_word(dialog_ptr + 18, 0);
23488 bus.write_word(dialog_ptr + 20, 100);
23489 bus.write_word(dialog_ptr + 22, 160);
23490 bus.write_word(dialog_ptr + 164, 0); disp.dialog_items.insert(
23492 dialog_ptr,
23493 vec![DialogItem {
23494 item_type: 0, rect: (20, 30, 60, 110),
23496 text: String::new(),
23497 resource_id: 0,
23498 proc_ptr: 0,
23499 sel_start: 0,
23500 sel_end: 0,
23501 }],
23502 );
23503
23504 bus.write_word(event_ptr, 3); bus.write_long(event_ptr + 2, 0x00000041); bus.write_long(event_ptr + 6, 0);
23507 bus.write_word(event_ptr + 10, 0);
23508 bus.write_word(event_ptr + 12, 0);
23509 bus.write_word(event_ptr + 14, 0);
23510
23511 bus.write_long(TEST_SP, item_hit_ptr);
23512 bus.write_long(TEST_SP + 4, dialog_out_ptr);
23513 bus.write_long(TEST_SP + 8, event_ptr);
23514
23515 let result = disp.dispatch_dialog(true, 0x180, &mut cpu, &mut bus);
23516 assert!(result.unwrap().is_ok());
23517 assert_eq!(bus.read_word(TEST_SP + 12), 0);
23518 }
23519
23520 #[test]
23521 fn dialog_select_keydown_with_enabled_edit_text_returns_hit() {
23522 let (mut disp, mut cpu, mut bus) = setup();
23525 let dialog_ptr = bus.alloc(170);
23526 let event_ptr = 0x300000u32;
23527 let dialog_out_ptr = 0x300100u32;
23528 let item_hit_ptr = 0x300104u32;
23529
23530 disp.front_window = dialog_ptr;
23531 bus.write_word(dialog_ptr + 8, 0);
23532 bus.write_word(dialog_ptr + 10, 0);
23533 bus.write_word(dialog_ptr + 16, 0);
23534 bus.write_word(dialog_ptr + 18, 0);
23535 bus.write_word(dialog_ptr + 20, 100);
23536 bus.write_word(dialog_ptr + 22, 160);
23537 bus.write_word(dialog_ptr + 164, 0); disp.dialog_items.insert(
23539 dialog_ptr,
23540 vec![DialogItem {
23541 item_type: 16, rect: (20, 30, 60, 110),
23543 text: "Hello".to_string(),
23544 resource_id: 0,
23545 proc_ptr: 0,
23546 sel_start: 0,
23547 sel_end: 0,
23548 }],
23549 );
23550
23551 bus.write_word(event_ptr, 3); bus.write_long(event_ptr + 2, 0x00000041); bus.write_long(event_ptr + 6, 0);
23554 bus.write_word(event_ptr + 10, 0);
23555 bus.write_word(event_ptr + 12, 0);
23556 bus.write_word(event_ptr + 14, 0);
23557
23558 bus.write_long(TEST_SP, item_hit_ptr);
23559 bus.write_long(TEST_SP + 4, dialog_out_ptr);
23560 bus.write_long(TEST_SP + 8, event_ptr);
23561
23562 let result = disp.dispatch_dialog(true, 0x180, &mut cpu, &mut bus);
23563 assert!(result.unwrap().is_ok());
23564 assert_eq!(bus.read_word(TEST_SP + 12), 0xFFFF);
23565 assert_eq!(bus.read_long(dialog_out_ptr), dialog_ptr);
23566 assert_eq!(bus.read_word(item_hit_ptr), 1);
23567 }
23568
23569 #[test]
23570 fn dialog_select_null_event_blinks_active_edit_text_insertion_caret() {
23571 let (mut disp, mut cpu, mut bus) = setup_with_port();
23575 let dialog_ptr = bus.alloc(170);
23576 let event_ptr = bus.alloc(16);
23577 let dialog_out_ptr = bus.alloc(4);
23578 let item_hit_ptr = bus.alloc(2);
23579 let items_handle = bus.alloc(4);
23580 let ditl_ptr = bus.alloc(16);
23581 let text_item_handle = TrapDispatcher::allocate_handle_with_data(&mut bus, 0);
23582 let text_h = make_te_with_text(&mut disp, &mut bus, b"");
23583 let te_ptr = bus.read_long(text_h);
23584 let (screen_base, row_bytes, _screen_w, _screen_h, _pixel_size) = disp.screen_mode;
23585
23586 for i in 0..(row_bytes * 80) {
23587 bus.write_byte(screen_base + i, 0);
23588 }
23589
23590 disp.front_window = dialog_ptr;
23591 bus.write_word(dialog_ptr + 8, 0);
23592 bus.write_word(dialog_ptr + 10, 0);
23593 bus.write_word(dialog_ptr + 16, 0);
23594 bus.write_word(dialog_ptr + 18, 0);
23595 bus.write_word(dialog_ptr + 20, 100);
23596 bus.write_word(dialog_ptr + 22, 160);
23597 bus.write_long(dialog_ptr + 156, items_handle);
23598 bus.write_long(dialog_ptr + 160, text_h);
23599 bus.write_word(dialog_ptr + 164, 0);
23600
23601 bus.write_long(items_handle, ditl_ptr);
23602 bus.write_word(ditl_ptr, 0);
23603 bus.write_long(ditl_ptr + 2, text_item_handle);
23604 bus.write_word(ditl_ptr + 6, 20);
23605 bus.write_word(ditl_ptr + 8, 20);
23606 bus.write_word(ditl_ptr + 10, 38);
23607 bus.write_word(ditl_ptr + 12, 120);
23608 bus.write_byte(ditl_ptr + 14, 16);
23609 bus.write_byte(ditl_ptr + 15, 0);
23610
23611 disp.dialog_items.insert(
23612 dialog_ptr,
23613 vec![DialogItem {
23614 item_type: 16,
23615 rect: (20, 20, 38, 120),
23616 text: String::new(),
23617 resource_id: 0,
23618 proc_ptr: 0,
23619 sel_start: 0,
23620 sel_end: 0,
23621 }],
23622 );
23623
23624 let dispatch_dialog_select_event = |disp: &mut TrapDispatcher,
23625 cpu: &mut MockCpu,
23626 bus: &mut MacMemoryBus,
23627 what: u16,
23628 tick: u32| {
23629 disp.tick_count = tick;
23630 bus.write_long(crate::memory::globals::addr::TICKS, tick);
23631 cpu.write_reg(Register::A7, TEST_SP);
23632 bus.write_word(event_ptr, what);
23633 bus.write_long(event_ptr + 2, 0);
23634 bus.write_long(event_ptr + 6, 0);
23635 bus.write_word(event_ptr + 10, 25);
23636 bus.write_word(event_ptr + 12, 25);
23637 bus.write_word(event_ptr + 14, 0);
23638 bus.write_long(dialog_out_ptr, 0xDEAD_BEEF);
23639 bus.write_word(item_hit_ptr, 0xCAFE);
23640 bus.write_long(TEST_SP, item_hit_ptr);
23641 bus.write_long(TEST_SP + 4, dialog_out_ptr);
23642 bus.write_long(TEST_SP + 8, event_ptr);
23643 disp.dispatch_dialog(true, 0x180, cpu, bus)
23644 .unwrap()
23645 .unwrap();
23646 };
23647
23648 dispatch_dialog_select_event(&mut disp, &mut cpu, &mut bus, 1, 100);
23649 assert_eq!(bus.read_word(TEST_SP + 12), 0xFFFF);
23650 assert_eq!(bus.read_long(dialog_out_ptr), dialog_ptr);
23651 assert_eq!(bus.read_word(item_hit_ptr), 1);
23652 assert_eq!(bus.read_word(te_ptr + TrapDispatcher::TE_ACTIVE_OFFSET), 1);
23653 assert_eq!(
23654 bus.read_long(te_ptr + TrapDispatcher::TE_CARET_TIME_OFFSET),
23655 100
23656 );
23657 assert_eq!(
23658 bus.read_word(te_ptr + TrapDispatcher::TE_CARET_STATE_OFFSET),
23659 0
23660 );
23661 assert!(
23662 screen_pixel_is_set(&bus, screen_base, row_bytes, 1, 0),
23663 "DialogSelect editText mouse-down should display the insertion caret"
23664 );
23665
23666 dispatch_dialog_select_event(&mut disp, &mut cpu, &mut bus, 0, 131);
23667 assert_eq!(bus.read_word(TEST_SP + 12), 0);
23668 assert_eq!(bus.read_long(dialog_out_ptr), 0xDEAD_BEEF);
23669 assert_eq!(bus.read_word(item_hit_ptr), 0xCAFE);
23670 assert_eq!(
23671 bus.read_word(te_ptr + TrapDispatcher::TE_CARET_STATE_OFFSET),
23672 0
23673 );
23674 assert!(
23675 screen_pixel_is_set(&bus, screen_base, row_bytes, 1, 0),
23676 "DialogSelect null event before 32 ticks should keep the caret visible"
23677 );
23678
23679 dispatch_dialog_select_event(&mut disp, &mut cpu, &mut bus, 0, 132);
23680 assert_eq!(
23681 bus.read_long(te_ptr + TrapDispatcher::TE_CARET_TIME_OFFSET),
23682 132
23683 );
23684 assert_eq!(
23685 bus.read_word(te_ptr + TrapDispatcher::TE_CARET_STATE_OFFSET),
23686 1
23687 );
23688 assert!(
23689 !screen_pixel_is_set(&bus, screen_base, row_bytes, 1, 0),
23690 "DialogSelect null event at the 32-tick boundary should hide the caret"
23691 );
23692
23693 dispatch_dialog_select_event(&mut disp, &mut cpu, &mut bus, 0, 164);
23694 assert_eq!(
23695 bus.read_long(te_ptr + TrapDispatcher::TE_CARET_TIME_OFFSET),
23696 164
23697 );
23698 assert_eq!(
23699 bus.read_word(te_ptr + TrapDispatcher::TE_CARET_STATE_OFFSET),
23700 0
23701 );
23702 assert!(
23703 screen_pixel_is_set(&bus, screen_base, row_bytes, 1, 0),
23704 "the next DialogSelect null-event blink interval should show the caret again"
23705 );
23706 }
23707
23708 #[test]
23711 fn draw_dialog_pops_4_bytes() {
23712 let (mut disp, mut cpu, mut bus) = setup();
23713 bus.write_long(TEST_SP, 0x200000);
23715
23716 let result = disp.dispatch_dialog(true, 0x181, &mut cpu, &mut bus);
23717 assert!(result.unwrap().is_ok());
23718 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 4);
23719 }
23720
23721 #[test]
23722 fn draw_dialog_repaints_dialog_background_for_known_dialog() {
23723 let (mut disp, mut cpu, mut bus) = setup();
23726 let dialog_ptr = bus.alloc(170);
23727 let (screen_base, row_bytes, _w, _h, pixel_size) = disp.screen_mode;
23728
23729 bus.write_word(dialog_ptr + 8, 0);
23730 bus.write_word(dialog_ptr + 10, 0);
23731 bus.write_word(dialog_ptr + 16, 0);
23732 bus.write_word(dialog_ptr + 18, 0);
23733 bus.write_word(dialog_ptr + 20, 30);
23734 bus.write_word(dialog_ptr + 22, 40);
23735 disp.dialog_items.insert(
23736 dialog_ptr,
23737 vec![DialogItem {
23738 item_type: 4, rect: (8, 8, 20, 32),
23740 text: "OK".to_string(),
23741 resource_id: 0,
23742 proc_ptr: 0,
23743 sel_start: 0,
23744 sel_end: 0,
23745 }],
23746 );
23747
23748 let probe_x = 4u32;
23749 let probe_y = 4u32;
23750 let probe_addr = if pixel_size == 8 {
23751 screen_base + probe_y * row_bytes + probe_x
23752 } else {
23753 screen_base + probe_y * row_bytes + (probe_x / 8)
23754 };
23755 let probe_bit = 1 << (7 - (probe_x % 8));
23756 if pixel_size == 8 {
23757 bus.write_byte(probe_addr, 0xFF); } else {
23759 bus.write_byte(probe_addr, bus.read_byte(probe_addr) | probe_bit);
23760 }
23761
23762 bus.write_long(TEST_SP, dialog_ptr);
23763 let result = disp.dispatch_dialog(true, 0x181, &mut cpu, &mut bus);
23764 assert!(result.unwrap().is_ok());
23765 if pixel_size == 8 {
23766 assert_eq!(bus.read_byte(probe_addr), 0); } else {
23768 assert_eq!(bus.read_byte(probe_addr) & probe_bit, 0);
23769 }
23770 }
23771
23772 #[test]
23773 fn draw_dialog_repaints_document_proc_title_chrome() {
23774 let screen_base = 0x300000u32;
23778 let row_bytes = 64u32;
23779 let (mut disp, mut cpu, mut bus) = setup();
23780 disp.set_screen_mode_for_test(screen_base, row_bytes, 512, 342, 1);
23781
23782 let dialog_ptr = bus.alloc(170);
23783 bus.write_word(dialog_ptr + 6, 0);
23784 bus.write_word(dialog_ptr + 8, (-40i16) as u16);
23785 bus.write_word(dialog_ptr + 10, (-80i16) as u16);
23786 bus.write_word(dialog_ptr + 16, 0);
23787 bus.write_word(dialog_ptr + 18, 0);
23788 bus.write_word(dialog_ptr + 20, 80);
23789 bus.write_word(dialog_ptr + 22, 160);
23790 bus.write_byte(dialog_ptr + 112, 0);
23791 let title_ptr = bus.alloc(16);
23792 bus.write_pstring(title_ptr, b"Modeless");
23793 let title_handle = bus.alloc(4);
23794 bus.write_long(title_handle, title_ptr);
23795 bus.write_long(dialog_ptr + 134, title_handle);
23796 disp.window_proc_ids.insert(dialog_ptr, 0);
23797 disp.dialog_items.insert(dialog_ptr, Vec::new());
23798
23799 bus.write_long(TEST_SP, dialog_ptr);
23800 let result = disp.dispatch_dialog(true, 0x181, &mut cpu, &mut bus);
23801 assert!(result.unwrap().is_ok());
23802
23803 assert!(
23804 screen_pixel_is_set(&bus, screen_base, row_bytes, 85, 24),
23805 "documentProc DrawDialog should redraw active title-bar stripes"
23806 );
23807 assert!(
23808 screen_pixel_is_set(&bus, screen_base, row_bytes, 79, 21),
23809 "documentProc DrawDialog should redraw the title-bar border"
23810 );
23811 }
23812
23813 #[test]
23814 fn draw_dialog_radio_items_use_control_value_state() {
23815 let screen_base = 0x300000u32;
23819 let row_bytes = 64u32;
23820 let bounds = (40, 40, 120, 220);
23821 let dialog_ptr = 0x2000;
23822 let items = vec![DialogItem {
23823 item_type: 6,
23824 rect: (20, 20, 40, 160),
23825 text: "Selected".to_string(),
23826 resource_id: 0,
23827 proc_ptr: 0,
23828 sel_start: 0,
23829 sel_end: 0,
23830 }];
23831 let (mut disp, _cpu, mut bus) = setup();
23832 disp.set_screen_mode_for_test(screen_base, row_bytes, 512, 342, 1);
23833 disp.dialog_control_values.insert((dialog_ptr, 1), 1);
23834
23835 disp.draw_dialog(&mut bus, bounds, 1, "", &items, 0, "", 0, false, dialog_ptr);
23836
23837 assert!(
23838 screen_pixel_is_set(&bus, screen_base, row_bytes, 67, 69),
23839 "selected radio item should draw the central dot"
23840 );
23841 }
23842
23843 #[test]
23844 fn draw_dialog_queues_user_item_draw_proc_for_known_dialog() {
23845 let (mut disp, mut cpu, mut bus) = setup();
23848 let dialog_ptr = bus.alloc(170);
23849
23850 bus.write_word(dialog_ptr + 16, 0);
23851 bus.write_word(dialog_ptr + 18, 0);
23852 bus.write_word(dialog_ptr + 20, 80);
23853 bus.write_word(dialog_ptr + 22, 120);
23854 disp.dialog_items.insert(
23855 dialog_ptr,
23856 vec![
23857 DialogItem {
23858 item_type: 0,
23859 rect: (8, 8, 24, 48),
23860 proc_ptr: 0x500000,
23861 ..Default::default()
23862 },
23863 DialogItem {
23864 item_type: 0,
23865 rect: (100, 8, 120, 48),
23866 proc_ptr: 0x600000,
23867 ..Default::default()
23868 },
23869 DialogItem {
23870 item_type: 4,
23871 rect: (40, 8, 60, 48),
23872 text: "OK".to_string(),
23873 ..Default::default()
23874 },
23875 ],
23876 );
23877
23878 bus.write_long(TEST_SP, dialog_ptr);
23879 let result = disp.dispatch_dialog(true, 0x181, &mut cpu, &mut bus);
23880 assert!(result.unwrap().is_ok());
23881
23882 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 4);
23883 assert_eq!(
23884 disp.modeless_dialog_draw_proc_queue
23885 .iter()
23886 .copied()
23887 .collect::<Vec<_>>(),
23888 vec![(dialog_ptr, 0x500000, 1)]
23889 );
23890 }
23891
23892 #[test]
23893 fn draw_button_systemless_theme_routes_dialog_chrome_through_provider() {
23894 let screen_base = 0x300000u32;
23895 let row_bytes = 64u32;
23896
23897 let (mut classic, _classic_cpu, mut classic_bus) = setup();
23898 classic.set_screen_mode_for_test(screen_base, row_bytes, 512, 342, 1);
23899 classic.draw_button(&mut classic_bus, 20, 20, 40, 80, "", true);
23900
23901 let (mut themed, _themed_cpu, mut themed_bus) = setup();
23902 themed.set_ui_theme_id(UiThemeId::SystemlessDefault);
23903 themed.set_screen_mode_for_test(screen_base, row_bytes, 512, 342, 1);
23904 themed.draw_button(&mut themed_bus, 20, 20, 40, 80, "", true);
23905
23906 assert!(
23907 !screen_pixel_is_set(&classic_bus, screen_base, row_bytes, 16, 16),
23908 "classic default-button round rect should leave its outer corner as background"
23909 );
23910 assert!(
23911 screen_pixel_is_set(&themed_bus, screen_base, row_bytes, 16, 16),
23912 "systemless-default provider chrome should own the default-button outline corner"
23913 );
23914 }
23915
23916 #[test]
23917 fn draw_checkbox_and_radio_systemless_theme_route_dialog_chrome_through_provider() {
23918 let screen_base = 0x300000u32;
23919 let row_bytes = 64u32;
23920
23921 let (mut classic, _classic_cpu, mut classic_bus) = setup();
23922 classic.set_screen_mode_for_test(screen_base, row_bytes, 512, 342, 1);
23923 classic.draw_checkbox(&mut classic_bus, 20, 20, 40, 120, "", true);
23924 classic.draw_radio(&mut classic_bus, 50, 20, 70, 120, "", true);
23925
23926 let (mut themed, _themed_cpu, mut themed_bus) = setup();
23927 themed.set_ui_theme_id(UiThemeId::SystemlessDefault);
23928 themed.set_screen_mode_for_test(screen_base, row_bytes, 512, 342, 1);
23929 themed.draw_checkbox(&mut themed_bus, 20, 20, 40, 120, "", true);
23930 themed.draw_radio(&mut themed_bus, 50, 20, 70, 120, "", true);
23931
23932 let classic_checkbox_pixels =
23933 count_set_pixels(&classic_bus, screen_base, row_bytes, 21, 20, 33, 32);
23934 let themed_checkbox_pixels =
23935 count_set_pixels(&themed_bus, screen_base, row_bytes, 21, 20, 33, 32);
23936 assert!(
23937 themed_checkbox_pixels > classic_checkbox_pixels,
23938 "systemless-default dialog checkbox provider should draw a denser selected mark ({themed_checkbox_pixels} <= {classic_checkbox_pixels})"
23939 );
23940 let classic_radio_pixels =
23941 count_set_pixels(&classic_bus, screen_base, row_bytes, 51, 20, 63, 32);
23942 let themed_radio_pixels =
23943 count_set_pixels(&themed_bus, screen_base, row_bytes, 51, 20, 63, 32);
23944 assert!(
23945 themed_radio_pixels > classic_radio_pixels,
23946 "systemless-default dialog radio provider should draw a denser selected mark ({themed_radio_pixels} <= {classic_radio_pixels})"
23947 );
23948 }
23949
23950 #[test]
23951 fn draw_popup_control_systemless_theme_routes_dialog_chrome_through_provider() {
23952 let screen_base = 0x300000u32;
23953 let row_bytes = 64u32;
23954
23955 let (mut classic, _classic_cpu, mut classic_bus) = setup();
23956 classic.set_screen_mode_for_test(screen_base, row_bytes, 512, 342, 1);
23957 classic.draw_popup_control(&mut classic_bus, 20, 20, 40, 120, "");
23958
23959 let (mut themed, _themed_cpu, mut themed_bus) = setup();
23960 themed.set_ui_theme_id(UiThemeId::SystemlessDefault);
23961 themed.set_screen_mode_for_test(screen_base, row_bytes, 512, 342, 1);
23962 themed.draw_popup_control(&mut themed_bus, 20, 20, 40, 120, "");
23963
23964 assert!(
23965 screen_pixel_is_set(&classic_bus, screen_base, row_bytes, 120, 39),
23966 "classic popup CDEF should draw its one-pixel offset shadow"
23967 );
23968 assert!(
23969 !screen_pixel_is_set(&themed_bus, screen_base, row_bytes, 120, 39),
23970 "systemless-default popup provider should not draw the classic offset shadow"
23971 );
23972 }
23973
23974 #[test]
23975 fn draw_edit_text_systemless_theme_routes_caret_through_provider() {
23976 let screen_base = 0x300000u32;
23977 let row_bytes = 64u32;
23978
23979 let (mut classic, _classic_cpu, mut classic_bus) = setup();
23980 classic.set_screen_mode_for_test(screen_base, row_bytes, 512, 342, 1);
23981 classic.draw_edit_text(&mut classic_bus, 20, 20, 40, 100, "", false);
23982
23983 let (mut themed, _themed_cpu, mut themed_bus) = setup();
23984 themed.set_ui_theme_id(UiThemeId::SystemlessDefault);
23985 themed.set_screen_mode_for_test(screen_base, row_bytes, 512, 342, 1);
23986 themed.draw_edit_text(&mut themed_bus, 20, 20, 40, 100, "", false);
23987
23988 assert!(
23989 !screen_pixel_is_set(&classic_bus, screen_base, row_bytes, 22, 22),
23990 "classic editText caret should remain a single vertical bar"
23991 );
23992 assert!(
23993 screen_pixel_is_set(&themed_bus, screen_base, row_bytes, 22, 22),
23994 "systemless-default provider should draw the caret cap"
23995 );
23996 }
23997
23998 #[test]
23999 fn draw_edit_text_systemless_theme_routes_field_frame_through_provider() {
24000 let screen_base = 0x300000u32;
24001 let row_bytes = 64u32;
24002
24003 let (mut classic, _classic_cpu, mut classic_bus) = setup();
24004 classic.set_screen_mode_for_test(screen_base, row_bytes, 512, 342, 1);
24005 classic.draw_edit_text(&mut classic_bus, 20, 20, 40, 100, "", false);
24006
24007 let (mut themed, _themed_cpu, mut themed_bus) = setup();
24008 themed.set_ui_theme_id(UiThemeId::SystemlessDefault);
24009 themed.set_screen_mode_for_test(screen_base, row_bytes, 512, 342, 1);
24010 themed.draw_edit_text(&mut themed_bus, 20, 20, 40, 100, "", false);
24011
24012 assert!(
24013 !screen_pixel_is_set(&classic_bus, screen_base, row_bytes, 19, 22),
24014 "classic editText field should keep the upper interior clear"
24015 );
24016 assert!(
24017 screen_pixel_is_set(&themed_bus, screen_base, row_bytes, 19, 22),
24018 "systemless-default provider should draw focused editText field chrome"
24019 );
24020 }
24021
24022 #[test]
24023 fn draw_dialog_systemless_theme_routes_disabled_edit_text_state_through_provider() {
24024 let screen_base = 0x300000u32;
24025 let row_bytes = 64u32;
24026 let bounds = (40, 40, 110, 190);
24027 let items = vec![DialogItem {
24028 item_type: 0x80 | 16,
24029 rect: (20, 20, 44, 130),
24030 text: "Disabled".to_string(),
24031 resource_id: 0,
24032 proc_ptr: 0,
24033 sel_start: 0,
24034 sel_end: 0,
24035 }];
24036
24037 let (mut classic, _classic_cpu, mut classic_bus) = setup();
24038 classic.set_screen_mode_for_test(screen_base, row_bytes, 512, 342, 1);
24039 classic.draw_dialog(
24040 &mut classic_bus,
24041 bounds,
24042 1,
24043 "",
24044 &items,
24045 0,
24046 "",
24047 0,
24048 false,
24049 0x2000,
24050 );
24051
24052 let (mut themed, _themed_cpu, mut themed_bus) = setup();
24053 themed.set_ui_theme_id(UiThemeId::SystemlessDefault);
24054 themed.set_screen_mode_for_test(screen_base, row_bytes, 512, 342, 1);
24055 themed.draw_dialog(
24056 &mut themed_bus,
24057 bounds,
24058 1,
24059 "",
24060 &items,
24061 0,
24062 "",
24063 0,
24064 false,
24065 0x2000,
24066 );
24067
24068 let disabled_inner_x =
24069 bounds.1 + items[0].rect.1 - TrapDispatcher::EDIT_TEXT_FRAME_OUTSET + 2;
24070 let disabled_inner_y = bounds.0 + items[0].rect.0 + 2;
24071 assert!(
24072 !screen_pixel_is_set(
24073 &classic_bus,
24074 screen_base,
24075 row_bytes,
24076 disabled_inner_x,
24077 disabled_inner_y
24078 ),
24079 "classic editText field should keep the disabled-state inner frame pixel clear"
24080 );
24081 assert!(
24082 screen_pixel_is_set(
24083 &themed_bus,
24084 screen_base,
24085 row_bytes,
24086 disabled_inner_x,
24087 disabled_inner_y
24088 ),
24089 "systemless-default provider should receive disabled editText field state"
24090 );
24091 }
24092
24093 #[test]
24094 fn draw_dialog_systemless_theme_routes_disabled_standard_controls_through_provider() {
24095 let screen_base = 0x300000u32;
24096 let row_bytes = 64u32;
24097 let bounds = (40, 40, 140, 210);
24098 let items = vec![
24099 DialogItem {
24100 item_type: 0x80 | 4,
24101 rect: (20, 20, 40, 82),
24102 text: String::new(),
24103 resource_id: 0,
24104 proc_ptr: 0,
24105 sel_start: 0,
24106 sel_end: 0,
24107 },
24108 DialogItem {
24109 item_type: 0x80 | 5,
24110 rect: (52, 20, 72, 120),
24111 text: String::new(),
24112 resource_id: 0,
24113 proc_ptr: 0,
24114 sel_start: 0,
24115 sel_end: 0,
24116 },
24117 DialogItem {
24118 item_type: 0x80 | 6,
24119 rect: (84, 20, 104, 120),
24120 text: String::new(),
24121 resource_id: 0,
24122 proc_ptr: 0,
24123 sel_start: 0,
24124 sel_end: 0,
24125 },
24126 ];
24127
24128 let (mut classic, _classic_cpu, mut classic_bus) = setup();
24129 classic.set_screen_mode_for_test(screen_base, row_bytes, 512, 342, 1);
24130 classic.draw_dialog(
24131 &mut classic_bus,
24132 bounds,
24133 1,
24134 "",
24135 &items,
24136 0,
24137 "",
24138 0,
24139 false,
24140 0x2000,
24141 );
24142
24143 let (mut themed, _themed_cpu, mut themed_bus) = setup();
24144 themed.set_ui_theme_id(UiThemeId::SystemlessDefault);
24145 themed.set_screen_mode_for_test(screen_base, row_bytes, 512, 342, 1);
24146 themed.draw_dialog(
24147 &mut themed_bus,
24148 bounds,
24149 1,
24150 "",
24151 &items,
24152 0,
24153 "",
24154 0,
24155 false,
24156 0x2000,
24157 );
24158
24159 let probes = [
24163 (
24164 bounds.1 + items[0].rect.1 + 2,
24165 bounds.0 + items[0].rect.0 + 2,
24166 ),
24167 (
24168 bounds.1 + items[2].rect.1 + 2,
24169 bounds.0
24170 + items[2].rect.0
24171 + (items[2].rect.2
24172 - items[2].rect.0
24173 - TrapDispatcher::STANDARD_CONTROL_MARK_SIZE)
24174 / 2,
24175 ),
24176 ];
24177 for (x, y) in probes {
24178 assert!(
24179 !screen_pixel_is_set(&classic_bus, screen_base, row_bytes, x, y),
24180 "classic disabled DITL controls should keep standard appearance at ({x},{y})"
24181 );
24182 assert!(
24183 screen_pixel_is_set(&themed_bus, screen_base, row_bytes, x, y),
24184 "systemless-default provider should receive disabled DITL control state at ({x},{y})"
24185 );
24186 }
24187 }
24188
24189 #[test]
24190 fn draw_dialog_classic_keeps_disabled_checkbox_and_radio_titles_undimmed() {
24191 let screen_base = 0x300000u32;
24192 let row_bytes = 64u32;
24193 let bounds = (40, 40, 190, 230);
24194 let items = vec![
24195 DialogItem {
24196 item_type: 5,
24197 rect: (20, 20, 40, 150),
24198 text: "Sound".to_string(),
24199 resource_id: 0,
24200 proc_ptr: 0,
24201 sel_start: 0,
24202 sel_end: 0,
24203 },
24204 DialogItem {
24205 item_type: 0x80 | 5,
24206 rect: (52, 20, 72, 150),
24207 text: "Sound".to_string(),
24208 resource_id: 0,
24209 proc_ptr: 0,
24210 sel_start: 0,
24211 sel_end: 0,
24212 },
24213 DialogItem {
24214 item_type: 6,
24215 rect: (84, 20, 104, 150),
24216 text: "Music".to_string(),
24217 resource_id: 0,
24218 proc_ptr: 0,
24219 sel_start: 0,
24220 sel_end: 0,
24221 },
24222 DialogItem {
24223 item_type: 0x80 | 6,
24224 rect: (116, 20, 136, 150),
24225 text: "Music".to_string(),
24226 resource_id: 0,
24227 proc_ptr: 0,
24228 sel_start: 0,
24229 sel_end: 0,
24230 },
24231 ];
24232
24233 let (mut dispatcher, _cpu, mut bus) = setup();
24234 dispatcher.set_screen_mode_for_test(screen_base, row_bytes, 512, 342, 1);
24235 dispatcher.draw_dialog(&mut bus, bounds, 1, "", &items, 0, "", 0, false, 0x2000);
24236
24237 let label_left = bounds.1
24238 + items[0].rect.1
24239 + TrapDispatcher::STANDARD_CONTROL_MARK_LEFT_INSET
24240 + TrapDispatcher::STANDARD_CONTROL_MARK_SIZE
24241 + TrapDispatcher::STANDARD_CONTROL_TITLE_GAP;
24242 let enabled_checkbox = count_set_pixels(
24243 &bus,
24244 screen_base,
24245 row_bytes,
24246 bounds.0 + items[0].rect.0,
24247 label_left,
24248 bounds.0 + items[0].rect.2,
24249 bounds.1 + items[0].rect.3,
24250 );
24251 let disabled_checkbox = count_set_pixels(
24252 &bus,
24253 screen_base,
24254 row_bytes,
24255 bounds.0 + items[1].rect.0,
24256 label_left,
24257 bounds.0 + items[1].rect.2,
24258 bounds.1 + items[1].rect.3,
24259 );
24260 let enabled_radio = count_set_pixels(
24261 &bus,
24262 screen_base,
24263 row_bytes,
24264 bounds.0 + items[2].rect.0,
24265 label_left,
24266 bounds.0 + items[2].rect.2,
24267 bounds.1 + items[2].rect.3,
24268 );
24269 let disabled_radio = count_set_pixels(
24270 &bus,
24271 screen_base,
24272 row_bytes,
24273 bounds.0 + items[3].rect.0,
24274 label_left,
24275 bounds.0 + items[3].rect.2,
24276 bounds.1 + items[3].rect.3,
24277 );
24278
24279 assert!(
24280 enabled_checkbox > 0,
24281 "enabled checkbox title should be visible"
24282 );
24283 assert_eq!(
24284 enabled_checkbox, disabled_checkbox,
24285 "classic itemDisable checkbox title must not be visually dimmed"
24286 );
24287 assert!(enabled_radio > 0, "enabled radio title should be visible");
24288 assert_eq!(
24289 enabled_radio, disabled_radio,
24290 "classic itemDisable radio title must not be visually dimmed"
24291 );
24292 }
24293
24294 #[test]
24295 fn draw_dialog_standard_control_hilite_255_titles_use_gray_device_index() {
24296 let screen_base = 0x300000u32;
24297 let row_bytes = 160u32;
24298 let bounds = (20, 20, 120, 220);
24299 let dialog_ptr = 0x2400u32;
24300 let items = vec![
24301 DialogItem {
24302 item_type: 5,
24303 rect: (20, 20, 40, 150),
24304 text: "Sound".to_string(),
24305 resource_id: 0,
24306 proc_ptr: 0,
24307 sel_start: 0,
24308 sel_end: 0,
24309 },
24310 DialogItem {
24311 item_type: 6,
24312 rect: (52, 20, 72, 150),
24313 text: "Music".to_string(),
24314 resource_id: 0,
24315 proc_ptr: 0,
24316 sel_start: 0,
24317 sel_end: 0,
24318 },
24319 ];
24320
24321 let (mut dispatcher, _cpu, mut bus) = setup();
24322 dispatcher.set_screen_mode_for_test(screen_base, row_bytes, 160, 140, 8);
24323 dispatcher.device_clut = [[0x2020, 0x4040, 0x6060]; 256];
24324 dispatcher.device_clut[0] = [0xFFFF, 0xFFFF, 0xFFFF];
24325 dispatcher.device_clut[42] = [0xA1A1, 0xA1A1, 0xA1A1];
24326 dispatcher.device_clut[255] = [0, 0, 0];
24327 for offset in 0..(row_bytes * 140) {
24328 bus.write_byte(screen_base + offset, 0);
24329 }
24330
24331 for (item_no, proc_id, item) in [(1i16, 1i16, &items[0]), (2, 2, &items[1])] {
24332 let ctrl_ptr = bus.alloc(296);
24333 let ctrl_handle = bus.alloc(4);
24334 bus.write_long(ctrl_handle, ctrl_ptr);
24335 dispatcher.initialize_control_record(
24336 &mut bus,
24337 ctrl_ptr,
24338 dialog_ptr,
24339 item.rect,
24340 item.text.as_bytes(),
24341 true,
24342 0,
24343 0,
24344 1,
24345 proc_id,
24346 0,
24347 );
24348 bus.write_byte(ctrl_ptr + 17, 255);
24349 dispatcher
24350 .dialog_control_handles
24351 .insert(ctrl_handle, (dialog_ptr, item_no));
24352 dispatcher
24353 .dialog_control_values
24354 .insert((dialog_ptr, item_no), 0);
24355 }
24356
24357 dispatcher.draw_dialog(&mut bus, bounds, 1, "", &items, 0, "", 0, false, dialog_ptr);
24358
24359 for (label, item) in [("checkbox", &items[0]), ("radio", &items[1])] {
24360 let label_left = bounds.1
24361 + item.rect.1
24362 + TrapDispatcher::STANDARD_CONTROL_MARK_LEFT_INSET
24363 + TrapDispatcher::STANDARD_CONTROL_MARK_SIZE
24364 + TrapDispatcher::STANDARD_CONTROL_TITLE_GAP;
24365 let top = bounds.0 + item.rect.0;
24366 let bottom = bounds.0 + item.rect.2;
24367 let right = bounds.1 + item.rect.3;
24368 let gray = count_pixel_index(
24369 &bus,
24370 screen_base,
24371 row_bytes,
24372 top,
24373 label_left,
24374 bottom,
24375 right,
24376 42,
24377 );
24378 let black = count_pixel_index(
24379 &bus,
24380 screen_base,
24381 row_bytes,
24382 top,
24383 label_left,
24384 bottom,
24385 right,
24386 255,
24387 );
24388 assert!(
24389 gray > 12,
24390 "inactive standard DITL {label} title should draw with the device gray index"
24391 );
24392 assert_eq!(
24393 black, 0,
24394 "inactive standard DITL {label} title must not leave black glyph pixels"
24395 );
24396 }
24397 }
24398
24399 #[test]
24400 fn draw_dialog_systemless_theme_routes_frame_through_provider() {
24401 let screen_base = 0x300000u32;
24402 let row_bytes = 64u32;
24403 let bounds = (40, 40, 100, 140);
24404
24405 let (mut classic, _classic_cpu, mut classic_bus) = setup();
24406 classic.set_screen_mode_for_test(screen_base, row_bytes, 512, 342, 1);
24407 classic.draw_dialog(
24408 &mut classic_bus,
24409 bounds,
24410 1,
24411 "",
24412 &[],
24413 0,
24414 "",
24415 0,
24416 false,
24417 0x2000,
24418 );
24419
24420 let (mut themed, _themed_cpu, mut themed_bus) = setup();
24421 themed.set_ui_theme_id(UiThemeId::SystemlessDefault);
24422 themed.set_screen_mode_for_test(screen_base, row_bytes, 512, 342, 1);
24423 themed.draw_dialog(&mut themed_bus, bounds, 1, "", &[], 0, "", 0, false, 0x2000);
24424
24425 assert!(
24426 !screen_pixel_is_set(&classic_bus, screen_base, row_bytes, 50, 38),
24427 "classic dBoxProc leaves the gap between outer and inner borders clear"
24428 );
24429 assert!(
24430 screen_pixel_is_set(&themed_bus, screen_base, row_bytes, 50, 38),
24431 "systemless-default provider should own dialog frame accent pixels"
24432 );
24433 }
24434
24435 #[test]
24436 fn draw_window_frame_systemless_theme_routes_border_through_provider() {
24437 let screen_base = 0x300000u32;
24438 let row_bytes = 64u32;
24439
24440 let (mut classic, _classic_cpu, mut classic_bus) = setup();
24441 classic.set_screen_mode_for_test(screen_base, row_bytes, 512, 342, 1);
24442 classic.window_bounds = (40, 40, 100, 140);
24443 classic.window_proc_id = 1;
24444 classic.draw_window_frame(&mut classic_bus);
24445
24446 let (mut themed, _themed_cpu, mut themed_bus) = setup();
24447 themed.set_ui_theme_id(UiThemeId::SystemlessDefault);
24448 themed.set_screen_mode_for_test(screen_base, row_bytes, 512, 342, 1);
24449 themed.window_bounds = (40, 40, 100, 140);
24450 themed.window_proc_id = 1;
24451 themed.draw_window_frame(&mut themed_bus);
24452
24453 assert!(
24454 !screen_pixel_is_set(&classic_bus, screen_base, row_bytes, 50, 34),
24455 "classic dBoxProc window frame keeps the structure gap clear"
24456 );
24457 assert!(
24458 screen_pixel_is_set(&themed_bus, screen_base, row_bytes, 50, 34),
24459 "systemless-default provider should own window frame accent pixels"
24460 );
24461 }
24462
24463 #[test]
24464 fn draw_dialog_preserves_existing_user_item_pixels() {
24465 let (mut disp, mut cpu, mut bus) = setup();
24469 let dialog_ptr = bus.alloc(170);
24470 let (screen_base, row_bytes, _w, _h, pixel_size) = disp.screen_mode;
24471
24472 bus.write_word(dialog_ptr + 8, 0);
24473 bus.write_word(dialog_ptr + 10, 0);
24474 bus.write_word(dialog_ptr + 16, 0);
24475 bus.write_word(dialog_ptr + 18, 0);
24476 bus.write_word(dialog_ptr + 20, 40);
24477 bus.write_word(dialog_ptr + 22, 80);
24478 disp.dialog_items.insert(
24479 dialog_ptr,
24480 vec![
24481 DialogItem {
24482 item_type: 0x80, rect: (8, 8, 20, 32),
24484 text: String::new(),
24485 resource_id: 0,
24486 proc_ptr: 0,
24487 sel_start: 0,
24488 sel_end: 0,
24489 },
24490 DialogItem {
24491 item_type: 4,
24492 rect: (24, 8, 36, 40),
24493 text: "OK".to_string(),
24494 resource_id: 0,
24495 proc_ptr: 0,
24496 sel_start: 0,
24497 sel_end: 0,
24498 },
24499 ],
24500 );
24501
24502 let user_x = 10u32;
24503 let user_y = 10u32;
24504 let background_x = 4u32;
24505 let background_y = 4u32;
24506 if pixel_size == 8 {
24507 bus.write_byte(screen_base + user_y * row_bytes + user_x, 0xFF);
24508 bus.write_byte(screen_base + background_y * row_bytes + background_x, 0xFF);
24509 } else {
24510 for (x, y) in [(user_x, user_y), (background_x, background_y)] {
24511 let addr = screen_base + y * row_bytes + (x / 8);
24512 let bit = 1 << (7 - (x % 8));
24513 bus.write_byte(addr, bus.read_byte(addr) | bit);
24514 }
24515 }
24516
24517 bus.write_long(TEST_SP, dialog_ptr);
24518 let result = disp.dispatch_dialog(true, 0x181, &mut cpu, &mut bus);
24519 assert!(result.unwrap().is_ok());
24520
24521 if pixel_size == 8 {
24522 assert_eq!(
24523 bus.read_byte(screen_base + user_y * row_bytes + user_x),
24524 0xFF,
24525 "DrawDialog should preserve pre-drawn userItem pixels"
24526 );
24527 assert_eq!(
24528 bus.read_byte(screen_base + background_y * row_bytes + background_x),
24529 0,
24530 "DrawDialog should still repaint normal dialog background"
24531 );
24532 } else {
24533 let user_addr = screen_base + user_y * row_bytes + (user_x / 8);
24534 let user_bit = 1 << (7 - (user_x % 8));
24535 let background_addr = screen_base + background_y * row_bytes + (background_x / 8);
24536 let background_bit = 1 << (7 - (background_x % 8));
24537 assert_ne!(bus.read_byte(user_addr) & user_bit, 0);
24538 assert_eq!(bus.read_byte(background_addr) & background_bit, 0);
24539 }
24540 }
24541
24542 #[test]
24543 fn modal_dialog_first_entry_does_not_preserve_invisible_user_item_background() {
24544 let (mut disp, mut cpu, mut bus) = setup();
24548 let dialog_ptr = bus.alloc(170);
24549 let item_hit_ptr = bus.alloc(2);
24550 let (screen_base, row_bytes, _w, _h, pixel_size) = disp.screen_mode;
24551 assert_eq!(pixel_size, 8);
24552
24553 bus.write_word(dialog_ptr + 8, 0);
24554 bus.write_word(dialog_ptr + 10, 0);
24555 bus.write_word(dialog_ptr + 16, 0);
24556 bus.write_word(dialog_ptr + 18, 0);
24557 bus.write_word(dialog_ptr + 20, 40);
24558 bus.write_word(dialog_ptr + 22, 80);
24559 bus.write_word(dialog_ptr + 108, 2);
24560 disp.front_window = dialog_ptr;
24561 disp.window_bounds = (0, 0, 40, 80);
24562 disp.window_proc_id = 2;
24563 disp.dialog_items.insert(
24564 dialog_ptr,
24565 vec![
24566 DialogItem {
24567 item_type: 0x80, rect: (8, 8, 20, 32),
24569 text: String::new(),
24570 resource_id: 0,
24571 proc_ptr: 0,
24572 sel_start: 0,
24573 sel_end: 0,
24574 },
24575 DialogItem {
24576 item_type: 4,
24577 rect: (24, 8, 36, 40),
24578 text: "OK".to_string(),
24579 resource_id: 0,
24580 proc_ptr: 0,
24581 sel_start: 0,
24582 sel_end: 0,
24583 },
24584 ],
24585 );
24586
24587 let user_x = 10u32;
24588 let user_y = 10u32;
24589 bus.write_byte(screen_base + user_y * row_bytes + user_x, 0xFF);
24590
24591 bus.write_long(TEST_SP, item_hit_ptr);
24592 bus.write_long(TEST_SP + 4, 0);
24593 let result = disp.dispatch_dialog(true, 0x191, &mut cpu, &mut bus);
24594 assert!(result.unwrap().is_ok());
24595
24596 assert_eq!(
24597 bus.read_byte(screen_base + user_y * row_bytes + user_x),
24598 0,
24599 "ModalDialog should paint a clean dialog background through untouched userItems"
24600 );
24601 assert!(disp.dialog_tracking.is_some());
24602 }
24603
24604 #[test]
24605 fn modal_dialog_first_entry_restores_visible_shell_before_user_item_preservation() {
24606 let (mut disp, mut cpu, mut bus) = setup();
24613 let screen_base = bus.alloc((800 * 600) as u32);
24614 bus.write_bytes(screen_base, &vec![0x77; 800 * 600]);
24615 bus.write_long(0x0824, screen_base);
24616 disp.screen_mode = (screen_base, 800, 800, 600, 8);
24617
24618 let mut dlog = build_test_dlog((100, 100, 180, 260), 1711, 0);
24619 dlog[10] = 1; let ditl = build_test_ditl_items(&[
24621 (0, (8, 8, 30, 80), b"".as_slice()),
24622 (4, (44, 20, 64, 90), b"OK".as_slice()),
24623 ]);
24624 disp.install_test_resource(&mut bus, *b"DLOG", 1710, &dlog);
24625 disp.install_test_resource(&mut bus, *b"DITL", 1711, &ditl);
24626
24627 cpu.write_reg(Register::A7, TEST_SP);
24628 bus.write_long(TEST_SP, 0xFFFF_FFFF); bus.write_long(TEST_SP + 4, 0); bus.write_word(TEST_SP + 8, 1710); disp.dispatch_dialog(true, 0x17C, &mut cpu, &mut bus)
24632 .unwrap()
24633 .unwrap();
24634
24635 let dlg_ptr = bus.read_long(TEST_SP + 10);
24636 assert_ne!(dlg_ptr, 0);
24637 assert!(disp.dialog_visible_snapshots.contains_key(&dlg_ptr));
24638
24639 let probe_v = 120u32;
24640 let probe_h = 120u32;
24641 let probe_addr = screen_base + probe_v * 800 + probe_h;
24642 assert_eq!(
24643 bus.read_byte(probe_addr),
24644 0,
24645 "visible dialog creation should paint the userItem area as dialog background"
24646 );
24647
24648 bus.write_byte(probe_addr, 0x55);
24649 let item_hit_ptr = bus.alloc(2);
24650 cpu.write_reg(Register::A7, TEST_SP);
24651 bus.write_long(TEST_SP, item_hit_ptr);
24652 bus.write_long(TEST_SP + 4, 0);
24653 disp.dispatch_dialog(true, 0x191, &mut cpu, &mut bus)
24654 .unwrap()
24655 .unwrap();
24656
24657 assert_eq!(
24658 bus.read_byte(probe_addr),
24659 0,
24660 "first ModalDialog entry should not preserve stale non-dialog pixels in userItems"
24661 );
24662 assert!(disp.dialog_tracking.is_some());
24663 assert!(
24664 !disp.dialog_visible_snapshots.contains_key(&dlg_ptr),
24665 "first ModalDialog entry consumes the clean visible shell snapshot"
24666 );
24667 }
24668
24669 #[test]
24670 fn premodal_dialog_port_draw_refreshes_visible_snapshot() {
24671 let (mut disp, _cpu, mut bus) = setup();
24680 let screen_base = bus.alloc((64 * 64) as u32);
24681 bus.write_bytes(screen_base, &vec![0x00; 64 * 64]);
24682 bus.write_long(0x0824, screen_base);
24683 disp.screen_mode = (screen_base, 64, 64, 64, 8);
24684
24685 let dialog_ptr = 0x200000u32;
24686 let bounds = (10, 10, 30, 30);
24687 let snapshot_width = (bounds.3 - bounds.1 + TrapDispatcher::DBOX_FRAME_MARGIN * 2) as usize;
24688 let snapshot_height =
24689 (bounds.2 - bounds.0 + TrapDispatcher::DBOX_FRAME_MARGIN * 2) as usize;
24690 disp.front_window = dialog_ptr;
24691 disp.dialog_items.insert(dialog_ptr, Vec::new());
24692 disp.dialog_visible_snapshots.insert(
24693 dialog_ptr,
24694 PersistentDialogSnapshot {
24695 bounds,
24696 pixels: vec![0x11; snapshot_width * snapshot_height],
24697 },
24698 );
24699
24700 let probe = screen_base + 20 * 64 + 20;
24701 bus.write_byte(probe, 0x77);
24702 disp.refresh_visible_dialog_snapshot_after_bulk_port_draw(&bus, dialog_ptr);
24703 bus.write_byte(probe, 0x00);
24704 disp.restore_visible_dialog_snapshots(&mut bus);
24705 assert_eq!(
24706 bus.read_byte(probe),
24707 0x77,
24708 "drawing into the dialog's own port refreshes its retained snapshot"
24709 );
24710
24711 disp.dialog_modal_entered.insert(dialog_ptr);
24712 bus.write_byte(probe, 0x88);
24713 disp.refresh_visible_dialog_snapshot_after_bulk_port_draw(&bus, dialog_ptr);
24714 bus.write_byte(probe, 0x00);
24715 disp.restore_visible_dialog_snapshots(&mut bus);
24716 assert_eq!(
24717 bus.read_byte(probe),
24718 0x88,
24719 "retained modal dialogs may refresh snapshots from later bulk drawing"
24720 );
24721
24722 let game_dialog_ptr = 0x200100u32;
24723 let game_bounds = (34, 10, 54, 30);
24724 bus.write_word(game_dialog_ptr + 6, 0xC000);
24725 let pixmap_handle = bus.alloc(4);
24726 let pixmap_ptr = bus.alloc(50);
24727 bus.write_long(pixmap_handle, pixmap_ptr);
24728 bus.write_long(pixmap_ptr, screen_base);
24729 bus.write_word(pixmap_ptr + 4, 0x8000 | 64);
24730 bus.write_word(pixmap_ptr + 6, (-(game_bounds.0)) as u16);
24731 bus.write_word(pixmap_ptr + 8, (-(game_bounds.1)) as u16);
24732 bus.write_word(pixmap_ptr + 32, 8);
24733 bus.write_long(game_dialog_ptr + 2, pixmap_handle);
24734 bus.write_word(game_dialog_ptr + 16, 0);
24735 bus.write_word(game_dialog_ptr + 18, 0);
24736 bus.write_word(game_dialog_ptr + 20, 20);
24737 bus.write_word(game_dialog_ptr + 22, 20);
24738 bus.write_byte(game_dialog_ptr + 110, 0xFF);
24739 disp.dialog_items.insert(
24740 game_dialog_ptr,
24741 vec![DialogItem {
24742 item_type: 0,
24743 rect: (0, 0, 20, 20),
24744 text: String::new(),
24745 resource_id: 0,
24746 proc_ptr: 0,
24747 sel_start: 0,
24748 sel_end: 0,
24749 }],
24750 );
24751 disp.front_window = game_dialog_ptr;
24752 bus.write_byte(screen_base + 40 * 64 + 20, 0x99);
24753 disp.refresh_visible_dialog_snapshot_after_bulk_port_draw(&bus, game_dialog_ptr);
24754 bus.write_byte(screen_base + 40 * 64 + 20, 0x00);
24755 disp.restore_visible_dialog_snapshots(&mut bus);
24756 assert_eq!(
24757 bus.read_byte(screen_base + 40 * 64 + 20),
24758 0x99,
24759 "game-managed dialogs should create snapshots from app-owned bulk drawing"
24760 );
24761 }
24762
24763 #[test]
24764 fn updtdialog_pops_eight_bytes() {
24765 let (mut disp, mut cpu, mut bus) = setup();
24768 let sp = TEST_SP;
24769 bus.write_long(sp, 0x300100); bus.write_long(sp + 4, 0x200000); let result = disp.dispatch_dialog(true, 0x178, &mut cpu, &mut bus);
24773 assert!(result.unwrap().is_ok());
24774 assert_eq!(cpu.read_reg(Register::A7), sp + 8);
24775 }
24776
24777 #[test]
24778 fn updtdialog_nil_update_region_is_a_noop() {
24779 let (mut disp, mut cpu, mut bus) = setup();
24782 let existing = 0x200040u32;
24783 disp.window_list = vec![existing];
24784 disp.front_window = existing;
24785 bus.write_byte(existing + 110, 0xFF);
24786
24787 let screen_base = bus.alloc((800 * 600) as u32);
24788 bus.write_long(0x0824, screen_base);
24789 disp.screen_mode = (screen_base, 800, 800, 600, 8);
24790
24791 let bounds_rect_ptr = 0x301200u32;
24792 bus.write_word(bounds_rect_ptr, 100);
24793 bus.write_word(bounds_rect_ptr + 2, 100);
24794 bus.write_word(bounds_rect_ptr + 4, 300);
24795 bus.write_word(bounds_rect_ptr + 6, 400);
24796
24797 let sp = TEST_SP - 30;
24798 cpu.write_reg(Register::A7, sp);
24799 for i in 0..34u32 {
24800 bus.write_byte(sp + i, 0);
24801 }
24802 bus.write_long(sp + 22, bounds_rect_ptr);
24803 bus.write_word(sp + 16, 1); bus.write_long(sp + 10, 0); let result = disp.dispatch_dialog(true, 0x17D, &mut cpu, &mut bus);
24807 assert!(result.unwrap().is_ok());
24808 let dlg_ptr = bus.read_long(sp + 30);
24809 assert_ne!(dlg_ptr, 0);
24810
24811 let probe_x = 120u32;
24812 let probe_y = 120u32;
24813 let probe_addr = screen_base + probe_y * 800 + probe_x;
24814 bus.write_byte(probe_addr, 0xFF);
24815
24816 cpu.write_reg(Register::A7, TEST_SP);
24817 bus.write_long(TEST_SP, 0); bus.write_long(TEST_SP + 4, dlg_ptr);
24819
24820 let result = disp.dispatch_dialog(true, 0x178, &mut cpu, &mut bus);
24821 assert!(result.unwrap().is_ok());
24822 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 8);
24823 assert_eq!(bus.read_byte(probe_addr), 0xFF);
24824 }
24825
24826 #[test]
24827 fn updtdialog_queues_user_item_draw_proc_for_intersecting_update_region() {
24828 let (mut disp, mut cpu, mut bus) = setup();
24832 let screen_base = bus.alloc(100 * 100);
24833 bus.write_long(0x0824, screen_base);
24834 disp.screen_mode = (screen_base, 100, 100, 100, 8);
24835
24836 let dialog_ptr = bus.alloc(256);
24837 bus.write_word(dialog_ptr + 16, 0);
24838 bus.write_word(dialog_ptr + 18, 0);
24839 bus.write_word(dialog_ptr + 20, 100);
24840 bus.write_word(dialog_ptr + 22, 100);
24841 bus.write_word(dialog_ptr + 108, 2); disp.dialog_items.insert(
24843 dialog_ptr,
24844 vec![
24845 DialogItem {
24846 item_type: 0,
24847 rect: (8, 8, 24, 32),
24848 proc_ptr: 0x500000,
24849 ..Default::default()
24850 },
24851 DialogItem {
24852 item_type: 0,
24853 rect: (60, 60, 80, 80),
24854 proc_ptr: 0x600000,
24855 ..Default::default()
24856 },
24857 ],
24858 );
24859 let update_rgn_ptr = bus.alloc(10);
24860 let update_rgn = bus.alloc(4);
24861 bus.write_long(update_rgn, update_rgn_ptr);
24862 bus.write_word(update_rgn_ptr, 10);
24863 bus.write_word(update_rgn_ptr + 2, 0);
24864 bus.write_word(update_rgn_ptr + 4, 0);
24865 bus.write_word(update_rgn_ptr + 6, 40);
24866 bus.write_word(update_rgn_ptr + 8, 40);
24867
24868 bus.write_long(TEST_SP, update_rgn);
24869 bus.write_long(TEST_SP + 4, dialog_ptr);
24870 let result = disp.dispatch_dialog(true, 0x178, &mut cpu, &mut bus);
24871 assert!(result.unwrap().is_ok());
24872
24873 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 8);
24874 assert_eq!(
24875 disp.modeless_dialog_draw_proc_queue
24876 .iter()
24877 .copied()
24878 .collect::<Vec<_>>(),
24879 vec![(dialog_ptr, 0x500000, 1)]
24880 );
24881 }
24882
24883 #[test]
24884 fn te_text_box_erases_box_before_drawing_text() {
24885 let (mut disp, mut cpu, mut bus) = setup_with_port();
24888 let port_ptr = 0x181000u32;
24889 let screen_base = bus.read_long(0x0824);
24890
24891 for y in 0..12u32 {
24893 for byte in 0..3u32 {
24894 bus.write_byte(screen_base + y * 64 + byte, 0xFF);
24895 }
24896 }
24897
24898 disp.current_port = port_ptr;
24900 disp.tx_size = 12;
24901 bus.write_word(port_ptr + 74, 12);
24902
24903 let text_ptr = 0x200000u32;
24904 bus.write_byte(text_ptr, b'A');
24905
24906 let box_ptr = 0x200100u32;
24907 bus.write_word(box_ptr, 0); bus.write_word(box_ptr + 2, 0); bus.write_word(box_ptr + 4, 12); bus.write_word(box_ptr + 6, 24); let sp = TEST_SP - 14;
24913 cpu.write_reg(Register::A7, sp);
24914 bus.write_word(sp, 0); bus.write_long(sp + 2, box_ptr);
24916 bus.write_long(sp + 6, 1); bus.write_long(sp + 10, text_ptr);
24918
24919 let result = disp.dispatch_dialog(true, 0x1CE, &mut cpu, &mut bus);
24920 assert!(result.unwrap().is_ok());
24921 assert_eq!(cpu.read_reg(Register::A7), TEST_SP);
24922
24923 let probe_addr = screen_base + 10 * 64 + 2;
24926 assert_eq!(bus.read_byte(probe_addr) & (1 << 3), 0);
24927 }
24928
24929 #[test]
24930 fn te_text_box_erases_box_for_zero_length_text() {
24931 let (mut disp, mut cpu, mut bus) = setup_with_port();
24934 let port_ptr = 0x181000u32;
24935 let screen_base = bus.read_long(0x0824);
24936
24937 for y in 0..12u32 {
24938 for byte in 0..3u32 {
24939 bus.write_byte(screen_base + y * 64 + byte, 0xFF);
24940 }
24941 }
24942
24943 disp.current_port = port_ptr;
24944 disp.tx_size = 12;
24945 bus.write_word(port_ptr + 74, 12);
24946
24947 let text_ptr = 0x200000u32;
24948 let box_ptr = 0x200100u32;
24949 bus.write_word(box_ptr, 0);
24950 bus.write_word(box_ptr + 2, 0);
24951 bus.write_word(box_ptr + 4, 12);
24952 bus.write_word(box_ptr + 6, 24);
24953
24954 let sp = TEST_SP - 14;
24955 cpu.write_reg(Register::A7, sp);
24956 bus.write_word(sp, 0);
24957 bus.write_long(sp + 2, box_ptr);
24958 bus.write_long(sp + 6, 0);
24959 bus.write_long(sp + 10, text_ptr);
24960
24961 let result = disp.dispatch_dialog(true, 0x1CE, &mut cpu, &mut bus);
24962 assert!(result.unwrap().is_ok());
24963 assert_eq!(cpu.read_reg(Register::A7), TEST_SP);
24964
24965 let probe_addr = screen_base + 10 * 64 + 2;
24966 assert_eq!(bus.read_byte(probe_addr) & (1 << 3), 0);
24967 }
24968
24969 #[test]
24970 fn te_text_box_consumes_align_box_length_text_arguments() {
24971 let (mut disp, mut cpu, mut bus) = setup_with_port();
24974 let port_ptr = 0x181000u32;
24975 disp.current_port = port_ptr;
24976 disp.tx_size = 12;
24977 bus.write_word(port_ptr + 74, 12);
24978
24979 let text_ptr = 0x200200u32;
24980 bus.write_byte(text_ptr, b'A');
24981
24982 let box_ptr = 0x200240u32;
24983 bus.write_word(box_ptr, 0);
24984 bus.write_word(box_ptr + 2, 0);
24985 bus.write_word(box_ptr + 4, 20);
24986 bus.write_word(box_ptr + 6, 60);
24987
24988 let sp = TEST_SP - 14;
24989 cpu.write_reg(Register::A7, sp);
24990 bus.write_word(sp, 0);
24991 bus.write_long(sp + 2, box_ptr);
24992 bus.write_long(sp + 6, 1);
24993 bus.write_long(sp + 10, text_ptr);
24994
24995 let result = disp.dispatch_dialog(true, 0x1CE, &mut cpu, &mut bus);
24996 assert!(result.unwrap().is_ok());
24997 assert_eq!(cpu.read_reg(Register::A7), TEST_SP);
24998 }
24999
25000 #[test]
25001 fn te_text_box_align_parameter_controls_rendered_line_origin() {
25002 let (mut disp, mut cpu, mut bus) = setup_with_port();
25005 let port_ptr = 0x181000u32;
25006 disp.current_port = port_ptr;
25007 disp.tx_size = 12;
25008 bus.write_word(port_ptr + 74, 12);
25009
25010 let text = b"ABC";
25011 let text_ptr = 0x200280u32;
25012 bus.write_bytes(text_ptr, text);
25013
25014 let box_ptr = 0x2002C0u32;
25015 let box_top = 0i16;
25016 let box_left = 10i16;
25017 let box_bottom = 40i16;
25018 let box_right = 110i16;
25019 bus.write_word(box_ptr, box_top as u16);
25020 bus.write_word(box_ptr + 2, box_left as u16);
25021 bus.write_word(box_ptr + 4, box_bottom as u16);
25022 bus.write_word(box_ptr + 6, box_right as u16);
25023
25024 let advance_extra = disp.advance_extra();
25025 let missing_advance = disp.missing_glyph_advance();
25026 let mut line_width = 0i16;
25027 for &byte in text {
25028 if let Some((glyph, _)) =
25029 crate::quickdraw::text::get_glyph(disp.tx_font, disp.tx_size, byte as char)
25030 {
25031 line_width += glyph.advance as i16 + advance_extra;
25032 } else {
25033 line_width += missing_advance;
25034 }
25035 }
25036
25037 let mut run_align = |align: i16| -> i16 {
25038 let sp = TEST_SP - 14;
25039 cpu.write_reg(Register::A7, sp);
25040 bus.write_word(sp, align as u16);
25041 bus.write_long(sp + 2, box_ptr);
25042 bus.write_long(sp + 6, text.len() as u32);
25043 bus.write_long(sp + 10, text_ptr);
25044 let result = disp.dispatch_dialog(true, 0x1CE, &mut cpu, &mut bus);
25045 assert!(result.unwrap().is_ok());
25046 assert_eq!(cpu.read_reg(Register::A7), TEST_SP);
25047 disp.pn_loc.1
25048 };
25049
25050 let x_left = run_align(0);
25051 let x_center = run_align(1);
25052 let x_right = run_align(-1);
25053
25054 let expected_left =
25055 TrapDispatcher::te_line_origin_x(0, box_left, box_right, line_width) + line_width;
25056 let expected_center =
25057 TrapDispatcher::te_line_origin_x(1, box_left, box_right, line_width) + line_width;
25058 let expected_right =
25059 TrapDispatcher::te_line_origin_x(-1, box_left, box_right, line_width) + line_width;
25060
25061 assert_eq!(x_left, expected_left);
25062 assert_eq!(x_center, expected_center);
25063 assert_eq!(x_right, expected_right);
25064 assert!(x_left < x_center && x_center < x_right);
25065 }
25066
25067 #[test]
25068 fn te_text_box_pascal_procedure_protocol_does_not_overwrite_past_arg_frame() {
25069 let (mut disp, mut cpu, mut bus) = setup_with_port();
25076 let port_ptr = 0x181000u32;
25077 disp.current_port = port_ptr;
25078 disp.tx_size = 12;
25079 bus.write_word(port_ptr + 74, 12);
25080
25081 let text_ptr = 0x200400u32;
25082 bus.write_byte(text_ptr, b'X');
25083
25084 let box_ptr = 0x200440u32;
25085 bus.write_word(box_ptr, 0);
25086 bus.write_word(box_ptr + 2, 0);
25087 bus.write_word(box_ptr + 4, 12);
25088 bus.write_word(box_ptr + 6, 64);
25089
25090 let sp = TEST_SP - 14;
25094 bus.write_word(TEST_SP, 0xCAFE);
25095 bus.write_word(TEST_SP + 2, 0xBABE);
25096 cpu.write_reg(Register::A7, sp);
25097 bus.write_word(sp, 0); bus.write_long(sp + 2, box_ptr);
25099 bus.write_long(sp + 6, 1); bus.write_long(sp + 10, text_ptr);
25101
25102 let result = disp.dispatch_dialog(true, 0x1CE, &mut cpu, &mut bus);
25103 assert!(result.unwrap().is_ok());
25104 assert_eq!(cpu.read_reg(Register::A7), TEST_SP);
25105 assert_eq!(bus.read_word(TEST_SP), 0xCAFE);
25106 assert_eq!(bus.read_word(TEST_SP + 2), 0xBABE);
25107 }
25108
25109 #[test]
25110 fn te_text_box_wraps_when_text_exceeds_box_width() {
25111 let (mut disp, mut cpu, mut bus) = setup_with_port();
25114 let port_ptr = 0x181000u32;
25115 disp.current_port = port_ptr;
25116 disp.tx_size = 12;
25117 bus.write_word(port_ptr + 74, 12);
25118
25119 let text = b"A A A A";
25120 let text_ptr = 0x200300u32;
25121 bus.write_bytes(text_ptr, text);
25122
25123 let mut glyph_w = disp.missing_glyph_advance();
25124 if let Some((glyph, _)) = crate::quickdraw::text::get_glyph(disp.tx_font, disp.tx_size, 'A')
25125 {
25126 glyph_w = glyph.advance as i16 + disp.advance_extra();
25127 }
25128
25129 let wide_box_ptr = 0x200340u32;
25130 bus.write_word(wide_box_ptr, 0);
25131 bus.write_word(wide_box_ptr + 2, 0);
25132 bus.write_word(wide_box_ptr + 4, 80);
25133 bus.write_word(wide_box_ptr + 6, 140);
25134
25135 let narrow_box_ptr = 0x200380u32;
25136 bus.write_word(narrow_box_ptr, 0);
25137 bus.write_word(narrow_box_ptr + 2, 0);
25138 bus.write_word(narrow_box_ptr + 4, 80);
25139 bus.write_word(narrow_box_ptr + 6, glyph_w as u16);
25140
25141 let mut run_box = |box_ptr: u32| -> i16 {
25142 let sp = TEST_SP - 14;
25143 cpu.write_reg(Register::A7, sp);
25144 bus.write_word(sp, 0);
25145 bus.write_long(sp + 2, box_ptr);
25146 bus.write_long(sp + 6, text.len() as u32);
25147 bus.write_long(sp + 10, text_ptr);
25148 let result = disp.dispatch_dialog(true, 0x1CE, &mut cpu, &mut bus);
25149 assert!(result.unwrap().is_ok());
25150 assert_eq!(cpu.read_reg(Register::A7), TEST_SP);
25151 disp.pn_loc.0
25152 };
25153
25154 let y_wide = run_box(wide_box_ptr);
25155 let y_narrow = run_box(narrow_box_ptr);
25156 let metrics = crate::quickdraw::text::get_font_metrics(disp.tx_font, disp.tx_size);
25157 let line_height = metrics.ascent + metrics.descent + metrics.leading.max(2);
25158
25159 assert!(
25160 y_narrow >= y_wide + line_height,
25161 "narrow box should force at least one additional wrapped line"
25162 );
25163 }
25164
25165 fn alloc_region_handle(bus: &mut MacMemoryBus, rect: Option<(i16, i16, i16, i16)>) -> u32 {
25168 let rgn_ptr = bus.alloc(10);
25169 bus.write_word(rgn_ptr, 10);
25170 if let Some((top, left, bottom, right)) = rect.filter(|r| r.2 > r.0 && r.3 > r.1) {
25171 bus.write_word(rgn_ptr + 2, top as u16);
25172 bus.write_word(rgn_ptr + 4, left as u16);
25173 bus.write_word(rgn_ptr + 6, bottom as u16);
25174 bus.write_word(rgn_ptr + 8, right as u16);
25175 } else {
25176 bus.write_long(rgn_ptr + 2, 0);
25177 bus.write_long(rgn_ptr + 6, 0);
25178 }
25179 let handle = bus.alloc(4);
25180 bus.write_long(handle, rgn_ptr);
25181 handle
25182 }
25183
25184 fn seed_window_regions(
25185 bus: &mut MacMemoryBus,
25186 window_ptr: u32,
25187 content_rect: (i16, i16, i16, i16),
25188 ) {
25189 bus.write_word(window_ptr + 16, content_rect.0 as u16);
25192 bus.write_word(window_ptr + 18, content_rect.1 as u16);
25193 bus.write_word(window_ptr + 20, content_rect.2 as u16);
25194 bus.write_word(window_ptr + 22, content_rect.3 as u16);
25195 let cont_rgn = alloc_region_handle(bus, Some(content_rect));
25196 let update_rgn = alloc_region_handle(bus, None);
25197 bus.write_long(window_ptr + 118, cont_rgn);
25198 bus.write_long(window_ptr + 122, update_rgn);
25199 bus.write_byte(window_ptr + 110, 0xFF);
25200 }
25201
25202 fn seed_app_owned_modal_dialog(
25203 disp: &mut TrapDispatcher,
25204 bus: &mut MacMemoryBus,
25205 dialog_ptr: u32,
25206 prev_window: u32,
25207 proc_id: i16,
25208 ) {
25209 seed_window_regions(bus, dialog_ptr, (0, 0, 100, 220));
25210 seed_window_regions(bus, prev_window, (0, 0, 342, 512));
25211 bus.write_word(dialog_ptr + 8, 0);
25212 bus.write_word(dialog_ptr + 10, 0);
25213 bus.write_word(dialog_ptr + 108, 2);
25214 disp.window_proc_ids.insert(dialog_ptr, proc_id);
25215 disp.front_window = dialog_ptr;
25216 disp.current_port = dialog_ptr;
25217 disp.window_bounds = (0, 0, 100, 220);
25218 disp.window_proc_id = proc_id;
25219 disp.window_list = vec![dialog_ptr, prev_window];
25220 disp.window_stack
25221 .push((prev_window, (0, 0, 342, 512), 0, "Game".to_string()));
25222 disp.dialog_items.insert(
25223 dialog_ptr,
25224 vec![DialogItem {
25225 item_type: 4,
25226 rect: (60, 130, 82, 200),
25227 text: "OK".to_string(),
25228 ..DialogItem::default()
25229 }],
25230 );
25231 disp.sent_open_app_event = true;
25232 }
25233
25234 fn seed_retained_modal_dialog(
25235 disp: &mut TrapDispatcher,
25236 bus: &mut MacMemoryBus,
25237 dialog_ptr: u32,
25238 prev_window: u32,
25239 proc_id: i16,
25240 ) {
25241 seed_app_owned_modal_dialog(disp, bus, dialog_ptr, prev_window, proc_id);
25242 disp.dialog_modal_entered.insert(dialog_ptr);
25243 }
25244
25245 #[test]
25246 fn close_dialog_pops_4_bytes() {
25247 let (mut disp, mut cpu, mut bus) = setup();
25250 bus.write_long(TEST_SP, 0x200000);
25251
25252 let result = disp.dispatch_dialog(true, 0x182, &mut cpu, &mut bus);
25253 assert!(result.unwrap().is_ok());
25254 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 4);
25255 }
25256
25257 #[test]
25258 fn close_dialog_front_dialog_restores_previous_port_state() {
25259 let (mut disp, mut cpu, mut bus) = setup();
25262 let dialog_ptr = 0x200000u32;
25263 let prev_window = 0x181000u32;
25264 seed_window_regions(&mut bus, prev_window, (0, 0, 342, 512));
25265
25266 disp.front_window = dialog_ptr;
25267 disp.current_port = dialog_ptr;
25268 disp.window_bounds = (100, 120, 220, 320);
25269 bus.write_long(crate::memory::globals::addr::THE_PORT, dialog_ptr);
25270 let a5 = cpu.read_reg(Register::A5);
25271 let global_ptr = bus.read_long(a5);
25272 bus.write_long(global_ptr, dialog_ptr);
25273 disp.window_stack
25274 .push((prev_window, (0, 0, 342, 512), 2, "Prev".to_string()));
25275
25276 bus.write_long(TEST_SP, dialog_ptr);
25277
25278 let result = disp.dispatch_dialog(true, 0x182, &mut cpu, &mut bus);
25279 assert!(result.unwrap().is_ok());
25280 assert_eq!(disp.front_window, prev_window);
25281 assert_eq!(disp.current_port, prev_window);
25282 assert_eq!(
25283 bus.read_long(crate::memory::globals::addr::THE_PORT),
25284 prev_window
25285 );
25286 assert_eq!(bus.read_long(global_ptr), prev_window);
25287 assert_eq!(
25288 TrapDispatcher::region_handle_rect(&bus, bus.read_long(prev_window + 122)),
25289 Some((100, 120, 220, 320)),
25290 "CloseDialog should invalidate the dialog-exposed area on the promoted window"
25291 );
25292 assert!(
25293 disp.event_queue.iter().any(|event| {
25294 event.what == 8 && event.message == prev_window && (event.modifiers & 1) != 0
25295 }),
25296 "CloseDialog should queue activateEvt for the promoted front window"
25297 );
25298 assert!(
25299 disp.event_queue
25300 .iter()
25301 .any(|event| event.what == 6 && event.message == prev_window),
25302 "CloseDialog must queue updateEvt for the newly exposed front window"
25303 );
25304 }
25305
25306 #[test]
25307 fn close_dialog_restores_saved_background_pixels() {
25308 let (mut disp, mut cpu, mut bus) = setup();
25309 let screen_base = 0x300000u32;
25310 let row_bytes: u32 = 640;
25311 disp.set_screen_mode_for_test(screen_base, row_bytes, 640, 480, 8);
25312
25313 let bounds = (100i16, 100i16, 150i16, 200i16);
25314 let dialog_ptr = 0x200000u32;
25315 disp.front_window = dialog_ptr;
25316 disp.window_bounds = bounds;
25317
25318 for y in 92u32..158 {
25319 for x in 92u32..208 {
25320 bus.write_byte(screen_base + y * row_bytes + x, 0xCC);
25321 }
25322 }
25323
25324 disp.dialog_saved_pixels
25325 .insert(dialog_ptr, vec![0x33; 66 * 116]);
25326
25327 bus.write_long(TEST_SP, dialog_ptr);
25328 let result = disp.dispatch_dialog(true, 0x182, &mut cpu, &mut bus);
25329 assert!(result.unwrap().is_ok());
25330
25331 for y in 92u32..158 {
25332 for x in 92u32..208 {
25333 assert_eq!(
25334 bus.read_byte(screen_base + y * row_bytes + x),
25335 0x33,
25336 "CloseDialog should restore saved background at ({x},{y})"
25337 );
25338 }
25339 }
25340 assert!(
25341 !disp.dialog_saved_pixels.contains_key(&dialog_ptr),
25342 "saved pixels must be consumed after CloseDialog"
25343 );
25344 }
25345
25346 #[test]
25347 fn close_dialog_non_front_dialog_leaves_front_window_and_port_unchanged() {
25348 let (mut disp, mut cpu, mut bus) = setup();
25351 let dialog_ptr = 0x200000u32;
25352 let other_front = 0x181000u32;
25353
25354 disp.front_window = other_front;
25355 disp.current_port = other_front;
25356 bus.write_long(crate::memory::globals::addr::THE_PORT, other_front);
25357 let a5 = cpu.read_reg(Register::A5);
25358 let global_ptr = bus.read_long(a5);
25359 bus.write_long(global_ptr, other_front);
25360 disp.window_stack
25361 .push((0x170000, (1, 2, 3, 4), 3, "Prev".to_string()));
25362
25363 bus.write_long(TEST_SP, dialog_ptr);
25364 let result = disp.dispatch_dialog(true, 0x182, &mut cpu, &mut bus);
25365 assert!(result.unwrap().is_ok());
25366
25367 assert_eq!(disp.front_window, other_front);
25368 assert_eq!(disp.current_port, other_front);
25369 assert_eq!(
25370 bus.read_long(crate::memory::globals::addr::THE_PORT),
25371 other_front
25372 );
25373 assert_eq!(bus.read_long(global_ptr), other_front);
25374 assert_eq!(
25375 disp.window_stack.len(),
25376 1,
25377 "non-front CloseDialog should not consume the saved front-window stack entry"
25378 );
25379 }
25380
25381 #[test]
25382 fn close_dialog_removes_window_but_preserves_caller_storage() {
25383 let (mut disp, mut cpu, mut bus) = setup();
25387 install_new_dialog_test_screen(&mut disp, &mut bus, 0x77);
25388 let storage_ptr = bus.alloc(170);
25389 let ditl = build_test_ditl_item(8, (10, 12, 24, 140), b"Static");
25390 let (items_handle, items_ptr) = install_ditl_handle_for_test(&mut bus, &ditl);
25391
25392 let (_sp, dialog_ptr) = call_new_dialog_for_test(
25393 &mut disp,
25394 &mut cpu,
25395 &mut bus,
25396 storage_ptr,
25397 (80, 90, 150, 260),
25398 false,
25399 4,
25400 0xFFFF_FFFF,
25401 items_handle,
25402 );
25403
25404 let text_handle = ditl_item_handle_field(&bus, items_ptr, 1);
25405 let text_ptr = bus.read_long(text_handle);
25406 let te_handle = bus.read_long(dialog_ptr + 160);
25407 let te_ptr = bus.read_long(te_handle);
25408 assert_ne!(text_handle, 0);
25409 assert_ne!(text_ptr, 0);
25410 assert_ne!(te_handle, 0);
25411 assert_ne!(te_ptr, 0);
25412
25413 cpu.write_reg(Register::A7, TEST_SP);
25414 bus.write_long(TEST_SP, dialog_ptr);
25415 disp.dispatch_dialog(true, 0x182, &mut cpu, &mut bus)
25416 .unwrap()
25417 .unwrap();
25418
25419 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 4);
25420 assert!(!disp.window_list.contains(&dialog_ptr));
25421 assert_eq!(bus.get_alloc_size(storage_ptr), Some(170));
25422 assert_eq!(bus.get_alloc_size(items_handle), Some(4));
25423 assert_eq!(bus.get_alloc_size(items_ptr), Some(ditl.len() as u32));
25424 assert_eq!(bus.get_alloc_size(text_ptr), None);
25425 assert_eq!(bus.get_alloc_size(text_handle), None);
25426 assert_eq!(bus.get_alloc_size(te_ptr), None);
25427 assert_eq!(bus.get_alloc_size(te_handle), None);
25428 assert!(!disp.dialog_items.contains_key(&dialog_ptr));
25429 assert!(!disp.dialog_item_handles.contains_key(&text_handle));
25430 }
25431
25432 #[test]
25435 fn dispos_dialog_pops_4_bytes() {
25436 let (mut disp, mut cpu, mut bus) = setup();
25437 bus.write_long(TEST_SP, 0x200000);
25439
25440 let result = disp.dispatch_dialog(true, 0x183, &mut cpu, &mut bus);
25441 assert!(result.unwrap().is_ok());
25442 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 4);
25443 }
25444
25445 #[test]
25446 fn dispos_dialog_restores_previous_port_state() {
25447 let (mut disp, mut cpu, mut bus) = setup();
25448 let dialog_ptr = 0x200000u32;
25449 let prev_window = 0x181000u32;
25450 seed_window_regions(&mut bus, prev_window, (0, 0, 342, 512));
25451
25452 disp.front_window = dialog_ptr;
25453 disp.current_port = dialog_ptr;
25454 disp.window_bounds = (100, 120, 220, 320);
25455 bus.write_long(crate::memory::globals::addr::THE_PORT, dialog_ptr);
25456 let a5 = cpu.read_reg(Register::A5);
25457 let global_ptr = bus.read_long(a5);
25458 bus.write_long(global_ptr, dialog_ptr);
25459 disp.window_stack
25460 .push((prev_window, (0, 0, 342, 512), 2, "Prev".to_string()));
25461
25462 bus.write_long(TEST_SP, dialog_ptr);
25463
25464 let result = disp.dispatch_dialog(true, 0x183, &mut cpu, &mut bus);
25465 assert!(result.unwrap().is_ok());
25466 assert_eq!(disp.front_window, prev_window);
25467 assert_eq!(disp.current_port, prev_window);
25468 assert_eq!(
25469 bus.read_long(crate::memory::globals::addr::THE_PORT),
25470 prev_window
25471 );
25472 assert_eq!(bus.read_long(global_ptr), prev_window);
25473 assert_eq!(
25474 TrapDispatcher::region_handle_rect(&bus, bus.read_long(prev_window + 122)),
25475 Some((100, 120, 220, 320)),
25476 "DisposDialog should invalidate the dialog-exposed area on the promoted window"
25477 );
25478 assert!(
25479 disp.event_queue.iter().any(|event| {
25480 event.what == 8 && event.message == prev_window && (event.modifiers & 1) != 0
25481 }),
25482 "DisposDialog should queue activateEvt for the promoted front window"
25483 );
25484 assert!(
25485 disp.event_queue
25486 .iter()
25487 .any(|event| event.what == 6 && event.message == prev_window),
25488 "DisposDialog must queue updateEvt for the newly exposed front window"
25489 );
25490 }
25491
25492 #[test]
25493 fn dispos_dialog_removes_dialog_from_window_list() {
25494 let (mut disp, mut cpu, mut bus) = setup();
25497 let dialog_ptr = 0x200000u32;
25498 let prev_window = 0x181000u32;
25499
25500 disp.window_list = vec![dialog_ptr, prev_window];
25501 disp.front_window = dialog_ptr;
25502 disp.current_port = dialog_ptr;
25503 bus.write_byte(dialog_ptr + 110, 0xFF);
25504 bus.write_byte(prev_window + 110, 0xFF);
25505 disp.window_stack
25506 .push((prev_window, (0, 0, 342, 512), 2, "Prev".to_string()));
25507
25508 bus.write_long(TEST_SP, dialog_ptr);
25509 let result = disp.dispatch_dialog(true, 0x183, &mut cpu, &mut bus);
25510 assert!(result.unwrap().is_ok());
25511 assert_eq!(disp.window_list, vec![prev_window]);
25512 assert_eq!(disp.front_window, prev_window);
25513 }
25514
25515 #[test]
25516 fn dispos_dialog_with_user_item_proc_argument_does_not_close_dialog() {
25517 let (mut disp, mut cpu, mut bus) = setup();
25523 let dialog_ptr = 0x200000u32;
25524 let prev_window = 0x181000u32;
25525 let user_item_proc = 0x00016178u32;
25526
25527 seed_window_regions(&mut bus, prev_window, (0, 0, 342, 512));
25528 disp.window_list = vec![dialog_ptr, prev_window];
25529 disp.front_window = dialog_ptr;
25530 disp.current_port = dialog_ptr;
25531 disp.window_bounds = (110, 155, 380, 645);
25532 disp.dialog_items.insert(
25533 dialog_ptr,
25534 vec![DialogItem {
25535 item_type: 0x80,
25536 proc_ptr: user_item_proc,
25537 ..DialogItem::default()
25538 }],
25539 );
25540 disp.dialog_saved_pixels
25541 .insert(dialog_ptr, vec![0x33; 66 * 116]);
25542 disp.window_stack
25543 .push((prev_window, (0, 0, 342, 512), 2, "Prev".to_string()));
25544
25545 bus.write_long(TEST_SP, user_item_proc);
25546 let result = disp.dispatch_dialog(true, 0x183, &mut cpu, &mut bus);
25547 assert!(result.unwrap().is_ok());
25548 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 4);
25549 assert_eq!(disp.window_list, vec![dialog_ptr, prev_window]);
25550 assert_eq!(disp.front_window, dialog_ptr);
25551 assert!(disp.dialog_items.contains_key(&dialog_ptr));
25552 assert!(disp.dialog_saved_pixels.contains_key(&dialog_ptr));
25553 }
25554
25555 #[test]
25556 fn dispos_dialog_after_modal_button_hit_recovers_stale_proc_argument() {
25557 let (mut disp, mut cpu, mut bus) = setup();
25563 let dialog_ptr = 0x200000u32;
25564 let prev_window = 0x181000u32;
25565 let stale_proc_arg = 0x00016178u32;
25566
25567 seed_window_regions(&mut bus, prev_window, (0, 0, 342, 512));
25568 disp.window_list = vec![dialog_ptr, prev_window];
25569 disp.front_window = dialog_ptr;
25570 disp.current_port = dialog_ptr;
25571 disp.window_bounds = (110, 155, 380, 645);
25572 disp.dialog_modal_entered.insert(dialog_ptr);
25573 disp.pending_modal_button_dispose_dialog = Some(dialog_ptr);
25574 disp.dialog_items.insert(
25575 dialog_ptr,
25576 vec![DialogItem {
25577 item_type: 4,
25578 text: String::from("OK"),
25579 ..DialogItem::default()
25580 }],
25581 );
25582 disp.window_stack
25583 .push((prev_window, (0, 0, 342, 512), 2, "Prev".to_string()));
25584
25585 bus.write_long(TEST_SP, stale_proc_arg);
25586 let result = disp.dispatch_dialog(true, 0x183, &mut cpu, &mut bus);
25587 assert!(result.unwrap().is_ok());
25588 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 4);
25589 assert_eq!(disp.window_list, vec![prev_window]);
25590 assert_eq!(disp.front_window, prev_window);
25591 assert!(!disp.dialog_items.contains_key(&dialog_ptr));
25592 assert!(disp.pending_modal_button_dispose_dialog.is_none());
25593 }
25594
25595 #[test]
25596 fn dispose_dialog_disposes_allocated_storage_and_items() {
25597 let (mut disp, mut cpu, mut bus) = setup();
25600 install_new_dialog_test_screen(&mut disp, &mut bus, 0x77);
25601 let ditl = build_test_ditl_item(8, (10, 12, 24, 140), b"Static");
25602 let (items_handle, items_ptr) = install_ditl_handle_for_test(&mut bus, &ditl);
25603
25604 let (_sp, dialog_ptr) = call_new_dialog_for_test(
25605 &mut disp,
25606 &mut cpu,
25607 &mut bus,
25608 0,
25609 (80, 90, 150, 260),
25610 false,
25611 4,
25612 0xFFFF_FFFF,
25613 items_handle,
25614 );
25615
25616 let text_handle = ditl_item_handle_field(&bus, items_ptr, 1);
25617 let text_ptr = bus.read_long(text_handle);
25618 let te_handle = bus.read_long(dialog_ptr + 160);
25619 let te_ptr = bus.read_long(te_handle);
25620
25621 cpu.write_reg(Register::A7, TEST_SP);
25622 bus.write_long(TEST_SP, dialog_ptr);
25623 disp.dispatch_dialog(true, 0x183, &mut cpu, &mut bus)
25624 .unwrap()
25625 .unwrap();
25626
25627 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 4);
25628 assert_eq!(bus.get_alloc_size(text_ptr), None);
25629 assert_eq!(bus.get_alloc_size(text_handle), None);
25630 assert_eq!(bus.get_alloc_size(te_ptr), None);
25631 assert_eq!(bus.get_alloc_size(te_handle), None);
25632 assert_eq!(bus.get_alloc_size(items_ptr), None);
25633 assert_eq!(bus.get_alloc_size(items_handle), None);
25634 assert_eq!(bus.get_alloc_size(dialog_ptr), None);
25635 assert!(!disp.dialog_items.contains_key(&dialog_ptr));
25636 }
25637
25638 #[test]
25639 fn dispose_dialog_releases_text_handles() {
25640 let (mut disp, mut cpu, mut bus) = setup();
25643 install_new_dialog_test_screen(&mut disp, &mut bus, 0x77);
25644 let ditl = build_test_ditl_item(16, (10, 12, 24, 140), b"Edit");
25645 let (items_handle, items_ptr) = install_ditl_handle_for_test(&mut bus, &ditl);
25646
25647 let (_sp, dialog_ptr) = call_new_dialog_for_test(
25648 &mut disp,
25649 &mut cpu,
25650 &mut bus,
25651 0,
25652 (80, 90, 150, 260),
25653 false,
25654 4,
25655 0xFFFF_FFFF,
25656 items_handle,
25657 );
25658 let item_text_handle = ditl_item_handle_field(&bus, items_ptr, 1);
25659 let item_text_ptr = bus.read_long(item_text_handle);
25660 let dialog_te_handle = bus.read_long(dialog_ptr + 160);
25661 let dialog_te_ptr = bus.read_long(dialog_te_handle);
25662
25663 cpu.write_reg(Register::A7, TEST_SP);
25664 bus.write_long(TEST_SP, dialog_ptr);
25665 disp.dispatch_dialog(true, 0x183, &mut cpu, &mut bus)
25666 .unwrap()
25667 .unwrap();
25668
25669 assert_eq!(bus.get_alloc_size(item_text_ptr), None);
25670 assert_eq!(bus.get_alloc_size(item_text_handle), None);
25671 assert_eq!(bus.get_alloc_size(dialog_te_ptr), None);
25672 assert_eq!(bus.get_alloc_size(dialog_te_handle), None);
25673 assert!(!disp.dialog_item_handles.contains_key(&item_text_handle));
25674 }
25675
25676 #[test]
25677 fn dispose_dialog_releases_control_mappings() {
25678 let (mut disp, mut cpu, mut bus) = setup();
25681 install_new_dialog_test_screen(&mut disp, &mut bus, 0x77);
25682 let ditl = build_test_ditl_item(4, (36, 40, 58, 120), b"OK");
25683 let (items_handle, _items_ptr) = install_ditl_handle_for_test(&mut bus, &ditl);
25684
25685 let (_sp, dialog_ptr) = call_new_dialog_for_test(
25686 &mut disp,
25687 &mut cpu,
25688 &mut bus,
25689 0,
25690 (80, 90, 150, 260),
25691 false,
25692 4,
25693 0xFFFF_FFFF,
25694 items_handle,
25695 );
25696 let box_ptr = bus.alloc(8);
25697 let item_ptr = bus.alloc(4);
25698 let type_ptr = bus.alloc(2);
25699 let snapshot = get_ditem_snapshot_for_test(
25700 &mut disp, &mut cpu, &mut bus, dialog_ptr, 1, box_ptr, item_ptr, type_ptr,
25701 );
25702 let control_handle = snapshot.item;
25703 let control_ptr = bus.read_long(control_handle);
25704 assert_ne!(control_handle, 0);
25705 assert_ne!(control_ptr, 0);
25706 assert_eq!(
25707 disp.dialog_control_handles.get(&control_handle),
25708 Some(&(dialog_ptr, 1))
25709 );
25710 assert!(disp.control_proc_ids.contains_key(&control_ptr));
25711 let aux_handle = disp.ensure_control_aux_record(&mut bus, control_handle);
25712 let aux_ptr = bus.read_long(aux_handle);
25713 assert_ne!(aux_handle, 0);
25714 assert_ne!(aux_ptr, 0);
25715
25716 cpu.write_reg(Register::A7, TEST_SP);
25717 bus.write_long(TEST_SP, dialog_ptr);
25718 disp.dispatch_dialog(true, 0x183, &mut cpu, &mut bus)
25719 .unwrap()
25720 .unwrap();
25721
25722 assert_eq!(bus.get_alloc_size(control_ptr), None);
25723 assert_eq!(bus.get_alloc_size(control_handle), None);
25724 assert_eq!(bus.get_alloc_size(aux_ptr), None);
25725 assert_eq!(bus.get_alloc_size(aux_handle), None);
25726 assert!(disp.control_aux_state(control_handle).is_none());
25727 assert!(!disp.dialog_control_handles.contains_key(&control_handle));
25728 assert!(!disp.control_proc_ids.contains_key(&control_ptr));
25729 }
25730
25731 #[test]
25732 fn dispose_dialog_clears_visible_snapshot() {
25733 let (mut disp, mut cpu, mut bus) = setup();
25734 let dialog_ptr = bus.alloc(170);
25735 disp.window_list = vec![dialog_ptr];
25736 disp.dialog_visible_snapshots.insert(
25737 dialog_ptr,
25738 PersistentDialogSnapshot {
25739 bounds: (10, 20, 30, 40),
25740 pixels: vec![0x55; 400],
25741 },
25742 );
25743
25744 bus.write_long(TEST_SP, dialog_ptr);
25745 disp.dispatch_dialog(true, 0x183, &mut cpu, &mut bus)
25746 .unwrap()
25747 .unwrap();
25748
25749 assert!(!disp.dialog_visible_snapshots.contains_key(&dialog_ptr));
25750 }
25751
25752 #[test]
25753 fn dispose_dialog_clears_retained_click_state() {
25754 let (mut disp, mut cpu, mut bus) = setup();
25755 let dialog_ptr = bus.alloc(170);
25756 disp.window_list = vec![dialog_ptr];
25757 disp.retained_modal_dialog_click = Some(RetainedModalDialogClickState {
25758 dialog_ptr,
25759 item_no: 1,
25760 rect: (10, 20, 30, 40),
25761 title: "OK".to_string(),
25762 is_default: true,
25763 highlighted: true,
25764 delivered_to_app: false,
25765 });
25766
25767 bus.write_long(TEST_SP, dialog_ptr);
25768 disp.dispatch_dialog(true, 0x183, &mut cpu, &mut bus)
25769 .unwrap()
25770 .unwrap();
25771
25772 assert!(disp.retained_modal_dialog_click.is_none());
25773 }
25774
25775 #[test]
25776 fn dispose_dialog_with_active_tracking_cancels_tracking() {
25777 let (mut disp, mut cpu, mut bus) = setup();
25778 let dialog_ptr = 0x200000u32;
25779 disp.dialog_tracking = Some(DialogTrackingState {
25780 dialog_ptr,
25781 ..Default::default()
25782 });
25783
25784 bus.write_long(TEST_SP, dialog_ptr);
25785 disp.dispatch_dialog(true, 0x183, &mut cpu, &mut bus)
25786 .unwrap()
25787 .unwrap();
25788
25789 assert!(disp.dialog_tracking.is_none());
25790 }
25791
25792 #[test]
25793 fn retained_modal_dialog_consumes_outside_mouse_down() {
25794 let (mut disp, mut cpu, mut bus) = setup();
25795 let dialog_ptr = bus.alloc(170);
25796 let prev_window = bus.alloc(170);
25797 seed_retained_modal_dialog(&mut disp, &mut bus, dialog_ptr, prev_window, 1);
25798
25799 disp.push_mouse_down(140, 260);
25800 let (what, _message, _where_v, _where_h, _modifiers, has_event) =
25801 disp.dequeue_toolbox_event(&mut cpu, &mut bus, 0xFFFF);
25802
25803 assert!(!has_event);
25804 assert_eq!(what, 0);
25805 assert!(disp.event_queue.is_empty());
25806 assert_eq!(disp.front_window, dialog_ptr);
25807 assert!(
25808 disp.retained_modal_dialog_click.is_some(),
25809 "outside click must be captured until mouseUp so it cannot leak behind the dialog"
25810 );
25811
25812 disp.push_mouse_up(140, 260);
25813 let (_what, _message, _where_v, _where_h, _modifiers, has_event) =
25814 disp.dequeue_toolbox_event(&mut cpu, &mut bus, 0xFFFF);
25815
25816 assert!(!has_event);
25817 assert_eq!(disp.front_window, dialog_ptr);
25818 assert!(disp.retained_modal_dialog_click.is_none());
25819 assert!(
25820 disp.dialog_items.contains_key(&dialog_ptr),
25821 "outside clicks do not dismiss modal dialogs"
25822 );
25823 }
25824
25825 #[test]
25826 fn app_owned_modal_dialog_mouse_down_is_delivered_to_event_loop() {
25827 let (mut disp, mut cpu, mut bus) = setup();
25833 let dialog_ptr = bus.alloc(170);
25834 let prev_window = bus.alloc(170);
25835 let screen_base = 0x300000u32;
25836 let row_bytes = 320u32;
25837 disp.set_screen_mode_for_test(screen_base, row_bytes, 320, 200, 8);
25838 let probe = screen_base + 70 * row_bytes + 150;
25839 bus.write_byte(probe, 17);
25840
25841 seed_app_owned_modal_dialog(&mut disp, &mut bus, dialog_ptr, prev_window, 1);
25842
25843 disp.push_mouse_down(70, 150);
25844 let (what, _message, where_v, where_h, _modifiers, has_event) =
25845 disp.dequeue_toolbox_event(&mut cpu, &mut bus, 0xFFFF);
25846
25847 assert!(has_event);
25848 assert_eq!(what, 1);
25849 assert_eq!((where_v, where_h), (70, 150));
25850 assert_eq!(
25851 disp.retained_modal_dialog_click
25852 .as_ref()
25853 .map(|click| (click.item_no, click.delivered_to_app)),
25854 Some((1, true))
25855 );
25856 assert_eq!(
25857 bus.read_byte(probe),
25858 238,
25859 "app-owned modal button should still show the pressed state"
25860 );
25861 assert_eq!(disp.front_window, dialog_ptr);
25862 assert!(
25863 disp.dialog_items.contains_key(&dialog_ptr),
25864 "application-owned dialog should not be closed by event dequeue"
25865 );
25866 }
25867
25868 #[test]
25869 fn app_owned_modal_dialog_button_mouse_up_dismisses_after_delivery() {
25870 let (mut disp, mut cpu, mut bus) = setup();
25876 let dialog_ptr = bus.alloc(170);
25877 let prev_window = bus.alloc(170);
25878 let screen_base = 0x300000u32;
25879 let row_bytes = 320u32;
25880 disp.set_screen_mode_for_test(screen_base, row_bytes, 320, 200, 8);
25881 let probe = screen_base + 70 * row_bytes + 150;
25882 bus.write_byte(probe, 17);
25883
25884 seed_app_owned_modal_dialog(&mut disp, &mut bus, dialog_ptr, prev_window, 1);
25885
25886 disp.push_mouse_down(70, 150);
25887 let (what, _message, where_v, where_h, _modifiers, has_event) =
25888 disp.dequeue_toolbox_event(&mut cpu, &mut bus, 0xFFFF);
25889 assert!(has_event);
25890 assert_eq!(what, 1);
25891 assert_eq!((where_v, where_h), (70, 150));
25892 assert_eq!(disp.front_window, dialog_ptr);
25893
25894 disp.push_mouse_up(70, 150);
25895 let (what, _message, where_v, where_h, _modifiers, has_event) =
25896 disp.dequeue_toolbox_event(&mut cpu, &mut bus, 0xFFFF);
25897
25898 assert!(
25899 has_event,
25900 "app-owned modal mouseUp remains deliverable after compatibility close"
25901 );
25902 assert_eq!(what, 2);
25903 assert_eq!((where_v, where_h), (70, 150));
25904 assert_eq!(bus.read_byte(probe), 17);
25905 assert_eq!(disp.front_window, prev_window);
25906 assert!(!disp.dialog_items.contains_key(&dialog_ptr));
25907 assert!(disp.retained_modal_dialog_click.is_none());
25908 assert!(
25909 disp.event_queue
25910 .iter()
25911 .any(|event| event.what == 6 && event.message == prev_window),
25912 "closing the dialog must invalidate/update the exposed window"
25913 );
25914 }
25915
25916 #[test]
25917 fn retained_modal_dialog_button_click_highlights_then_dismisses() {
25918 let (mut disp, mut cpu, mut bus) = setup();
25919 let dialog_ptr = bus.alloc(170);
25920 let prev_window = bus.alloc(170);
25921 let screen_base = 0x300000u32;
25922 let row_bytes = 320u32;
25923 disp.set_screen_mode_for_test(screen_base, row_bytes, 320, 200, 8);
25924 seed_retained_modal_dialog(&mut disp, &mut bus, dialog_ptr, prev_window, 1);
25925
25926 let probe = screen_base + 70 * row_bytes + 150;
25927 bus.write_byte(probe, 17);
25928 let bounds = TrapDispatcher::dialog_screen_bounds(&bus, dialog_ptr);
25929 assert_eq!(bounds, (0, 0, 100, 220));
25930 let base_pixels = disp.save_dialog_pixels(&bus, bounds);
25931 disp.dialog_visible_snapshots.insert(
25932 dialog_ptr,
25933 PersistentDialogSnapshot {
25934 bounds,
25935 pixels: base_pixels,
25936 },
25937 );
25938 let items = disp.dialog_items.get(&dialog_ptr).cloned().unwrap();
25939 assert_eq!(
25940 disp.dialog_item_hit_test(
25941 &bus,
25942 &items,
25943 bounds,
25944 70,
25945 150,
25946 &disp.dialog_popup_original_rects,
25947 dialog_ptr,
25948 ),
25949 1
25950 );
25951
25952 disp.push_mouse_down(70, 150);
25953 let (_what, _message, _where_v, _where_h, _modifiers, has_event) =
25954 disp.dequeue_toolbox_event(&mut cpu, &mut bus, 0xFFFF);
25955
25956 assert!(!has_event);
25957 assert_eq!(
25958 disp.retained_modal_dialog_click
25959 .as_ref()
25960 .map(|click| click.item_no),
25961 Some(1)
25962 );
25963 assert_eq!(
25964 bus.read_byte(probe),
25965 238,
25966 "button interior should invert on mouseDown"
25967 );
25968 disp.restore_visible_dialog_snapshots(&mut bus);
25969 disp.redraw_retained_modal_dialog_click(&mut bus);
25970 assert_eq!(
25971 bus.read_byte(probe),
25972 238,
25973 "pressed retained-modal button must survive visible-dialog snapshot redraw"
25974 );
25975 assert_eq!(disp.front_window, dialog_ptr);
25976
25977 disp.push_mouse_up(70, 150);
25978 let (_what, _message, _where_v, _where_h, _modifiers, has_event) =
25979 disp.dequeue_toolbox_event(&mut cpu, &mut bus, 0xFFFF);
25980
25981 assert!(!has_event);
25982 assert_eq!(
25983 bus.read_byte(probe),
25984 17,
25985 "button should be unhighlighted before close"
25986 );
25987 assert_eq!(disp.front_window, prev_window);
25988 assert!(!disp.dialog_items.contains_key(&dialog_ptr));
25989 assert!(disp.retained_modal_dialog_click.is_none());
25990 assert!(
25991 !disp
25992 .event_queue
25993 .iter()
25994 .any(|event| matches!(event.what, 1 | 2)),
25995 "consumed dialog click must not leave mouse events for the game loop"
25996 );
25997 assert!(
25998 disp.event_queue
25999 .iter()
26000 .any(|event| event.what == 6 && event.message == prev_window),
26001 "closing the dialog must invalidate/update the exposed window"
26002 );
26003 }
26004
26005 #[test]
26006 fn modal_dialog_button_tracking_systemless_theme_routes_pressed_state_through_provider() {
26007 let (mut disp, _cpu, mut bus) = setup();
26008 let screen_base = 0x300000u32;
26009 let row_bytes = 64u32;
26010 let bounds = (0, 0, 100, 220);
26011 let item_rect = (60, 120, 80, 180);
26012 let screen_rect = TrapDispatcher::dialog_item_screen_rect(bounds, item_rect);
26013
26014 disp.set_ui_theme_id(UiThemeId::SystemlessDefault);
26015 disp.set_screen_mode_for_test(screen_base, row_bytes, 512, 342, 1);
26016 disp.draw_button(
26017 &mut bus,
26018 screen_rect.0,
26019 screen_rect.1,
26020 screen_rect.2,
26021 screen_rect.3,
26022 "",
26023 true,
26024 );
26025
26026 let probe_x = 150;
26027 let probe_y = 70;
26028 assert!(
26029 !screen_pixel_is_set(&bus, screen_base, row_bytes, probe_x, probe_y),
26030 "unpressed provider button interior should start clear"
26031 );
26032
26033 let dialog_ptr = bus.alloc(170);
26034 disp.dialog_tracking = Some(DialogTrackingState {
26035 dialog_ptr,
26036 bounds,
26037 items: vec![DialogItem {
26038 item_type: 4,
26039 rect: item_rect,
26040 text: String::new(),
26041 resource_id: 0,
26042 proc_ptr: 0,
26043 sel_start: 0,
26044 sel_end: 0,
26045 }],
26046 default_item: 1,
26047 active_button: Some(DialogButtonTrackingState {
26048 item_no: 1,
26049 rect: item_rect,
26050 title: String::new(),
26051 is_default: true,
26052 highlighted: false,
26053 }),
26054 ..Default::default()
26055 });
26056
26057 disp.mouse_button = true;
26058 disp.mouse_pos = (probe_y, probe_x);
26059 disp.handle_dialog_button_tracking(&mut bus);
26060
26061 assert!(
26062 screen_pixel_is_set(&bus, screen_base, row_bytes, probe_x, probe_y),
26063 "inside tracking should paint the provider pressed fill"
26064 );
26065 assert_eq!(
26066 disp.dialog_tracking
26067 .as_ref()
26068 .and_then(|tracking| tracking.active_button.as_ref())
26069 .map(|button| button.highlighted),
26070 Some(true)
26071 );
26072
26073 disp.mouse_pos = (40, 50);
26074 disp.handle_dialog_button_tracking(&mut bus);
26075
26076 assert!(
26077 !screen_pixel_is_set(&bus, screen_base, row_bytes, probe_x, probe_y),
26078 "outside tracking should redraw unpressed provider chrome"
26079 );
26080 assert_eq!(
26081 disp.dialog_tracking
26082 .as_ref()
26083 .and_then(|tracking| tracking.active_button.as_ref())
26084 .map(|button| button.highlighted),
26085 Some(false)
26086 );
26087 }
26088
26089 #[test]
26090 fn retained_modal_dialog_capture_does_not_apply_to_modeless_dialogs() {
26091 let (mut disp, mut cpu, mut bus) = setup();
26092 let dialog_ptr = bus.alloc(170);
26093 let prev_window = bus.alloc(170);
26094 seed_retained_modal_dialog(&mut disp, &mut bus, dialog_ptr, prev_window, 4);
26095
26096 disp.push_mouse_down(70, 150);
26097 let (what, _message, where_v, where_h, _modifiers, has_event) =
26098 disp.dequeue_toolbox_event(&mut cpu, &mut bus, 0xFFFF);
26099
26100 assert!(has_event);
26101 assert_eq!(what, 1);
26102 assert_eq!((where_v, where_h), (70, 150));
26103 assert!(disp.retained_modal_dialog_click.is_none());
26104 assert_eq!(disp.front_window, dialog_ptr);
26105 }
26106
26107 #[test]
26108 fn dispos_dialog_clears_tracking_for_disposed_dialog() {
26109 let (mut disp, mut cpu, mut bus) = setup();
26110 let dialog_ptr = 0x200000u32;
26111 disp.dialog_tracking = Some(crate::trap::dispatch::DialogTrackingState {
26112 dialog_ptr,
26113 bounds: (0, 0, 32, 32),
26114 title: String::new(),
26115 proc_id: 1,
26116 items: Vec::new(),
26117 default_item: 0,
26118 cancel_item: 0,
26119 edit_text: String::new(),
26120 edit_item: 0,
26121 saved_pixels: Vec::new(),
26122 stack_ptr: 0,
26123 item_hit_ptr: 0,
26124 rendered_pixels: Vec::new(),
26125 flash_remaining: 0,
26126 flash_delay: 0,
26127 flash_item: 0,
26128 edit_text_modified: false,
26129 draw_proc_queue: VecDeque::new(),
26130 draw_procs_done: true,
26131 rendered_pixels_final: true,
26132 filter_proc: 0,
26133 game_managed: false,
26134 last_filter_event: None,
26135 popup_draws: Vec::new(),
26136 active_popup: None,
26137 active_button: None,
26138 active_user_item: None,
26139 });
26140
26141 bus.write_long(TEST_SP, dialog_ptr);
26142 let result = disp.dispatch_dialog(true, 0x183, &mut cpu, &mut bus);
26143 assert!(result.unwrap().is_ok());
26144 assert!(disp.dialog_tracking.is_none());
26145 }
26146
26147 #[test]
26148 fn dispos_dialog_clears_dialog_scoped_side_maps_for_disposed_dialog() {
26149 let (mut disp, mut cpu, mut bus) = setup();
26150 let dialog_ptr = 0x200000u32;
26151 let other_dialog_ptr = 0x210000u32;
26152 let text_handle = 0x300000u32;
26153 let other_text_handle = 0x300010u32;
26154 let ctrl_handle = 0x300020u32;
26155 let other_ctrl_handle = 0x300030u32;
26156
26157 disp.dialog_items
26158 .insert(dialog_ptr, vec![DialogItem::default()]);
26159 disp.dialog_items
26160 .insert(other_dialog_ptr, vec![DialogItem::default()]);
26161 disp.dialog_item_handles
26162 .insert(text_handle, (dialog_ptr, 0));
26163 disp.dialog_item_handles
26164 .insert(other_text_handle, (other_dialog_ptr, 0));
26165 disp.dialog_control_handles
26166 .insert(ctrl_handle, (dialog_ptr, 1));
26167 disp.dialog_control_handles
26168 .insert(other_ctrl_handle, (other_dialog_ptr, 1));
26169 disp.dialog_control_values.insert((dialog_ptr, 1), 1);
26170 disp.dialog_control_values.insert((other_dialog_ptr, 1), 1);
26171 disp.hidden_dialog_item_rects
26172 .insert((dialog_ptr, 1), (10, 20, 30, 40));
26173 disp.hidden_dialog_item_rects
26174 .insert((other_dialog_ptr, 1), (50, 60, 70, 80));
26175 disp.dialog_item_popup_menus.insert((dialog_ptr, 1), 900);
26176 disp.dialog_item_popup_menus
26177 .insert((other_dialog_ptr, 1), 901);
26178 disp.dialog_popup_original_rects
26179 .insert((dialog_ptr, 1), (10, 20, 30, 130));
26180 disp.dialog_popup_original_rects
26181 .insert((other_dialog_ptr, 1), (50, 60, 70, 180));
26182 disp.dialog_popup_candidate_items.insert((dialog_ptr, 1));
26183 disp.dialog_popup_candidate_items
26184 .insert((other_dialog_ptr, 1));
26185 disp.dialog_cancel_items.insert(dialog_ptr, 2);
26186 disp.dialog_cancel_items.insert(other_dialog_ptr, 3);
26187 disp.pending_dialog_popup_menu = Some(PendingDialogPopupMenu {
26188 dialog_ptr,
26189 item_no: 1,
26190 menu_id: 900,
26191 rect: (10, 20, 30, 130),
26192 });
26193
26194 bus.write_long(TEST_SP, dialog_ptr);
26195 disp.dispatch_dialog(true, 0x183, &mut cpu, &mut bus)
26196 .unwrap()
26197 .unwrap();
26198
26199 assert!(!disp.dialog_items.contains_key(&dialog_ptr));
26200 assert!(disp.dialog_items.contains_key(&other_dialog_ptr));
26201 assert!(!disp.dialog_item_handles.contains_key(&text_handle));
26202 assert!(disp.dialog_item_handles.contains_key(&other_text_handle));
26203 assert!(!disp.dialog_control_handles.contains_key(&ctrl_handle));
26204 assert!(disp.dialog_control_handles.contains_key(&other_ctrl_handle));
26205 assert!(!disp.dialog_control_values.contains_key(&(dialog_ptr, 1)));
26206 assert!(disp
26207 .dialog_control_values
26208 .contains_key(&(other_dialog_ptr, 1)));
26209 assert!(!disp.hidden_dialog_item_rects.contains_key(&(dialog_ptr, 1)));
26210 assert!(disp
26211 .hidden_dialog_item_rects
26212 .contains_key(&(other_dialog_ptr, 1)));
26213 assert!(!disp.dialog_item_popup_menus.contains_key(&(dialog_ptr, 1)));
26214 assert!(disp
26215 .dialog_item_popup_menus
26216 .contains_key(&(other_dialog_ptr, 1)));
26217 assert!(!disp
26218 .dialog_popup_original_rects
26219 .contains_key(&(dialog_ptr, 1)));
26220 assert!(disp
26221 .dialog_popup_original_rects
26222 .contains_key(&(other_dialog_ptr, 1)));
26223 assert!(!disp.dialog_popup_candidate_items.contains(&(dialog_ptr, 1)));
26224 assert!(disp
26225 .dialog_popup_candidate_items
26226 .contains(&(other_dialog_ptr, 1)));
26227 assert!(!disp.dialog_cancel_items.contains_key(&dialog_ptr));
26228 assert!(disp.dialog_cancel_items.contains_key(&other_dialog_ptr));
26229 assert!(disp.pending_dialog_popup_menu.is_none());
26230 }
26231
26232 #[test]
26248 fn disposdialog_restores_saved_background_pixels() {
26249 let (mut disp, mut cpu, mut bus) = setup();
26250
26251 let screen_base = 0x300000u32;
26253 let row_bytes: u32 = 640;
26254 disp.set_screen_mode_for_test(screen_base, row_bytes, 640, 480, 8);
26255
26256 let bounds = (100i16, 100i16, 150i16, 200i16);
26257 let dialog_ptr = 0x200000u32;
26258 disp.front_window = dialog_ptr;
26259 disp.window_bounds = bounds;
26260
26261 for y in 92u32..158 {
26264 for x in 92u32..208 {
26265 bus.write_byte(screen_base + y * row_bytes + x, 0xCC);
26266 }
26267 }
26268
26269 disp.dialog_saved_pixels
26272 .insert(dialog_ptr, vec![0x33; 66 * 116]);
26273
26274 bus.write_long(TEST_SP, dialog_ptr);
26275 let result = disp.dispatch_dialog(true, 0x183, &mut cpu, &mut bus);
26276 assert!(result.unwrap().is_ok());
26277
26278 for y in 92u32..158 {
26280 for x in 92u32..208 {
26281 let addr = screen_base + y * row_bytes + x;
26282 let got = bus.read_byte(addr);
26283 assert_eq!(
26284 got, 0x33,
26285 "byte at ({},{}) must be restored background 0x33, got 0x{:02X}",
26286 x, y, got
26287 );
26288 }
26289 }
26290 assert!(
26291 !disp.dialog_saved_pixels.contains_key(&dialog_ptr),
26292 "saved pixels must be consumed after DisposDialog"
26293 );
26294 }
26295
26296 #[test]
26297 fn visible_dialog_saved_background_tracks_screen_draws_behind_it() {
26298 let (mut disp, mut cpu, mut bus) = setup();
26303 let screen_base = 0x300000u32;
26304 let row_bytes: u32 = 640;
26305 disp.set_screen_mode_for_test(screen_base, row_bytes, 640, 480, 8);
26306
26307 let bounds = (100i16, 100i16, 150i16, 200i16);
26308 let dialog_ptr = bus.alloc(170);
26309 let background_port = bus.alloc(170);
26310 let save_top = 92u32;
26311 let save_left = 92u32;
26312 let save_bottom = 158u32;
26313 let save_right = 208u32;
26314 let row_width = (save_right - save_left) as usize;
26315
26316 for y in save_top..save_bottom {
26317 for x in save_left..save_right {
26318 bus.write_byte(screen_base + y * row_bytes + x, 0xEE);
26319 }
26320 }
26321
26322 disp.dialog_saved_pixels.insert(
26323 dialog_ptr,
26324 vec![0x11; row_width * (save_bottom - save_top) as usize],
26325 );
26326 disp.dialog_visible_snapshots.insert(
26327 dialog_ptr,
26328 PersistentDialogSnapshot {
26329 bounds,
26330 pixels: vec![0xEE; row_width * (save_bottom - save_top) as usize],
26331 },
26332 );
26333
26334 for y in 120u32..123 {
26335 for x in 130u32..139 {
26336 bus.write_byte(screen_base + y * row_bytes + x, 0x44);
26337 }
26338 }
26339 disp.refresh_dialog_saved_pixels_after_screen_draw(
26340 &bus,
26341 background_port,
26342 (120, 130, 123, 139),
26343 );
26344
26345 let saved = disp.dialog_saved_pixels.get(&dialog_ptr).unwrap();
26346 let touched_idx = (120 - save_top) as usize * row_width + (130 - save_left) as usize;
26347 let untouched_idx = (100 - save_top) as usize * row_width + (100 - save_left) as usize;
26348 assert_eq!(saved[touched_idx], 0x44);
26349 assert_eq!(saved[untouched_idx], 0x11);
26350
26351 for y in save_top..save_bottom {
26352 for x in save_left..save_right {
26353 bus.write_byte(screen_base + y * row_bytes + x, 0xEE);
26354 }
26355 }
26356
26357 disp.front_window = dialog_ptr;
26358 disp.current_port = dialog_ptr;
26359 disp.window_bounds = bounds;
26360 disp.window_list = vec![dialog_ptr];
26361 disp.window_stack.push((0, (0, 0, 0, 0), -1, String::new()));
26362
26363 bus.write_long(TEST_SP, dialog_ptr);
26364 disp.dispatch_dialog(true, 0x183, &mut cpu, &mut bus)
26365 .unwrap()
26366 .unwrap();
26367
26368 assert_eq!(bus.read_byte(screen_base + 120 * row_bytes + 130), 0x44);
26369 assert_eq!(bus.read_byte(screen_base + 100 * row_bytes + 100), 0x11);
26370 }
26371
26372 #[test]
26373 fn active_modal_dialog_saved_background_tracks_screen_draws_behind_it() {
26374 let (mut disp, _cpu, mut bus) = setup();
26379 let screen_base = 0x300000u32;
26380 let row_bytes: u32 = 640;
26381 disp.set_screen_mode_for_test(screen_base, row_bytes, 640, 480, 8);
26382
26383 let bounds = (100i16, 100i16, 150i16, 200i16);
26384 let dialog_ptr = bus.alloc(170);
26385 let background_port = bus.alloc(170);
26386 let save_top = 92u32;
26387 let save_left = 92u32;
26388 let save_bottom = 158u32;
26389 let save_right = 208u32;
26390 let row_width = (save_right - save_left) as usize;
26391
26392 for y in save_top..save_bottom {
26393 for x in save_left..save_right {
26394 bus.write_byte(screen_base + y * row_bytes + x, 0xEE);
26395 }
26396 }
26397
26398 disp.dialog_saved_pixels.insert(
26399 dialog_ptr,
26400 vec![0x11; row_width * (save_bottom - save_top) as usize],
26401 );
26402 let mut tracking = dialog_tracking_state_for_test(dialog_ptr);
26403 tracking.bounds = bounds;
26404 disp.dialog_tracking = Some(tracking);
26405
26406 for y in 120u32..123 {
26407 for x in 130u32..139 {
26408 bus.write_byte(screen_base + y * row_bytes + x, 0x44);
26409 }
26410 }
26411 disp.refresh_dialog_saved_pixels_after_screen_draw(
26412 &bus,
26413 background_port,
26414 (120, 130, 123, 139),
26415 );
26416
26417 let saved = disp.dialog_saved_pixels.get(&dialog_ptr).unwrap();
26418 let touched_idx = (120 - save_top) as usize * row_width + (130 - save_left) as usize;
26419 let untouched_idx = (100 - save_top) as usize * row_width + (100 - save_left) as usize;
26420 assert_eq!(saved[touched_idx], 0x44);
26421 assert_eq!(saved[untouched_idx], 0x11);
26422 }
26423
26424 #[test]
26425 fn retained_modal_dialog_saved_background_tracks_same_port_screen_draws() {
26426 let (mut disp, _cpu, mut bus) = setup();
26431 let screen_base = 0x300000u32;
26432 let row_bytes: u32 = 640;
26433 disp.set_screen_mode_for_test(screen_base, row_bytes, 640, 480, 8);
26434
26435 let bounds = (100i16, 100i16, 150i16, 200i16);
26436 let dialog_ptr = bus.alloc(170);
26437 let save_top = 92u32;
26438 let save_left = 92u32;
26439 let save_bottom = 158u32;
26440 let save_right = 208u32;
26441 let row_width = (save_right - save_left) as usize;
26442
26443 disp.dialog_saved_pixels.insert(
26444 dialog_ptr,
26445 vec![0x11; row_width * (save_bottom - save_top) as usize],
26446 );
26447 disp.dialog_visible_snapshots.insert(
26448 dialog_ptr,
26449 PersistentDialogSnapshot {
26450 bounds,
26451 pixels: vec![0xEE; row_width * (save_bottom - save_top) as usize],
26452 },
26453 );
26454
26455 let touched_idx = (120 - save_top) as usize * row_width + (130 - save_left) as usize;
26456 bus.write_byte(screen_base + 120 * row_bytes + 130, 0x22);
26457 disp.refresh_dialog_saved_pixels_after_screen_draw(&bus, dialog_ptr, (120, 130, 121, 131));
26458 assert_eq!(
26459 disp.dialog_saved_pixels.get(&dialog_ptr).unwrap()[touched_idx],
26460 0x11,
26461 "pre-modal same-port dialog drawing must not become saved-under background"
26462 );
26463
26464 disp.dialog_modal_entered.insert(dialog_ptr);
26465 bus.write_byte(screen_base + 120 * row_bytes + 130, 0x44);
26466 disp.refresh_dialog_saved_pixels_after_screen_draw(&bus, dialog_ptr, (120, 130, 121, 131));
26467 assert_eq!(
26468 disp.dialog_saved_pixels.get(&dialog_ptr).unwrap()[touched_idx],
26469 0x44,
26470 "retained modal same-port background drawing should update saved-under pixels"
26471 );
26472 }
26473
26474 #[test]
26475 fn stale_fullscreen_dialog_exposure_restores_from_offscreen_scene_port() {
26476 let (mut disp, _cpu, mut bus) = setup();
26477 let screen_base = bus.alloc(100 * 80);
26478 bus.write_long(0x0824, screen_base);
26479 disp.screen_mode = (screen_base, 100, 100, 80, 8);
26480 disp.device_clut[0x22] = [0x3333, 0x7777, 0x2222];
26481 disp.color_manager_clut = disp.device_clut;
26482
26483 let offscreen_base = bus.alloc(100 * 80);
26484 bus.write_bytes(offscreen_base, &vec![0x22; 100 * 80]);
26485 let port = bus.alloc(170);
26486 let pixmap_handle = bus.alloc(4);
26487 let pixmap_ptr = bus.alloc(50);
26488 bus.write_word(port + 6, 0xC000);
26489 bus.write_long(port + 2, pixmap_handle);
26490 bus.write_long(pixmap_handle, pixmap_ptr);
26491 bus.write_long(pixmap_ptr, offscreen_base);
26492 bus.write_word(pixmap_ptr + 4, 0x8000 | 100);
26493 bus.write_word(pixmap_ptr + 6, 0);
26494 bus.write_word(pixmap_ptr + 8, 0);
26495 bus.write_word(pixmap_ptr + 10, 80);
26496 bus.write_word(pixmap_ptr + 12, 100);
26497 bus.write_word(pixmap_ptr + 32, 8);
26498 disp.cport_ports.insert(port);
26499
26500 bus.write_bytes(screen_base, &vec![0x00; 100 * 80]);
26501 assert!(
26502 disp.restore_dialog_exposure_from_fullscreen_offscreen_port(&mut bus, (20, 20, 50, 70))
26503 );
26504 assert_eq!(bus.read_byte(screen_base + 20 * 100 + 20), 0x22);
26505 assert_eq!(bus.read_byte(screen_base + 49 * 100 + 69), 0x22);
26506 assert_eq!(bus.read_byte(screen_base + 2 * 100 + 2), 0x00);
26507 }
26508
26509 #[test]
26510 fn disposdialog_restore_is_bounded_by_save_margin() {
26511 let (mut disp, mut cpu, mut bus) = setup();
26515 let screen_base = 0x300000u32;
26516 let row_bytes: u32 = 640;
26517 disp.set_screen_mode_for_test(screen_base, row_bytes, 640, 480, 8);
26518
26519 let bounds = (100i16, 100i16, 150i16, 200i16);
26520 let dialog_ptr = 0x200000u32;
26521 disp.front_window = dialog_ptr;
26522 disp.window_bounds = bounds;
26523
26524 for y in 85u32..165 {
26527 for x in 85u32..215 {
26528 bus.write_byte(screen_base + y * row_bytes + x, 0xAA);
26529 }
26530 }
26531
26532 disp.dialog_saved_pixels
26533 .insert(dialog_ptr, vec![0x33; 66 * 116]);
26534
26535 bus.write_long(TEST_SP, dialog_ptr);
26536 disp.dispatch_dialog(true, 0x183, &mut cpu, &mut bus)
26537 .unwrap()
26538 .unwrap();
26539
26540 for x in 85u32..215 {
26543 assert_eq!(bus.read_byte(screen_base + 91 * row_bytes + x), 0xAA);
26544 }
26545 for x in 85u32..215 {
26547 assert_eq!(bus.read_byte(screen_base + 158 * row_bytes + x), 0xAA);
26548 }
26549 for y in 85u32..165 {
26551 assert_eq!(bus.read_byte(screen_base + y * row_bytes + 91), 0xAA);
26552 }
26553 for y in 85u32..165 {
26555 assert_eq!(bus.read_byte(screen_base + y * row_bytes + 208), 0xAA);
26556 }
26557 for y in 92u32..158 {
26559 for x in 92u32..208 {
26560 assert_eq!(bus.read_byte(screen_base + y * row_bytes + x), 0x33);
26561 }
26562 }
26563 }
26564
26565 #[test]
26566 fn disposdialog_without_saved_pixels_leaves_screen_untouched() {
26567 let (mut disp, mut cpu, mut bus) = setup();
26572 let screen_base = 0x300000u32;
26573 let row_bytes: u32 = 640;
26574 disp.set_screen_mode_for_test(screen_base, row_bytes, 640, 480, 8);
26575
26576 let bounds = (100i16, 100i16, 150i16, 200i16);
26577 let dialog_ptr = 0x200000u32;
26578 disp.front_window = dialog_ptr;
26579 disp.window_bounds = bounds;
26580
26581 for y in 95u32..155 {
26582 for x in 95u32..205 {
26583 bus.write_byte(screen_base + y * row_bytes + x, 0x77);
26584 }
26585 }
26586 assert!(!disp.dialog_saved_pixels.contains_key(&dialog_ptr));
26587
26588 bus.write_long(TEST_SP, dialog_ptr);
26589 disp.dispatch_dialog(true, 0x183, &mut cpu, &mut bus)
26590 .unwrap()
26591 .unwrap();
26592
26593 for y in 95u32..155 {
26594 for x in 95u32..205 {
26595 let addr = screen_base + y * row_bytes + x;
26596 assert_eq!(
26597 bus.read_byte(addr),
26598 0x77,
26599 "DisposDialog without saved pixels must not write to screen"
26600 );
26601 }
26602 }
26603 }
26604
26605 #[test]
26606 fn disposdialog_non_front_does_not_restore() {
26607 let (mut disp, mut cpu, mut bus) = setup();
26613 let screen_base = 0x300000u32;
26614 let row_bytes: u32 = 640;
26615 disp.set_screen_mode_for_test(screen_base, row_bytes, 640, 480, 8);
26616
26617 let dialog_ptr = 0x200000u32;
26618 let other_front = 0x280000u32;
26619 disp.front_window = other_front; disp.window_bounds = (200, 200, 300, 400); for y in 95u32..155 {
26623 for x in 95u32..205 {
26624 bus.write_byte(screen_base + y * row_bytes + x, 0x55);
26625 }
26626 }
26627 disp.dialog_saved_pixels
26628 .insert(dialog_ptr, vec![0x99; 66 * 116]);
26629
26630 bus.write_long(TEST_SP, dialog_ptr);
26631 disp.dispatch_dialog(true, 0x183, &mut cpu, &mut bus)
26632 .unwrap()
26633 .unwrap();
26634
26635 for y in 95u32..155 {
26636 for x in 95u32..205 {
26637 let addr = screen_base + y * row_bytes + x;
26638 assert_eq!(
26639 bus.read_byte(addr),
26640 0x55,
26641 "non-front DisposDialog must not restore over current front window"
26642 );
26643 }
26644 }
26645 assert!(
26646 !disp.dialog_saved_pixels.contains_key(&dialog_ptr),
26647 "saved pixels must still be removed for non-front dispose"
26648 );
26649 }
26650
26651 fn write_pascal_str(bus: &mut crate::memory::MacMemoryBus, addr: u32, s: &[u8]) {
26654 bus.write_byte(addr, s.len() as u8);
26655 for (i, b) in s.iter().enumerate() {
26656 bus.write_byte(addr + 1 + i as u32, *b);
26657 }
26658 }
26659
26660 #[test]
26661 fn param_text_saves_all_four_strings_and_pops_16_bytes() {
26662 let (mut disp, mut cpu, mut bus) = setup();
26663
26664 let p0 = 0x300000u32;
26665 let p1 = 0x300100u32;
26666 let p2 = 0x300200u32;
26667 let p3 = 0x300300u32;
26668 write_pascal_str(&mut bus, p0, b"alpha");
26669 write_pascal_str(&mut bus, p1, b"bravo");
26670 write_pascal_str(&mut bus, p2, b"chi");
26671 write_pascal_str(&mut bus, p3, b"d");
26672
26673 bus.write_long(TEST_SP, p3);
26674 bus.write_long(TEST_SP + 4, p2);
26675 bus.write_long(TEST_SP + 8, p1);
26676 bus.write_long(TEST_SP + 12, p0);
26677
26678 disp.dispatch_dialog(true, 0x18B, &mut cpu, &mut bus)
26679 .unwrap()
26680 .unwrap();
26681
26682 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 16);
26683 assert_eq!(disp.param_text[0], b"alpha");
26684 assert_eq!(disp.param_text[1], b"bravo");
26685 assert_eq!(disp.param_text[2], b"chi");
26686 assert_eq!(disp.param_text[3], b"d");
26687 }
26688
26689 #[test]
26690 fn param_text_empty_pascal_string_clears_slot() {
26691 let (mut disp, mut cpu, mut bus) = setup();
26696 disp.param_text[0] = b"stale".to_vec();
26697 disp.param_text[1] = b"stale".to_vec();
26698 disp.param_text[2] = b"stale".to_vec();
26699 disp.param_text[3] = b"stale".to_vec();
26700
26701 let empty_ptr = 0x300000u32;
26702 write_pascal_str(&mut bus, empty_ptr, b"");
26703
26704 for off in [0u32, 4, 8, 12] {
26705 bus.write_long(TEST_SP + off, empty_ptr);
26706 }
26707
26708 disp.dispatch_dialog(true, 0x18B, &mut cpu, &mut bus)
26709 .unwrap()
26710 .unwrap();
26711
26712 for i in 0..4 {
26713 assert_eq!(
26714 disp.param_text[i],
26715 Vec::<u8>::new(),
26716 "empty Pascal string must clear slot {}",
26717 i
26718 );
26719 }
26720 }
26721
26722 #[test]
26723 fn apply_param_text_substitutes_caret_placeholders() {
26724 let (mut disp, _cpu, _bus) = setup();
26725 disp.param_text[0] = b"MS UserKey".to_vec();
26726 disp.param_text[1] = b"42".to_vec();
26727
26728 assert_eq!(
26729 disp.apply_param_text("Unable to open the \"^0\" file."),
26730 "Unable to open the \"MS UserKey\" file."
26731 );
26732 assert_eq!(disp.apply_param_text("count: ^1"), "count: 42");
26733 assert_eq!(
26734 disp.apply_param_text("plain text without placeholders"),
26735 "plain text without placeholders"
26736 );
26737 assert_eq!(disp.apply_param_text("^0 ^1 ^2 ^3"), "MS UserKey 42 ");
26738 assert_eq!(
26739 disp.apply_param_text("^A literal caret"),
26740 "^A literal caret"
26741 );
26742
26743 assert_eq!(disp.apply_param_text("trailing^"), "trailing^");
26746 assert_eq!(disp.apply_param_text("^^0"), "^MS UserKey");
26747 assert_eq!(disp.apply_param_text("^9 unknown slot"), "^9 unknown slot");
26748 assert_eq!(disp.apply_param_text(""), "");
26749 }
26750
26751 #[test]
26752 fn param_text_nil_pointer_preserves_previous_slot_value() {
26753 let (mut disp, mut cpu, mut bus) = setup();
26759 disp.param_text[0] = b"old0".to_vec();
26760 disp.param_text[1] = b"old1".to_vec();
26761 disp.param_text[2] = b"old2".to_vec();
26762 disp.param_text[3] = b"old3".to_vec();
26763
26764 let new0 = 0x300000u32;
26765 write_pascal_str(&mut bus, new0, b"new0");
26766
26767 bus.write_long(TEST_SP, 0); bus.write_long(TEST_SP + 4, 0); bus.write_long(TEST_SP + 8, 0); bus.write_long(TEST_SP + 12, new0);
26771
26772 disp.dispatch_dialog(true, 0x18B, &mut cpu, &mut bus)
26773 .unwrap()
26774 .unwrap();
26775
26776 assert_eq!(disp.param_text[0], b"new0", "param0 should be replaced");
26777 assert_eq!(disp.param_text[1], b"old1", "NIL must preserve param1");
26778 assert_eq!(disp.param_text[2], b"old2", "NIL must preserve param2");
26779 assert_eq!(disp.param_text[3], b"old3", "NIL must preserve param3");
26780 }
26781
26782 #[test]
26783 fn apply_param_text_returns_borrowed_when_no_placeholders() {
26784 use std::borrow::Cow;
26788 let (mut disp, _cpu, _bus) = setup();
26789 disp.param_text[0] = b"value".to_vec();
26790
26791 let plain = disp.apply_param_text("hello world");
26792 assert!(
26793 matches!(plain, Cow::Borrowed(_)),
26794 "no-placeholder input must skip the allocation path"
26795 );
26796
26797 let substituted = disp.apply_param_text("hello ^0");
26798 assert!(
26799 matches!(substituted, Cow::Owned(_)),
26800 "with-placeholder input takes the allocation path"
26801 );
26802 assert_eq!(substituted, "hello value");
26803 }
26804
26805 #[test]
26808 fn get_ditem_clears_outputs_and_pops_18_bytes() {
26809 let (mut disp, mut cpu, mut bus) = setup();
26810
26811 let box_ptr = 0x300000u32;
26813 let item_ptr = 0x300100u32;
26814 let type_ptr = 0x300200u32;
26815
26816 bus.write_long(box_ptr, 0xDEADBEEF);
26818 bus.write_long(box_ptr + 4, 0xDEADBEEF);
26819 bus.write_long(item_ptr, 0xDEADBEEF);
26820 bus.write_word(type_ptr, 0xBEEF);
26821
26822 bus.write_long(TEST_SP, box_ptr);
26824 bus.write_long(TEST_SP + 4, item_ptr);
26825 bus.write_long(TEST_SP + 8, type_ptr);
26826 bus.write_word(TEST_SP + 12, 1); bus.write_long(TEST_SP + 14, 0x200000); let result = disp.dispatch_dialog(true, 0x18D, &mut cpu, &mut bus);
26830 assert!(result.unwrap().is_ok());
26831 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 18);
26832
26833 assert_eq!(bus.read_word(type_ptr), 0);
26835 assert_eq!(bus.read_long(item_ptr), 0);
26836 assert_eq!(bus.read_long(box_ptr), 0);
26837 assert_eq!(bus.read_long(box_ptr + 4), 0);
26838 }
26839
26840 #[test]
26841 fn get_ditem_text_handle_returns_existing_raw_text_handle() {
26842 let (mut disp, mut cpu, mut bus) = setup();
26843 let dialog_ptr = bus.alloc(170);
26844 let box_ptr = 0x300000u32;
26845 let item_ptr = 0x300100u32;
26846 let type_ptr = 0x300200u32;
26847 let text_handle = bus.alloc(4);
26848 let text_ptr = bus.alloc(5);
26849 let items_handle = bus.alloc(4);
26850 let ditl_ptr = bus.alloc(22);
26851
26852 bus.write_bytes(text_ptr, b"Hello");
26853 bus.write_long(text_handle, text_ptr);
26854 bus.write_long(items_handle, ditl_ptr);
26855 bus.write_long(dialog_ptr + 156, items_handle);
26856 bus.write_word(ditl_ptr, 0);
26857 bus.write_long(ditl_ptr + 2, text_handle);
26858 bus.write_word(ditl_ptr + 6, 10);
26859 bus.write_word(ditl_ptr + 8, 20);
26860 bus.write_word(ditl_ptr + 10, 30);
26861 bus.write_word(ditl_ptr + 12, 40);
26862 bus.write_byte(ditl_ptr + 14, 8);
26863 bus.write_byte(ditl_ptr + 15, 5);
26864 bus.write_bytes(ditl_ptr + 16, b"Hello");
26865
26866 disp.dialog_items.insert(
26867 dialog_ptr,
26868 vec![DialogItem {
26869 item_type: 8,
26870 rect: (10, 20, 30, 40),
26871 text: "Hello".to_string(),
26872 resource_id: 0,
26873 proc_ptr: 0,
26874 sel_start: 0,
26875 sel_end: 0,
26876 }],
26877 );
26878
26879 bus.write_long(TEST_SP, box_ptr);
26880 bus.write_long(TEST_SP + 4, item_ptr);
26881 bus.write_long(TEST_SP + 8, type_ptr);
26882 bus.write_word(TEST_SP + 12, 1);
26883 bus.write_long(TEST_SP + 14, dialog_ptr);
26884
26885 let result = disp.dispatch_dialog(true, 0x18D, &mut cpu, &mut bus);
26886 assert!(result.unwrap().is_ok());
26887
26888 assert_eq!(bus.read_long(item_ptr), text_handle);
26889 assert_eq!(bus.read_long(ditl_ptr + 2), text_handle);
26890 assert_eq!(bus.get_alloc_size(text_ptr), Some(5));
26891 assert_eq!(bus.read_bytes(text_ptr, 5), b"Hello".to_vec());
26892 }
26893
26894 #[test]
26895 fn get_ditem_records_pending_popup_candidate_without_associating() {
26896 let (mut disp, mut cpu, mut bus) = setup();
26897 let dialog_ptr = bus.alloc(170);
26898 let box_ptr = bus.alloc(8);
26899 let item_ptr = bus.alloc(4);
26900 let type_ptr = bus.alloc(2);
26901
26902 disp.dialog_items.insert(
26903 dialog_ptr,
26904 vec![DialogItem {
26905 item_type: 0,
26906 rect: (10, 20, 30, 40),
26907 text: String::new(),
26908 resource_id: 0,
26909 proc_ptr: 0,
26910 sel_start: 0,
26911 sel_end: 0,
26912 }],
26913 );
26914 disp.last_inserted_menu_id = Some(1234);
26915
26916 bus.write_long(TEST_SP, box_ptr);
26917 bus.write_long(TEST_SP + 4, item_ptr);
26918 bus.write_long(TEST_SP + 8, type_ptr);
26919 bus.write_word(TEST_SP + 12, 1);
26920 bus.write_long(TEST_SP + 14, dialog_ptr);
26921
26922 disp.dispatch_dialog(true, 0x18D, &mut cpu, &mut bus)
26923 .unwrap()
26924 .unwrap();
26925
26926 assert!(!disp.dialog_item_popup_menus.contains_key(&(dialog_ptr, 1)));
26927 assert!(!disp
26928 .dialog_popup_original_rects
26929 .contains_key(&(dialog_ptr, 1)));
26930 let pending = disp
26931 .pending_dialog_popup_menu
26932 .expect("enabled userItem should leave a pending popup candidate");
26933 assert_eq!(pending.dialog_ptr, dialog_ptr);
26934 assert_eq!(pending.item_no, 1);
26935 assert_eq!(pending.menu_id, 1234);
26936 assert_eq!(pending.rect, (10, 20, 30, 40));
26937 assert_eq!(disp.last_inserted_menu_id, None);
26938 }
26939
26940 #[test]
26941 fn get_ditem_ignores_recent_inserted_menu_for_disabled_user_item() {
26942 let (mut disp, mut cpu, mut bus) = setup();
26943 let dialog_ptr = bus.alloc(170);
26944 let box_ptr = bus.alloc(8);
26945 let item_ptr = bus.alloc(4);
26946 let type_ptr = bus.alloc(2);
26947
26948 disp.dialog_items.insert(
26949 dialog_ptr,
26950 vec![DialogItem {
26951 item_type: 0x80,
26952 rect: (10, 20, 30, 40),
26953 text: String::new(),
26954 resource_id: 0,
26955 proc_ptr: 0,
26956 sel_start: 0,
26957 sel_end: 0,
26958 }],
26959 );
26960 disp.last_inserted_menu_id = Some(1234);
26961
26962 bus.write_long(TEST_SP, box_ptr);
26963 bus.write_long(TEST_SP + 4, item_ptr);
26964 bus.write_long(TEST_SP + 8, type_ptr);
26965 bus.write_word(TEST_SP + 12, 1);
26966 bus.write_long(TEST_SP + 14, dialog_ptr);
26967
26968 disp.dispatch_dialog(true, 0x18D, &mut cpu, &mut bus)
26969 .unwrap()
26970 .unwrap();
26971
26972 assert!(!disp.dialog_item_popup_menus.contains_key(&(dialog_ptr, 1)));
26973 assert!(!disp
26974 .dialog_popup_original_rects
26975 .contains_key(&(dialog_ptr, 1)));
26976 assert!(disp.pending_dialog_popup_menu.is_none());
26977 assert_eq!(disp.last_inserted_menu_id, None);
26978 }
26979
26980 #[test]
26981 fn set_ditem_confirms_pending_popup_user_item_when_proc_installed() {
26982 let (mut disp, mut cpu, mut bus) = setup();
26983 let dialog_ptr = bus.alloc(170);
26984 let get_box_ptr = bus.alloc(8);
26985 let get_item_ptr = bus.alloc(4);
26986 let get_type_ptr = bus.alloc(2);
26987 let set_box_ptr = bus.alloc(8);
26988 let proc_ptr = 0x00AB_CDEF;
26989
26990 disp.dialog_items.insert(
26991 dialog_ptr,
26992 vec![DialogItem {
26993 item_type: 0,
26994 rect: (10, 20, 30, 40),
26995 text: String::new(),
26996 resource_id: 0,
26997 proc_ptr: 0,
26998 sel_start: 0,
26999 sel_end: 0,
27000 }],
27001 );
27002 disp.last_inserted_menu_id = Some(1234);
27003
27004 bus.write_long(TEST_SP, get_box_ptr);
27005 bus.write_long(TEST_SP + 4, get_item_ptr);
27006 bus.write_long(TEST_SP + 8, get_type_ptr);
27007 bus.write_word(TEST_SP + 12, 1);
27008 bus.write_long(TEST_SP + 14, dialog_ptr);
27009 disp.dispatch_dialog(true, 0x18D, &mut cpu, &mut bus)
27010 .unwrap()
27011 .unwrap();
27012
27013 cpu.write_reg(Register::A7, TEST_SP);
27014 bus.write_word(set_box_ptr, 12);
27015 bus.write_word(set_box_ptr + 2, 22);
27016 bus.write_word(set_box_ptr + 4, 32);
27017 bus.write_word(set_box_ptr + 6, 42);
27018 bus.write_long(TEST_SP, set_box_ptr);
27019 bus.write_long(TEST_SP + 4, proc_ptr);
27020 bus.write_word(TEST_SP + 8, 0);
27021 bus.write_word(TEST_SP + 10, 1);
27022 bus.write_long(TEST_SP + 12, dialog_ptr);
27023 disp.dispatch_dialog(true, 0x18E, &mut cpu, &mut bus)
27024 .unwrap()
27025 .unwrap();
27026
27027 assert_eq!(
27028 disp.dialog_item_popup_menus.get(&(dialog_ptr, 1)),
27029 Some(&1234)
27030 );
27031 assert_eq!(
27032 disp.dialog_popup_original_rects.get(&(dialog_ptr, 1)),
27033 Some(&(10, 20, 30, 40))
27034 );
27035 assert!(disp.pending_dialog_popup_menu.is_none());
27036 }
27037
27038 #[test]
27039 fn set_ditem_narrowing_user_item_records_popup_candidate_without_proc() {
27040 let (mut disp, mut cpu, mut bus) = setup();
27041 let dialog_ptr = bus.alloc(170);
27042 let set_box_ptr = bus.alloc(8);
27043
27044 disp.dialog_items.insert(
27045 dialog_ptr,
27046 vec![DialogItem {
27047 item_type: 0,
27048 rect: (10, 20, 30, 150),
27049 text: String::new(),
27050 resource_id: 0,
27051 proc_ptr: 0,
27052 sel_start: 0,
27053 sel_end: 0,
27054 }],
27055 );
27056
27057 bus.write_word(set_box_ptr, 10);
27058 bus.write_word(set_box_ptr + 2, 20);
27059 bus.write_word(set_box_ptr + 4, 30);
27060 bus.write_word(set_box_ptr + 6, 35);
27061 bus.write_long(TEST_SP, set_box_ptr);
27062 bus.write_long(TEST_SP + 4, 0);
27063 bus.write_word(TEST_SP + 8, 0);
27064 bus.write_word(TEST_SP + 10, 1);
27065 bus.write_long(TEST_SP + 12, dialog_ptr);
27066 disp.dispatch_dialog(true, 0x18E, &mut cpu, &mut bus)
27067 .unwrap()
27068 .unwrap();
27069
27070 assert!(!disp.dialog_item_popup_menus.contains_key(&(dialog_ptr, 1)));
27071 assert_eq!(
27072 disp.dialog_popup_original_rects.get(&(dialog_ptr, 1)),
27073 Some(&(10, 20, 30, 150))
27074 );
27075 assert!(disp.dialog_popup_candidate_items.contains(&(dialog_ptr, 1)));
27076 assert_eq!(
27077 disp.dialog_items
27078 .get(&dialog_ptr)
27079 .and_then(|items| items.first())
27080 .map(|item| item.rect),
27081 Some((10, 20, 30, 35))
27082 );
27083 }
27084
27085 #[test]
27088 fn set_ditem_pops_16_bytes() {
27089 let (mut disp, mut cpu, mut bus) = setup();
27090
27091 let result = disp.dispatch_dialog(true, 0x18E, &mut cpu, &mut bus);
27092 assert!(result.unwrap().is_ok());
27093 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 16);
27094 }
27095
27096 #[test]
27097 fn set_ditem_updates_item_fields_visible_through_get_ditem() {
27098 let (mut disp, mut cpu, mut bus) = setup();
27102 let dialog_ptr = bus.alloc(170);
27103 let items_handle = bus.alloc(4);
27104 let ditl_ptr = bus.alloc(22);
27105 let old_handle = bus.alloc(4);
27106 let old_ptr = bus.alloc(4);
27107 let new_handle = bus.alloc(4);
27108 let new_ptr = bus.alloc(3);
27109 let set_box_ptr = bus.alloc(8);
27110 let get_box_ptr = bus.alloc(8);
27111 let get_item_ptr = bus.alloc(4);
27112 let get_type_ptr = bus.alloc(2);
27113
27114 bus.write_bytes(old_ptr, b"Old!");
27115 bus.write_bytes(new_ptr, b"New");
27116 bus.write_long(old_handle, old_ptr);
27117 bus.write_long(new_handle, new_ptr);
27118 bus.write_long(items_handle, ditl_ptr);
27119 bus.write_long(dialog_ptr + 156, items_handle);
27120 bus.write_word(ditl_ptr, 0); bus.write_long(ditl_ptr + 2, old_handle);
27122 bus.write_word(ditl_ptr + 6, 10);
27123 bus.write_word(ditl_ptr + 8, 20);
27124 bus.write_word(ditl_ptr + 10, 30);
27125 bus.write_word(ditl_ptr + 12, 40);
27126 bus.write_byte(ditl_ptr + 14, 8);
27127 bus.write_byte(ditl_ptr + 15, 4);
27128 bus.write_bytes(ditl_ptr + 16, b"Old!");
27129
27130 disp.dialog_items.insert(
27131 dialog_ptr,
27132 vec![DialogItem {
27133 item_type: 8,
27134 rect: (10, 20, 30, 40),
27135 text: "Old!".to_string(),
27136 resource_id: 0,
27137 proc_ptr: 0,
27138 sel_start: 0,
27139 sel_end: 0,
27140 }],
27141 );
27142 disp.dialog_item_handles.insert(old_handle, (dialog_ptr, 0));
27143
27144 bus.write_word(set_box_ptr, 111);
27145 bus.write_word(set_box_ptr + 2, 222);
27146 bus.write_word(set_box_ptr + 4, 333);
27147 bus.write_word(set_box_ptr + 6, 444);
27148 bus.write_long(TEST_SP, set_box_ptr);
27149 bus.write_long(TEST_SP + 4, new_handle);
27150 bus.write_word(TEST_SP + 8, 16);
27151 bus.write_word(TEST_SP + 10, 1);
27152 bus.write_long(TEST_SP + 12, dialog_ptr);
27153
27154 disp.dispatch_dialog(true, 0x18E, &mut cpu, &mut bus)
27155 .unwrap()
27156 .unwrap();
27157
27158 cpu.write_reg(Register::A7, TEST_SP);
27159 bus.write_long(TEST_SP, get_box_ptr);
27160 bus.write_long(TEST_SP + 4, get_item_ptr);
27161 bus.write_long(TEST_SP + 8, get_type_ptr);
27162 bus.write_word(TEST_SP + 12, 1);
27163 bus.write_long(TEST_SP + 14, dialog_ptr);
27164 disp.dispatch_dialog(true, 0x18D, &mut cpu, &mut bus)
27165 .unwrap()
27166 .unwrap();
27167
27168 assert_eq!(bus.read_word(get_type_ptr), 16);
27169 assert_eq!(bus.read_long(get_item_ptr), new_handle);
27170 assert_eq!(bus.read_word(get_box_ptr), 111);
27171 assert_eq!(bus.read_word(get_box_ptr + 2), 222);
27172 assert_eq!(bus.read_word(get_box_ptr + 4), 333);
27173 assert_eq!(bus.read_word(get_box_ptr + 6), 444);
27174 }
27175
27176 #[test]
27177 fn set_ditem_user_item_treats_item_parameter_as_proc_ptr() {
27178 let (mut disp, mut cpu, mut bus) = setup();
27182 let dialog_ptr = bus.alloc(170);
27183 let items_handle = bus.alloc(4);
27184 let ditl_ptr = bus.alloc(16);
27185 let proc_ptr = 0x00AB_CDEFu32;
27186 let set_box_ptr = bus.alloc(8);
27187 let get_box_ptr = bus.alloc(8);
27188 let get_item_ptr = bus.alloc(4);
27189 let get_type_ptr = bus.alloc(2);
27190
27191 bus.write_long(items_handle, ditl_ptr);
27192 bus.write_long(dialog_ptr + 156, items_handle);
27193 bus.write_word(ditl_ptr, 0); bus.write_long(ditl_ptr + 2, 0);
27195 bus.write_word(ditl_ptr + 6, 10);
27196 bus.write_word(ditl_ptr + 8, 20);
27197 bus.write_word(ditl_ptr + 10, 30);
27198 bus.write_word(ditl_ptr + 12, 40);
27199 bus.write_byte(ditl_ptr + 14, 0);
27200 bus.write_byte(ditl_ptr + 15, 0);
27201
27202 disp.dialog_items.insert(
27203 dialog_ptr,
27204 vec![DialogItem {
27205 item_type: 0,
27206 rect: (10, 20, 30, 40),
27207 text: String::new(),
27208 resource_id: 0,
27209 proc_ptr: 0,
27210 sel_start: 0,
27211 sel_end: 0,
27212 }],
27213 );
27214
27215 bus.write_word(set_box_ptr, 50);
27216 bus.write_word(set_box_ptr + 2, 60);
27217 bus.write_word(set_box_ptr + 4, 70);
27218 bus.write_word(set_box_ptr + 6, 80);
27219 bus.write_long(TEST_SP, set_box_ptr);
27220 bus.write_long(TEST_SP + 4, proc_ptr);
27221 bus.write_word(TEST_SP + 8, 0);
27222 bus.write_word(TEST_SP + 10, 1);
27223 bus.write_long(TEST_SP + 12, dialog_ptr);
27224 disp.dispatch_dialog(true, 0x18E, &mut cpu, &mut bus)
27225 .unwrap()
27226 .unwrap();
27227
27228 cpu.write_reg(Register::A7, TEST_SP);
27229 bus.write_long(TEST_SP, get_box_ptr);
27230 bus.write_long(TEST_SP + 4, get_item_ptr);
27231 bus.write_long(TEST_SP + 8, get_type_ptr);
27232 bus.write_word(TEST_SP + 12, 1);
27233 bus.write_long(TEST_SP + 14, dialog_ptr);
27234 disp.dispatch_dialog(true, 0x18D, &mut cpu, &mut bus)
27235 .unwrap()
27236 .unwrap();
27237
27238 assert_eq!(bus.read_word(get_type_ptr), 0);
27239 assert_eq!(bus.read_long(get_item_ptr), proc_ptr);
27240 assert_eq!(bus.read_word(get_box_ptr), 50);
27241 assert_eq!(bus.read_word(get_box_ptr + 2), 60);
27242 assert_eq!(bus.read_word(get_box_ptr + 4), 70);
27243 assert_eq!(bus.read_word(get_box_ptr + 6), 80);
27244 }
27245
27246 #[test]
27247 fn set_dialog_item_text_resizes_existing_text_handle() {
27248 let (mut disp, mut cpu, mut bus) = setup();
27249 let dialog_ptr = 0x200000u32;
27250 let text_handle = bus.alloc(4);
27251 let text_ptr = bus.alloc(2);
27252 let new_text_ptr = 0x300000u32;
27253
27254 bus.write_long(text_handle, text_ptr);
27255 bus.write_byte(text_ptr, 1);
27256 bus.write_byte(text_ptr + 1, b'A');
27257 bus.write_byte(new_text_ptr, 5);
27258 bus.write_bytes(new_text_ptr + 1, b"Hello");
27259
27260 disp.dialog_items.insert(
27261 dialog_ptr,
27262 vec![DialogItem {
27263 item_type: 8,
27264 rect: (0, 0, 10, 10),
27265 text: "A".to_string(),
27266 resource_id: 0,
27267 proc_ptr: 0,
27268 sel_start: 0,
27269 sel_end: 0,
27270 }],
27271 );
27272 disp.dialog_item_handles
27273 .insert(text_handle, (dialog_ptr, 0));
27274
27275 bus.write_long(TEST_SP, new_text_ptr);
27276 bus.write_long(TEST_SP + 4, text_handle);
27277
27278 let result = disp.dispatch_dialog(true, 0x18F, &mut cpu, &mut bus);
27279 assert!(result.unwrap().is_ok());
27280
27281 let resized_ptr = bus.read_long(text_handle);
27282 assert_eq!(bus.get_alloc_size(resized_ptr), Some(5));
27283 assert_eq!(bus.read_bytes(resized_ptr, 5), b"Hello".to_vec());
27284 }
27285
27286 #[test]
27287 fn set_dialog_item_text_redraws_visible_text_item() {
27288 let (mut disp, mut cpu, mut bus) = setup();
27289 let screen_base = bus.alloc((200 * 100) as u32);
27290 let row_bytes = 200u32;
27291 disp.set_screen_mode_for_test(screen_base, row_bytes, 200, 100, 8);
27292 bus.write_long(0x0824, screen_base);
27293 TrapDispatcher::fb_fill_rect(
27294 &mut bus,
27295 screen_base,
27296 row_bytes,
27297 8,
27298 200,
27299 100,
27300 0,
27301 0,
27302 100,
27303 200,
27304 false,
27305 );
27306 let white = bus.read_byte(screen_base);
27307
27308 let bounds = (10, 20, 70, 180);
27309 let dialog_ptr = bus.alloc(170);
27310 bus.write_word(dialog_ptr + 6, 0); bus.write_word(dialog_ptr + 8, (-bounds.0) as u16);
27312 bus.write_word(dialog_ptr + 10, (-bounds.1) as u16);
27313 bus.write_word(dialog_ptr + 16, 0);
27314 bus.write_word(dialog_ptr + 18, 0);
27315 bus.write_word(dialog_ptr + 20, (bounds.2 - bounds.0) as u16);
27316 bus.write_word(dialog_ptr + 22, (bounds.3 - bounds.1) as u16);
27317 bus.write_word(dialog_ptr + 108, 2);
27318 bus.write_byte(dialog_ptr + 110, 0xFF);
27319 bus.write_word(dialog_ptr + 164, 0xFFFF);
27320 bus.write_word(dialog_ptr + 168, 1);
27321
27322 let items_handle = bus.alloc(4);
27323 let ditl_ptr = bus.alloc(16);
27324 let text_handle = bus.alloc(4);
27325 let text_ptr = bus.alloc(1);
27326 bus.write_bytes(text_ptr, b"A");
27327 bus.write_long(text_handle, text_ptr);
27328 bus.write_long(items_handle, ditl_ptr);
27329 bus.write_long(dialog_ptr + 156, items_handle);
27330 bus.write_word(ditl_ptr, 0);
27331 bus.write_long(ditl_ptr + 2, text_handle);
27332 bus.write_word(ditl_ptr + 6, 10);
27333 bus.write_word(ditl_ptr + 8, 10);
27334 bus.write_word(ditl_ptr + 10, 26);
27335 bus.write_word(ditl_ptr + 12, 140);
27336 bus.write_byte(ditl_ptr + 14, 8);
27337 bus.write_byte(ditl_ptr + 15, 0);
27338
27339 let items = vec![DialogItem {
27340 item_type: 8,
27341 rect: (10, 10, 26, 140),
27342 text: "A".to_string(),
27343 resource_id: 0,
27344 proc_ptr: 0,
27345 sel_start: 0,
27346 sel_end: 0,
27347 }];
27348 disp.dialog_items.insert(dialog_ptr, items.clone());
27349 disp.dialog_item_handles
27350 .insert(text_handle, (dialog_ptr, 0));
27351 disp.draw_dialog(&mut bus, bounds, 2, "", &items, 1, "", 0, false, dialog_ptr);
27352
27353 let count_nonwhite = |bus: &MacMemoryBus| -> usize {
27354 let mut count = 0;
27355 for y in 20..36u32 {
27356 for x in 30..160u32 {
27357 if bus.read_byte(screen_base + y * row_bytes + x) != white {
27358 count += 1;
27359 }
27360 }
27361 }
27362 count
27363 };
27364 let before = count_nonwhite(&bus);
27365
27366 let pstr = bus.alloc(9);
27367 bus.write_byte(pstr, 8);
27368 bus.write_bytes(pstr + 1, b"WWWWWWWW");
27369 bus.write_long(TEST_SP, pstr);
27370 bus.write_long(TEST_SP + 4, text_handle);
27371
27372 let result = disp.dispatch_dialog(true, 0x18F, &mut cpu, &mut bus);
27373 assert!(result.unwrap().is_ok());
27374
27375 let after = count_nonwhite(&bus);
27376 assert!(
27377 after > before + 20,
27378 "SetDialogItemText must draw the updated text item; before={} after={}",
27379 before,
27380 after
27381 );
27382 }
27383
27384 #[test]
27385 fn set_dialog_item_text_redraw_clips_repaint_to_dialog_bounds() {
27386 let (mut disp, mut cpu, mut bus) = setup();
27387 let screen_base = bus.alloc((200 * 100) as u32);
27388 let row_bytes = 200u32;
27389 disp.set_screen_mode_for_test(screen_base, row_bytes, 200, 100, 8);
27390 bus.write_long(0x0824, screen_base);
27391 TrapDispatcher::fb_fill_rect(
27392 &mut bus,
27393 screen_base,
27394 row_bytes,
27395 8,
27396 200,
27397 100,
27398 0,
27399 0,
27400 100,
27401 200,
27402 true,
27403 );
27404
27405 let bounds = (10, 20, 70, 120);
27406 let dialog_ptr = bus.alloc(170);
27407 bus.write_word(dialog_ptr + 6, 0); bus.write_word(dialog_ptr + 8, (-bounds.0) as u16);
27409 bus.write_word(dialog_ptr + 10, (-bounds.1) as u16);
27410 bus.write_word(dialog_ptr + 16, 0);
27411 bus.write_word(dialog_ptr + 18, 0);
27412 bus.write_word(dialog_ptr + 20, (bounds.2 - bounds.0) as u16);
27413 bus.write_word(dialog_ptr + 22, (bounds.3 - bounds.1) as u16);
27414 bus.write_word(dialog_ptr + 108, 2);
27415 bus.write_byte(dialog_ptr + 110, 0xFF);
27416 bus.write_word(dialog_ptr + 164, 1); bus.write_word(dialog_ptr + 168, 1);
27418
27419 let items_handle = bus.alloc(4);
27420 let ditl_ptr = bus.alloc(30);
27421 let edit_handle = bus.alloc(4);
27422 let edit_ptr = bus.alloc(3);
27423 bus.write_bytes(edit_ptr, b"Old");
27424 bus.write_long(edit_handle, edit_ptr);
27425 bus.write_long(items_handle, ditl_ptr);
27426 bus.write_long(dialog_ptr + 156, items_handle);
27427 bus.write_word(ditl_ptr, 1); bus.write_long(ditl_ptr + 2, 0);
27429 bus.write_word(ditl_ptr + 6, 5);
27430 bus.write_word(ditl_ptr + 8, 20);
27431 bus.write_word(ditl_ptr + 10, 20);
27432 bus.write_word(ditl_ptr + 12, 160); bus.write_byte(ditl_ptr + 14, 8);
27434 bus.write_byte(ditl_ptr + 15, 0);
27435 bus.write_long(ditl_ptr + 16, edit_handle);
27436 bus.write_word(ditl_ptr + 20, 25);
27437 bus.write_word(ditl_ptr + 22, 20);
27438 bus.write_word(ditl_ptr + 24, 41);
27439 bus.write_word(ditl_ptr + 26, 80);
27440 bus.write_byte(ditl_ptr + 28, 16);
27441 bus.write_byte(ditl_ptr + 29, 0);
27442
27443 let items = vec![
27444 DialogItem {
27445 item_type: 8,
27446 rect: (5, 20, 20, 160),
27447 text: String::new(),
27448 resource_id: 0,
27449 proc_ptr: 0,
27450 sel_start: 0,
27451 sel_end: 0,
27452 },
27453 DialogItem {
27454 item_type: 16,
27455 rect: (25, 20, 41, 80),
27456 text: "Old".to_string(),
27457 resource_id: 0,
27458 proc_ptr: 0,
27459 sel_start: 0,
27460 sel_end: 0,
27461 },
27462 ];
27463 disp.dialog_items.insert(dialog_ptr, items.clone());
27464 disp.dialog_item_handles
27465 .insert(edit_handle, (dialog_ptr, 1));
27466 disp.draw_dialog(
27467 &mut bus, bounds, 2, "", &items, 1, "Old", 2, false, dialog_ptr,
27468 );
27469
27470 let sample = screen_base + 17 * row_bytes + 150;
27471 let outside_before = bus.read_byte(sample);
27472
27473 let pstr = bus.alloc(4);
27474 bus.write_byte(pstr, 3);
27475 bus.write_bytes(pstr + 1, b"New");
27476 bus.write_long(TEST_SP, pstr);
27477 bus.write_long(TEST_SP + 4, edit_handle);
27478
27479 let result = disp.dispatch_dialog(true, 0x18F, &mut cpu, &mut bus);
27480 assert!(result.unwrap().is_ok());
27481
27482 assert_eq!(
27483 bus.read_byte(sample),
27484 outside_before,
27485 "SetDialogItemText repaint must not clear outside the dialog port"
27486 );
27487 }
27488
27489 #[test]
27490 fn get_dialog_item_text_returns_pascal_string_from_raw_handle() {
27491 let (mut disp, mut cpu, mut bus) = setup();
27492 let text_handle = bus.alloc(4);
27493 let text_ptr = bus.alloc(5);
27494 let out_ptr = 0x300000u32;
27495
27496 bus.write_long(text_handle, text_ptr);
27497 bus.write_bytes(text_ptr, b"Hello");
27498 bus.write_long(TEST_SP, out_ptr);
27499 bus.write_long(TEST_SP + 4, text_handle);
27500
27501 let result = disp.dispatch_dialog(true, 0x190, &mut cpu, &mut bus);
27502 assert!(result.unwrap().is_ok());
27503 assert_eq!(bus.read_byte(out_ptr), 5);
27504 assert_eq!(bus.read_bytes(out_ptr + 1, 5), b"Hello".to_vec());
27505 }
27506
27507 #[test]
27508 fn initialize_dialog_item_handles_rewrites_ditl_text_item_storage() {
27509 let (mut disp, _cpu, mut bus) = setup();
27510 let dialog_ptr = bus.alloc(170);
27511 let items_handle = bus.alloc(4);
27512 let ditl_ptr = bus.alloc(22);
27513
27514 bus.write_long(items_handle, ditl_ptr);
27515 bus.write_long(dialog_ptr + 156, items_handle);
27516 bus.write_word(ditl_ptr, 0);
27517 bus.write_long(ditl_ptr + 2, 0);
27518 bus.write_word(ditl_ptr + 6, 10);
27519 bus.write_word(ditl_ptr + 8, 20);
27520 bus.write_word(ditl_ptr + 10, 30);
27521 bus.write_word(ditl_ptr + 12, 40);
27522 bus.write_byte(ditl_ptr + 14, 8);
27523 bus.write_byte(ditl_ptr + 15, 5);
27524 bus.write_bytes(ditl_ptr + 16, b"Hello");
27525
27526 let items = vec![DialogItem {
27527 item_type: 8,
27528 rect: (10, 20, 30, 40),
27529 text: "Hello".to_string(),
27530 resource_id: 0,
27531 proc_ptr: 0,
27532 sel_start: 0,
27533 sel_end: 0,
27534 }];
27535
27536 disp.initialize_dialog_item_handles(&mut bus, dialog_ptr, &items);
27537
27538 let created_handle = bus.read_long(ditl_ptr + 2);
27539 let created_ptr = bus.read_long(created_handle);
27540 assert_ne!(created_handle, 0);
27541 assert_eq!(bus.get_alloc_size(created_ptr), Some(5));
27542 assert_eq!(bus.read_bytes(created_ptr, 5), b"Hello".to_vec());
27543 assert_eq!(
27544 disp.dialog_item_handles.get(&created_handle),
27545 Some(&(dialog_ptr, 0))
27546 );
27547 }
27548
27549 #[test]
27550 fn initialize_dialog_item_handles_materializes_icon_and_picture_handles() {
27551 let (mut disp, _cpu, mut bus) = setup();
27552 let dialog_ptr = bus.alloc(170);
27553 let items_handle = bus.alloc(4);
27554 let ditl_ptr = bus.alloc(34);
27555 let icon_ptr = disp.install_test_resource(&mut bus, *b"ICON", 421, &[0xFF; 128]);
27556 let pict_ptr = disp.install_test_resource(&mut bus, *b"PICT", 422, &[0x11; 32]);
27557
27558 bus.write_long(items_handle, ditl_ptr);
27559 bus.write_long(dialog_ptr + 156, items_handle);
27560 bus.write_word(ditl_ptr, 1);
27561 bus.write_long(ditl_ptr + 2, 0);
27562 bus.write_word(ditl_ptr + 6, 8);
27563 bus.write_word(ditl_ptr + 8, 8);
27564 bus.write_word(ditl_ptr + 10, 40);
27565 bus.write_word(ditl_ptr + 12, 40);
27566 bus.write_byte(ditl_ptr + 14, 32);
27567 bus.write_byte(ditl_ptr + 15, 2);
27568 bus.write_word(ditl_ptr + 16, 421);
27569 bus.write_long(ditl_ptr + 18, 0);
27570 bus.write_word(ditl_ptr + 22, 8);
27571 bus.write_word(ditl_ptr + 24, 48);
27572 bus.write_word(ditl_ptr + 26, 40);
27573 bus.write_word(ditl_ptr + 28, 112);
27574 bus.write_byte(ditl_ptr + 30, 64);
27575 bus.write_byte(ditl_ptr + 31, 2);
27576 bus.write_word(ditl_ptr + 32, 422);
27577
27578 let items = vec![
27579 DialogItem {
27580 item_type: 32,
27581 rect: (8, 8, 40, 40),
27582 text: String::new(),
27583 resource_id: 421,
27584 proc_ptr: 0,
27585 sel_start: 0,
27586 sel_end: 0,
27587 },
27588 DialogItem {
27589 item_type: 64,
27590 rect: (8, 48, 40, 112),
27591 text: String::new(),
27592 resource_id: 422,
27593 proc_ptr: 0,
27594 sel_start: 0,
27595 sel_end: 0,
27596 },
27597 ];
27598
27599 disp.initialize_dialog_item_handles(&mut bus, dialog_ptr, &items);
27600
27601 let icon_handle = bus.read_long(ditl_ptr + 2);
27602 let pict_handle = bus.read_long(ditl_ptr + 18);
27603 assert_ne!(icon_handle, 0);
27604 assert_ne!(pict_handle, 0);
27605 assert_eq!(bus.read_long(icon_handle), icon_ptr);
27606 assert_eq!(bus.read_long(pict_handle), pict_ptr);
27607 }
27608
27609 #[test]
27610 fn initialize_dialog_item_handles_clears_user_item_placeholder_proc_ptr() {
27611 let (mut disp, _cpu, mut bus) = setup();
27612 let dialog_ptr = bus.alloc(170);
27613 let items_handle = bus.alloc(4);
27614 let ditl_ptr = bus.alloc(16);
27615
27616 bus.write_long(items_handle, ditl_ptr);
27617 bus.write_long(dialog_ptr + 156, items_handle);
27618 bus.write_word(ditl_ptr, 0);
27619 bus.write_long(ditl_ptr + 2, 0x12345678);
27620 bus.write_word(ditl_ptr + 6, 10);
27621 bus.write_word(ditl_ptr + 8, 20);
27622 bus.write_word(ditl_ptr + 10, 30);
27623 bus.write_word(ditl_ptr + 12, 40);
27624 bus.write_byte(ditl_ptr + 14, 0);
27625 bus.write_byte(ditl_ptr + 15, 0);
27626
27627 let items = vec![DialogItem {
27628 item_type: 0,
27629 rect: (10, 20, 30, 40),
27630 text: String::new(),
27631 resource_id: 0,
27632 proc_ptr: 0,
27633 sel_start: 0,
27634 sel_end: 0,
27635 }];
27636
27637 disp.initialize_dialog_item_handles(&mut bus, dialog_ptr, &items);
27638
27639 assert_eq!(bus.read_long(ditl_ptr + 2), 0);
27640 }
27641
27642 #[test]
27643 fn initialize_dialog_item_handles_creates_standard_control_handle() {
27644 let (mut disp, _cpu, mut bus) = setup();
27645 let dialog_ptr = bus.alloc(170);
27646 let items_handle = bus.alloc(4);
27647 let ditl_ptr = bus.alloc(22);
27648
27649 bus.write_long(items_handle, ditl_ptr);
27650 bus.write_long(dialog_ptr + 156, items_handle);
27651 bus.write_word(ditl_ptr, 0);
27652 bus.write_long(ditl_ptr + 2, 0);
27653 bus.write_word(ditl_ptr + 6, 10);
27654 bus.write_word(ditl_ptr + 8, 20);
27655 bus.write_word(ditl_ptr + 10, 30);
27656 bus.write_word(ditl_ptr + 12, 120);
27657 bus.write_byte(ditl_ptr + 14, 5);
27658 bus.write_byte(ditl_ptr + 15, 5);
27659 bus.write_bytes(ditl_ptr + 16, b"Sound");
27660
27661 let items = vec![DialogItem {
27662 item_type: 5,
27663 rect: (10, 20, 30, 120),
27664 text: "Sound".to_string(),
27665 resource_id: 0,
27666 proc_ptr: 0,
27667 sel_start: 0,
27668 sel_end: 0,
27669 }];
27670
27671 disp.initialize_dialog_item_handles(&mut bus, dialog_ptr, &items);
27672
27673 let control_handle = bus.read_long(ditl_ptr + 2);
27674 let control_ptr = bus.read_long(control_handle);
27675 assert_ne!(control_handle, 0);
27676 assert_ne!(control_ptr, 0);
27677 assert_eq!(bus.read_long(dialog_ptr + 140), control_handle);
27678 assert_eq!(bus.read_long(control_ptr + 4), dialog_ptr);
27679 assert_eq!(bus.read_word(control_ptr + 8) as i16, 10);
27680 assert_eq!(bus.read_word(control_ptr + 10) as i16, 20);
27681 assert_eq!(bus.read_byte(control_ptr + 17), 0);
27682 assert_eq!(
27683 disp.dialog_control_handles.get(&control_handle),
27684 Some(&(dialog_ptr, 1))
27685 );
27686 assert_eq!(disp.dialog_control_values.get(&(dialog_ptr, 1)), Some(&0));
27687 assert_eq!(disp.control_proc_ids.get(&control_ptr), Some(&1));
27688 }
27689
27690 #[test]
27691 fn initialize_dialog_item_handles_creates_resctrl_control_handle() {
27692 let (mut disp, _cpu, mut bus) = setup();
27693 let dialog_ptr = bus.alloc(170);
27694 let items_handle = bus.alloc(4);
27695 let ditl_ptr = bus.alloc(18);
27696
27697 bus.write_long(items_handle, ditl_ptr);
27698 bus.write_long(dialog_ptr + 156, items_handle);
27699 bus.write_word(ditl_ptr, 0);
27700 bus.write_long(ditl_ptr + 2, 0);
27701 bus.write_word(ditl_ptr + 6, 10);
27702 bus.write_word(ditl_ptr + 8, 20);
27703 bus.write_word(ditl_ptr + 10, 30);
27704 bus.write_word(ditl_ptr + 12, 140);
27705 bus.write_byte(ditl_ptr + 14, 7);
27706 bus.write_byte(ditl_ptr + 15, 2);
27707 bus.write_word(ditl_ptr + 16, 128);
27708
27709 let mut cntl = Vec::new();
27710 for word in [0i16, 0, 20, 110, 2, -1, 4, 1300, 1009] {
27711 cntl.extend_from_slice(&(word as u16).to_be_bytes());
27712 }
27713 cntl.extend_from_slice(&0x1234_5678u32.to_be_bytes());
27714 cntl.push(4);
27715 cntl.extend_from_slice(b"Team");
27716 disp.install_test_resource(&mut bus, *b"CNTL", 128, &cntl);
27717
27718 let items = vec![DialogItem {
27719 item_type: 7,
27720 rect: (10, 20, 30, 140),
27721 text: String::new(),
27722 resource_id: 128,
27723 proc_ptr: 0,
27724 sel_start: 0,
27725 sel_end: 0,
27726 }];
27727
27728 disp.initialize_dialog_item_handles(&mut bus, dialog_ptr, &items);
27729
27730 let handle = bus.read_long(ditl_ptr + 2);
27731 let ctrl_ptr = bus.read_long(handle);
27732 assert_ne!(handle, 0);
27733 assert_ne!(ctrl_ptr, 0);
27734 assert_eq!(bus.read_long(dialog_ptr + 140), handle);
27735 assert_eq!(bus.read_long(ctrl_ptr + 4), dialog_ptr);
27736 assert_eq!(bus.read_word(ctrl_ptr + 8) as i16, 10);
27737 assert_eq!(bus.read_word(ctrl_ptr + 10) as i16, 20);
27738 assert_eq!(bus.read_word(ctrl_ptr + 12) as i16, 30);
27739 assert_eq!(bus.read_word(ctrl_ptr + 14) as i16, 140);
27740 assert_eq!(bus.read_word(ctrl_ptr + 18) as i16, 2);
27741 assert_eq!(bus.read_word(ctrl_ptr + 20) as i16, 1300);
27742 assert_eq!(disp.control_proc_ids.get(&ctrl_ptr), Some(&1009));
27743 assert_eq!(
27744 disp.dialog_control_handles.get(&handle),
27745 Some(&(dialog_ptr, 1))
27746 );
27747 assert_eq!(disp.dialog_control_values.get(&(dialog_ptr, 1)), Some(&2));
27748 }
27749
27750 #[test]
27751 fn popup_resctrl_initializes_contrl_data_private_menu_record() {
27752 let (mut disp, _cpu, mut bus) = setup();
27753 let dialog_ptr = bus.alloc(170);
27754 let items_handle = bus.alloc(4);
27755 let ditl_ptr = bus.alloc(18);
27756
27757 bus.write_long(items_handle, ditl_ptr);
27758 bus.write_long(dialog_ptr + 156, items_handle);
27759 bus.write_word(ditl_ptr, 0);
27760 bus.write_long(ditl_ptr + 2, 0);
27761 bus.write_word(ditl_ptr + 6, 10);
27762 bus.write_word(ditl_ptr + 8, 20);
27763 bus.write_word(ditl_ptr + 10, 30);
27764 bus.write_word(ditl_ptr + 12, 140);
27765 bus.write_byte(ditl_ptr + 14, 7);
27766 bus.write_byte(ditl_ptr + 15, 2);
27767 bus.write_word(ditl_ptr + 16, 128);
27768
27769 let mut cntl = Vec::new();
27770 for word in [0i16, 0, 20, 110, 1, -1, 0, 4000, 1009] {
27771 cntl.extend_from_slice(&(word as u16).to_be_bytes());
27772 }
27773 cntl.extend_from_slice(&0u32.to_be_bytes());
27774 cntl.push(0);
27775 disp.install_test_resource(&mut bus, *b"CNTL", 128, &cntl);
27776
27777 let items = vec![DialogItem {
27778 item_type: 7,
27779 rect: (10, 20, 30, 140),
27780 text: String::new(),
27781 resource_id: 128,
27782 proc_ptr: 0,
27783 sel_start: 0,
27784 sel_end: 0,
27785 }];
27786
27787 disp.initialize_dialog_item_handles(&mut bus, dialog_ptr, &items);
27788
27789 let ctrl_handle = bus.read_long(ditl_ptr + 2);
27790 let ctrl_ptr = bus.read_long(ctrl_handle);
27791 let private_handle = bus.read_long(ctrl_ptr + 28);
27792 let private_ptr = bus.read_long(private_handle);
27793 let menu_handle = bus.read_long(private_ptr);
27794 let menu_ptr = bus.read_long(menu_handle);
27795
27796 assert_ne!(private_handle, 0);
27797 assert_ne!(private_ptr, 0);
27798 assert_ne!(menu_handle, 0);
27799 assert_eq!(bus.read_word(private_ptr + 4) as i16, 4000);
27800 assert_eq!(bus.read_word(menu_ptr) as i16, 4000);
27801 assert_eq!(disp.popup_control_menu_id(&bus, ctrl_ptr, -1), 4000);
27802 assert!(disp
27803 .menus
27804 .iter()
27805 .any(|menu| menu.id == 4000 && menu.handle == menu_handle));
27806 }
27807
27808 #[test]
27809 fn append_ditl_overlay_preserves_existing_handles_and_initializes_resctrl() {
27810 let (mut disp, _cpu, mut bus) = setup();
27811 let dialog_ptr = bus.alloc(170);
27812 let items_handle = bus.alloc(4);
27813 let old_ditl_ptr = bus.alloc(18);
27814 let preserved_handle = 0x00A1_B2C3;
27815
27816 bus.write_long(items_handle, old_ditl_ptr);
27817 bus.write_long(dialog_ptr + 156, items_handle);
27818 bus.write_word(old_ditl_ptr, 0);
27819 bus.write_long(old_ditl_ptr + 2, preserved_handle);
27820 bus.write_word(old_ditl_ptr + 6, 10);
27821 bus.write_word(old_ditl_ptr + 8, 20);
27822 bus.write_word(old_ditl_ptr + 10, 30);
27823 bus.write_word(old_ditl_ptr + 12, 60);
27824 bus.write_byte(old_ditl_ptr + 14, 4);
27825 bus.write_byte(old_ditl_ptr + 15, 2);
27826 bus.write_bytes(old_ditl_ptr + 16, b"OK");
27827 disp.dialog_items.insert(
27828 dialog_ptr,
27829 vec![DialogItem {
27830 item_type: 4,
27831 rect: (10, 20, 30, 60),
27832 text: "OK".to_string(),
27833 resource_id: 0,
27834 proc_ptr: 0,
27835 sel_start: 0,
27836 sel_end: 0,
27837 }],
27838 );
27839
27840 let mut cntl = Vec::new();
27841 for word in [0i16, 0, 20, 110, 3, -1, 1, 5, 1008] {
27842 cntl.extend_from_slice(&(word as u16).to_be_bytes());
27843 }
27844 cntl.extend_from_slice(&0x1234_5678u32.to_be_bytes());
27845 cntl.push(4);
27846 cntl.extend_from_slice(b"Pane");
27847 disp.install_test_resource(&mut bus, *b"CNTL", 131, &cntl);
27848
27849 let append_handle = bus.alloc(4);
27850 let append_ditl_ptr = bus.alloc(18);
27851 bus.write_long(append_handle, append_ditl_ptr);
27852 bus.write_word(append_ditl_ptr, 0);
27853 bus.write_long(append_ditl_ptr + 2, 0);
27854 bus.write_word(append_ditl_ptr + 6, 40);
27855 bus.write_word(append_ditl_ptr + 8, 50);
27856 bus.write_word(append_ditl_ptr + 10, 60);
27857 bus.write_word(append_ditl_ptr + 12, 170);
27858 bus.write_byte(append_ditl_ptr + 14, 7);
27859 bus.write_byte(append_ditl_ptr + 15, 2);
27860 bus.write_word(append_ditl_ptr + 16, 131);
27861
27862 let count = disp.append_ditl_to_dialog(&mut bus, dialog_ptr, append_handle, 0);
27863
27864 assert_eq!(count, 2);
27865 let new_ditl_ptr = bus.read_long(items_handle);
27866 assert_ne!(new_ditl_ptr, old_ditl_ptr);
27867 assert_eq!(bus.read_word(new_ditl_ptr), 1);
27868 assert_eq!(
27869 bus.read_long(new_ditl_ptr + 2),
27870 preserved_handle,
27871 "AppendDITL must not recreate existing item handles"
27872 );
27873 let ctrl_handle = bus.read_long(new_ditl_ptr + 18);
27874 let ctrl_ptr = bus.read_long(ctrl_handle);
27875 assert_ne!(ctrl_handle, 0);
27876 assert_ne!(ctrl_ptr, 0);
27877 assert_eq!(bus.read_long(dialog_ptr + 140), ctrl_handle);
27878 assert_eq!(bus.read_word(ctrl_ptr + 8) as i16, 40);
27879 assert_eq!(bus.read_word(ctrl_ptr + 10) as i16, 50);
27880 assert_eq!(bus.read_word(ctrl_ptr + 12) as i16, 60);
27881 assert_eq!(bus.read_word(ctrl_ptr + 14) as i16, 170);
27882 assert_eq!(
27883 disp.dialog_control_handles.get(&ctrl_handle),
27884 Some(&(dialog_ptr, 2))
27885 );
27886 assert_eq!(disp.dialog_control_values.get(&(dialog_ptr, 2)), Some(&3));
27887 }
27888
27889 #[test]
27890 fn shorten_ditl_erases_removed_retained_item_rects_without_wiping_header_pixels() {
27891 let (mut disp, _cpu, mut bus) = setup();
27892 let screen_base = bus.alloc((400 * 300) as u32);
27893 for i in 0..400u32 * 300 {
27894 bus.write_byte(screen_base + i, 0x00);
27895 }
27896 bus.write_long(0x0824, screen_base);
27897 disp.screen_mode = (screen_base, 400, 400, 300, 8);
27898
27899 let dialog_ptr = bus.alloc(200);
27900 bus.write_word(dialog_ptr + 6, 0);
27901 bus.write_word(dialog_ptr + 8, (-100i16) as u16);
27902 bus.write_word(dialog_ptr + 10, (-100i16) as u16);
27903 bus.write_word(dialog_ptr + 16, 0);
27904 bus.write_word(dialog_ptr + 18, 0);
27905 bus.write_word(dialog_ptr + 20, 120);
27906 bus.write_word(dialog_ptr + 22, 200);
27907 let bounds = TrapDispatcher::dialog_screen_bounds(&bus, dialog_ptr);
27908
27909 let items_handle = bus.alloc(4);
27910 let ditl_ptr = bus.alloc(38);
27911 bus.write_long(items_handle, ditl_ptr);
27912 bus.write_long(dialog_ptr + 156, items_handle);
27913 bus.write_word(ditl_ptr, 1);
27914 bus.write_long(ditl_ptr + 2, 0);
27915 bus.write_word(ditl_ptr + 6, 10);
27916 bus.write_word(ditl_ptr + 8, 10);
27917 bus.write_word(ditl_ptr + 10, 30);
27918 bus.write_word(ditl_ptr + 12, 80);
27919 bus.write_byte(ditl_ptr + 14, 8);
27920 bus.write_byte(ditl_ptr + 15, 2);
27921 bus.write_bytes(ditl_ptr + 16, b"OK");
27922 bus.write_long(ditl_ptr + 20, 0);
27923 bus.write_word(ditl_ptr + 24, 40);
27924 bus.write_word(ditl_ptr + 26, 40);
27925 bus.write_word(ditl_ptr + 28, 60);
27926 bus.write_word(ditl_ptr + 30, 160);
27927 bus.write_byte(ditl_ptr + 32, 8);
27928 bus.write_byte(ditl_ptr + 33, 3);
27929 bus.write_bytes(ditl_ptr + 34, b"Old");
27930
27931 disp.dialog_items.insert(
27932 dialog_ptr,
27933 vec![
27934 DialogItem {
27935 item_type: 8,
27936 rect: (10, 10, 30, 80),
27937 text: "OK".to_string(),
27938 resource_id: 0,
27939 proc_ptr: 0,
27940 sel_start: 0,
27941 sel_end: 0,
27942 },
27943 DialogItem {
27944 item_type: 8,
27945 rect: (40, 40, 60, 160),
27946 text: "Old".to_string(),
27947 resource_id: 0,
27948 proc_ptr: 0,
27949 sel_start: 0,
27950 sel_end: 0,
27951 },
27952 ],
27953 );
27954
27955 disp.fill_rect_clipped_to_dialog(&mut bus, bounds, (106, 106, 114, 170), true);
27956 disp.fill_rect_clipped_to_dialog(&mut bus, bounds, (140, 140, 160, 260), true);
27957 let pixels = disp.save_dialog_pixels(&bus, bounds);
27958 disp.dialog_visible_snapshots
27959 .insert(dialog_ptr, PersistentDialogSnapshot { bounds, pixels });
27960
27961 let count = disp.shorten_ditl_in_dialog(&mut bus, dialog_ptr, 1);
27962
27963 assert_eq!(count, 1);
27964 assert_eq!(bus.read_word(ditl_ptr), 0);
27965 assert_eq!(bus.read_byte(screen_base + 110 * 400 + 120), 0xFF);
27966 assert_eq!(bus.read_byte(screen_base + 150 * 400 + 150), 0x00);
27967 let retained = disp.dialog_visible_snapshots.get(&dialog_ptr).unwrap();
27968 assert!(
27969 retained.pixels == disp.save_dialog_pixels(&bus, bounds),
27970 "ShortenDITL retained snapshot must match the erased framebuffer"
27971 );
27972 }
27973
27974 #[test]
27975 fn dialog_item_handle_addr_skips_zero_len_resctrl_resource_id() {
27976 let (mut disp, _cpu, mut bus) = setup();
27977 let dialog_ptr = bus.alloc(170);
27978 let items_handle = bus.alloc(4);
27979 let ditl_ptr = bus.alloc(36);
27980
27981 bus.write_long(items_handle, ditl_ptr);
27982 bus.write_long(dialog_ptr + 156, items_handle);
27983 bus.write_word(ditl_ptr, 1);
27984
27985 bus.write_long(ditl_ptr + 2, 0);
27986 bus.write_word(ditl_ptr + 6, 10);
27987 bus.write_word(ditl_ptr + 8, 20);
27988 bus.write_word(ditl_ptr + 10, 30);
27989 bus.write_word(ditl_ptr + 12, 140);
27990 bus.write_byte(ditl_ptr + 14, 7);
27991 bus.write_byte(ditl_ptr + 15, 0);
27992 bus.write_word(ditl_ptr + 16, 128);
27993
27994 bus.write_long(ditl_ptr + 18, 0);
27995 bus.write_word(ditl_ptr + 22, 40);
27996 bus.write_word(ditl_ptr + 24, 20);
27997 bus.write_word(ditl_ptr + 26, 52);
27998 bus.write_word(ditl_ptr + 28, 140);
27999 bus.write_byte(ditl_ptr + 30, 8);
28000 bus.write_byte(ditl_ptr + 31, 3);
28001 bus.write_bytes(ditl_ptr + 32, b"abc");
28002 bus.write_byte(ditl_ptr + 35, 0);
28003
28004 let mut cntl = Vec::new();
28005 for word in [0i16, 0, 20, 110, 2, -1, 4, 1300, 1009] {
28006 cntl.extend_from_slice(&(word as u16).to_be_bytes());
28007 }
28008 cntl.extend_from_slice(&0x1234_5678u32.to_be_bytes());
28009 cntl.push(4);
28010 cntl.extend_from_slice(b"Team");
28011 disp.install_test_resource(&mut bus, *b"CNTL", 128, &cntl);
28012
28013 let items = vec![
28014 DialogItem {
28015 item_type: 7,
28016 rect: (10, 20, 30, 140),
28017 text: String::new(),
28018 resource_id: 128,
28019 proc_ptr: 0,
28020 sel_start: 0,
28021 sel_end: 0,
28022 },
28023 DialogItem {
28024 item_type: 8,
28025 rect: (40, 20, 52, 140),
28026 text: "abc".to_string(),
28027 resource_id: 0,
28028 proc_ptr: 0,
28029 sel_start: 0,
28030 sel_end: 0,
28031 },
28032 ];
28033
28034 disp.initialize_dialog_item_handles(&mut bus, dialog_ptr, &items);
28035
28036 assert_eq!(
28037 bus.read_word(ditl_ptr + 16),
28038 128,
28039 "resCtrl resource ID must not be overwritten by the next item handle"
28040 );
28041 assert_ne!(bus.read_long(ditl_ptr + 2), 0);
28042 assert_ne!(bus.read_long(ditl_ptr + 18), 0);
28043 assert_eq!(
28044 TrapDispatcher::dialog_item_handle(&bus, dialog_ptr, 2),
28045 bus.read_long(ditl_ptr + 18)
28046 );
28047 }
28048
28049 #[test]
28050 fn draw_dialog_resctrl_uses_cntl_proc_id_not_always_popup() {
28051 let (mut disp, _cpu, mut bus) = setup();
28052 let screen_base = 0x300000u32;
28053 let row_bytes: u32 = 640;
28054 disp.set_screen_mode_for_test(screen_base, row_bytes, 640, 480, 8);
28055
28056 let dialog_ptr = bus.alloc(170);
28057 let items_handle = bus.alloc(4);
28058 let ditl_ptr = bus.alloc(18);
28059 bus.write_long(items_handle, ditl_ptr);
28060 bus.write_long(dialog_ptr + 156, items_handle);
28061 bus.write_word(ditl_ptr, 0);
28062 bus.write_long(ditl_ptr + 2, 0);
28063 bus.write_word(ditl_ptr + 6, 20);
28064 bus.write_word(ditl_ptr + 8, 20);
28065 bus.write_word(ditl_ptr + 10, 40);
28066 bus.write_word(ditl_ptr + 12, 120);
28067 bus.write_byte(ditl_ptr + 14, 7);
28068 bus.write_byte(ditl_ptr + 15, 2);
28069 bus.write_word(ditl_ptr + 16, 128);
28070
28071 let mut cntl = Vec::new();
28072 for word in [0i16, 0, 20, 100, 0, -1, 0, 0, 0] {
28073 cntl.extend_from_slice(&(word as u16).to_be_bytes());
28074 }
28075 cntl.extend_from_slice(&0u32.to_be_bytes());
28076 cntl.push(2);
28077 cntl.extend_from_slice(b"OK");
28078 disp.install_test_resource(&mut bus, *b"CNTL", 128, &cntl);
28079
28080 let items = vec![DialogItem {
28081 item_type: 7,
28082 rect: (20, 20, 40, 120),
28083 text: String::new(),
28084 resource_id: 128,
28085 proc_ptr: 0,
28086 sel_start: 0,
28087 sel_end: 0,
28088 }];
28089 disp.initialize_dialog_item_handles(&mut bus, dialog_ptr, &items);
28090
28091 disp.draw_dialog(
28092 &mut bus,
28093 (100, 100, 180, 260),
28094 3,
28095 "",
28096 &items,
28097 0,
28098 "",
28099 0,
28100 false,
28101 dialog_ptr,
28102 );
28103
28104 let old_popup_arrow_pixel = screen_base + 130 * row_bytes + 210;
28105 assert_ne!(
28106 bus.read_byte(old_popup_arrow_pixel),
28107 0xFF,
28108 "push-button resCtrl must not draw popup-menu arrow pixels"
28109 );
28110 }
28111
28112 #[test]
28113 fn show_dialog_item_updates_live_resctrl_control_rect() {
28114 let (mut disp, mut cpu, mut bus) = setup();
28115 let dialog_ptr = bus.alloc(170);
28116 let items_handle = bus.alloc(4);
28117 let ditl_ptr = bus.alloc(18);
28118 let hidden_rect = (20, 20 + 16384, 40, 120 + 16384);
28119 let visible_rect = (20, 20, 40, 120);
28120
28121 bus.write_long(items_handle, ditl_ptr);
28122 bus.write_long(dialog_ptr + 156, items_handle);
28123 bus.write_word(ditl_ptr, 0);
28124 bus.write_long(ditl_ptr + 2, 0);
28125 bus.write_word(ditl_ptr + 6, hidden_rect.0 as u16);
28126 bus.write_word(ditl_ptr + 8, hidden_rect.1 as u16);
28127 bus.write_word(ditl_ptr + 10, hidden_rect.2 as u16);
28128 bus.write_word(ditl_ptr + 12, hidden_rect.3 as u16);
28129 bus.write_byte(ditl_ptr + 14, 7);
28130 bus.write_byte(ditl_ptr + 15, 2);
28131 bus.write_word(ditl_ptr + 16, 128);
28132
28133 let mut cntl = Vec::new();
28134 for word in [0i16, 0, 20, 100, 0, -1, 0, 0, 0] {
28135 cntl.extend_from_slice(&(word as u16).to_be_bytes());
28136 }
28137 cntl.extend_from_slice(&0u32.to_be_bytes());
28138 cntl.push(2);
28139 cntl.extend_from_slice(b"OK");
28140 disp.install_test_resource(&mut bus, *b"CNTL", 128, &cntl);
28141
28142 let items = vec![DialogItem {
28143 item_type: 7,
28144 rect: hidden_rect,
28145 text: String::new(),
28146 resource_id: 128,
28147 proc_ptr: 0,
28148 sel_start: 0,
28149 sel_end: 0,
28150 }];
28151 disp.initialize_dialog_item_handles(&mut bus, dialog_ptr, &items);
28152 disp.dialog_items.insert(dialog_ptr, items);
28153 disp.hidden_dialog_item_rects
28154 .insert((dialog_ptr, 1), visible_rect);
28155
28156 bus.write_word(TEST_SP, 1);
28157 bus.write_long(TEST_SP + 2, dialog_ptr);
28158 disp.dispatch_dialog(true, 0x028, &mut cpu, &mut bus)
28159 .unwrap()
28160 .unwrap();
28161
28162 let ctrl_handle = disp.dialog_control_handle_for_item(dialog_ptr, 1).unwrap();
28163 let ctrl_ptr = bus.read_long(ctrl_handle);
28164 assert_eq!(bus.read_word(ctrl_ptr + 8) as i16, visible_rect.0);
28165 assert_eq!(bus.read_word(ctrl_ptr + 10) as i16, visible_rect.1);
28166 assert_eq!(bus.read_word(ctrl_ptr + 12) as i16, visible_rect.2);
28167 assert_eq!(bus.read_word(ctrl_ptr + 14) as i16, visible_rect.3);
28168 }
28169
28170 #[test]
28171 fn parse_ditl_preserves_user_item_proc_ptr() {
28172 let (_disp, _cpu, mut bus) = setup();
28173 let ditl_ptr = bus.alloc(16);
28174
28175 bus.write_word(ditl_ptr, 0);
28176 bus.write_long(ditl_ptr + 2, 0x12345678);
28177 bus.write_word(ditl_ptr + 6, 10);
28178 bus.write_word(ditl_ptr + 8, 20);
28179 bus.write_word(ditl_ptr + 10, 30);
28180 bus.write_word(ditl_ptr + 12, 40);
28181 bus.write_byte(ditl_ptr + 14, 0);
28182 bus.write_byte(ditl_ptr + 15, 0);
28183
28184 let items = TrapDispatcher::parse_ditl(&bus, ditl_ptr, 16);
28185 assert_eq!(items.len(), 1);
28186 assert_eq!(items[0].proc_ptr, 0x12345678);
28187 }
28188
28189 #[test]
28190 fn initialize_dialog_item_handles_preserves_user_item_proc_ptr() {
28191 let (mut disp, _cpu, mut bus) = setup();
28192 let dialog_ptr = bus.alloc(170);
28193 let items_handle = bus.alloc(4);
28194 let ditl_ptr = bus.alloc(16);
28195
28196 bus.write_long(items_handle, ditl_ptr);
28197 bus.write_long(dialog_ptr + 156, items_handle);
28198 bus.write_word(ditl_ptr, 0);
28199 bus.write_long(ditl_ptr + 2, 0);
28200 bus.write_word(ditl_ptr + 6, 10);
28201 bus.write_word(ditl_ptr + 8, 20);
28202 bus.write_word(ditl_ptr + 10, 30);
28203 bus.write_word(ditl_ptr + 12, 40);
28204 bus.write_byte(ditl_ptr + 14, 0);
28205 bus.write_byte(ditl_ptr + 15, 0);
28206
28207 let items = vec![DialogItem {
28208 item_type: 0,
28209 rect: (10, 20, 30, 40),
28210 text: String::new(),
28211 resource_id: 0,
28212 proc_ptr: 0x12345678,
28213 sel_start: 0,
28214 sel_end: 0,
28215 }];
28216
28217 disp.initialize_dialog_item_handles(&mut bus, dialog_ptr, &items);
28218
28219 assert_eq!(bus.read_long(ditl_ptr + 2), 0x12345678);
28220 }
28221
28222 #[test]
28223 fn refresh_ditl_proc_ptrs_clears_stale_user_item_proc_ptr() {
28224 let (_disp, _cpu, mut bus) = setup();
28225 let dialog_ptr = bus.alloc(170);
28226 let items_handle = bus.alloc(4);
28227 let ditl_ptr = bus.alloc(16);
28228
28229 bus.write_long(items_handle, ditl_ptr);
28230 bus.write_long(dialog_ptr + 156, items_handle);
28231 bus.write_word(ditl_ptr, 0);
28232 bus.write_long(ditl_ptr + 2, 0);
28233 bus.write_word(ditl_ptr + 6, 10);
28234 bus.write_word(ditl_ptr + 8, 20);
28235 bus.write_word(ditl_ptr + 10, 30);
28236 bus.write_word(ditl_ptr + 12, 40);
28237 bus.write_byte(ditl_ptr + 14, 0);
28238 bus.write_byte(ditl_ptr + 15, 0);
28239
28240 let mut items = vec![DialogItem {
28241 item_type: 0,
28242 rect: (10, 20, 30, 40),
28243 text: String::new(),
28244 resource_id: 0,
28245 proc_ptr: 0x12345678,
28246 sel_start: 0,
28247 sel_end: 0,
28248 }];
28249
28250 TrapDispatcher::refresh_ditl_proc_ptrs(&bus, dialog_ptr, &mut items);
28251
28252 assert_eq!(items[0].proc_ptr, 0);
28253 }
28254
28255 #[test]
28258 fn modal_dialog_writes_item_hit_and_pops_8_bytes() {
28259 let (mut disp, mut cpu, mut bus) = setup();
28260
28261 let item_hit_addr = 0x300000u32;
28262 bus.write_word(item_hit_addr, 0); bus.write_long(TEST_SP, item_hit_addr);
28266 bus.write_long(TEST_SP + 4, 0); let result = disp.dispatch_dialog(true, 0x191, &mut cpu, &mut bus);
28269 assert!(result.unwrap().is_ok());
28270 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 8);
28271 assert_eq!(bus.read_word(item_hit_addr), 1);
28272 }
28273
28274 #[test]
28275 fn modal_dialog_retained_edit_text_appends_across_reentry_and_command_a_selects() {
28276 let (mut disp, mut cpu, mut bus) = setup_with_port();
28277 let screen_base = bus.alloc(320 * 240);
28278 for offset in 0..320u32 * 240 {
28279 bus.write_byte(screen_base + offset, 0xFF);
28280 }
28281 bus.write_long(0x0824, screen_base);
28282 disp.set_screen_mode_for_test(screen_base, 320, 320, 240, 8);
28283
28284 let dialog_ptr = bus.alloc(256);
28285 let item_hit_addr = 0x300000u32;
28286 let bounds = (40, 40, 130, 280);
28287 disp.front_window = dialog_ptr;
28288 disp.current_port = dialog_ptr;
28289 disp.window_bounds = bounds;
28290 disp.window_proc_id = 2;
28291 disp.window_title.clear();
28292 disp.window_list = vec![dialog_ptr];
28293 bus.write_word(dialog_ptr + 108, 2);
28294 bus.write_word(dialog_ptr + 164, 0);
28295 bus.write_word(dialog_ptr + 168, 0);
28296 disp.dialog_items.insert(
28297 dialog_ptr,
28298 vec![DialogItem {
28299 item_type: 16,
28300 rect: (24, 24, 42, 190),
28301 text: String::new(),
28302 resource_id: 0,
28303 proc_ptr: 0,
28304 sel_start: 0,
28305 sel_end: 0,
28306 }],
28307 );
28308
28309 fn enter_modal(
28310 disp: &mut TrapDispatcher,
28311 cpu: &mut MockCpu,
28312 bus: &mut MacMemoryBus,
28313 item_hit_addr: u32,
28314 ) {
28315 bus.write_word(item_hit_addr, 0xCAFE);
28316 bus.write_long(TEST_SP, item_hit_addr);
28317 bus.write_long(TEST_SP + 4, 0);
28318 cpu.write_reg(Register::A7, TEST_SP);
28319 disp.dispatch_dialog(true, 0x191, cpu, bus)
28320 .unwrap()
28321 .unwrap();
28322 assert!(disp.dialog_tracking.is_some());
28323 assert_eq!(cpu.read_reg(Register::A7), TEST_SP);
28324 }
28325
28326 fn key_down(
28327 disp: &mut TrapDispatcher,
28328 cpu: &mut MockCpu,
28329 bus: &mut MacMemoryBus,
28330 item_hit_addr: u32,
28331 key_code: u8,
28332 char_code: u8,
28333 modifiers: u16,
28334 ) {
28335 disp.event_queue
28336 .push_back(crate::trap::dispatch::QueuedEvent {
28337 what: 3,
28338 message: (u32::from(key_code) << 8) | u32::from(char_code),
28339 where_v: 0,
28340 where_h: 0,
28341 modifiers,
28342 });
28343 disp.dispatch_dialog(true, 0x191, cpu, bus)
28344 .unwrap()
28345 .unwrap();
28346 assert!(disp.dialog_tracking.is_none());
28347 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 8);
28348 assert_eq!(bus.read_word(item_hit_addr), 1);
28349 }
28350
28351 for (key_code, char_code) in [(0x02, b'd'), (0x20, b'u'), (0x02, b'd')] {
28352 enter_modal(&mut disp, &mut cpu, &mut bus, item_hit_addr);
28353 key_down(
28354 &mut disp,
28355 &mut cpu,
28356 &mut bus,
28357 item_hit_addr,
28358 key_code,
28359 char_code,
28360 0,
28361 );
28362 }
28363
28364 let item = &disp.dialog_items[&dialog_ptr][0];
28365 assert_eq!(item.text, "dud");
28366 assert_eq!((item.sel_start, item.sel_end), (3, 3));
28367 assert!(disp
28368 .dialog_edit_text_modified_items
28369 .contains(&(dialog_ptr, 1)));
28370
28371 enter_modal(&mut disp, &mut cpu, &mut bus, item_hit_addr);
28372 key_down(
28373 &mut disp,
28374 &mut cpu,
28375 &mut bus,
28376 item_hit_addr,
28377 0x00,
28378 b'a',
28379 0x0100,
28380 );
28381 let item = &disp.dialog_items[&dialog_ptr][0];
28382 assert_eq!(item.text, "dud");
28383 assert_eq!((item.sel_start, item.sel_end), (0, 3));
28384 assert!(!disp
28385 .dialog_edit_text_modified_items
28386 .contains(&(dialog_ptr, 1)));
28387
28388 enter_modal(&mut disp, &mut cpu, &mut bus, item_hit_addr);
28389 key_down(&mut disp, &mut cpu, &mut bus, item_hit_addr, 0x33, 0x08, 0);
28390 let item = &disp.dialog_items[&dialog_ptr][0];
28391 assert_eq!(item.text, "");
28392 assert_eq!((item.sel_start, item.sel_end), (0, 0));
28393 }
28394
28395 #[test]
28396 fn modal_dialog_popup_resctrl_tracks_selection_and_returns_item() {
28397 let (mut disp, mut cpu, mut bus) = setup();
28398 let screen_base = bus.alloc((800 * 600) as u32);
28399 for i in 0..800u32 * 600 {
28400 bus.write_byte(screen_base + i, 0xFF);
28401 }
28402 bus.write_long(0x0824, screen_base);
28403 disp.screen_mode = (screen_base, 800, 800, 600, 8);
28404
28405 let dialog_ptr = bus.alloc(256);
28406 bus.write_word(dialog_ptr + 6, 0);
28407 bus.write_word(dialog_ptr + 8, (-100i16) as u16);
28408 bus.write_word(dialog_ptr + 10, (-100i16) as u16);
28409 bus.write_word(dialog_ptr + 16, 0);
28410 bus.write_word(dialog_ptr + 18, 0);
28411 bus.write_word(dialog_ptr + 20, 180);
28412 bus.write_word(dialog_ptr + 22, 220);
28413 let dialog_bounds = (100, 100, 280, 320);
28414
28415 let ctrl_ptr = bus.alloc(296);
28416 let ctrl_handle = bus.alloc(4);
28417 bus.write_long(ctrl_handle, ctrl_ptr);
28418 disp.initialize_control_record(
28419 &mut bus,
28420 ctrl_ptr,
28421 dialog_ptr,
28422 (10, 20, 30, 130),
28423 b"",
28424 true,
28425 1,
28426 900,
28427 0,
28428 1009,
28429 0,
28430 );
28431 disp.dialog_control_handles
28432 .insert(ctrl_handle, (dialog_ptr, 1));
28433 disp.dialog_control_values.insert((dialog_ptr, 1), 1);
28434 disp.menus.push(Menu {
28435 id: 900,
28436 title: "Squadies".to_string(),
28437 items: vec![
28438 MenuItem {
28439 text: "Duke".to_string(),
28440 icon: 0,
28441 key_equiv: 0,
28442 mark: 0,
28443 style: 0,
28444 enabled: true,
28445 },
28446 MenuItem {
28447 text: "Carnage".to_string(),
28448 icon: 0,
28449 key_equiv: 0,
28450 mark: 0,
28451 style: 0,
28452 enabled: true,
28453 },
28454 ],
28455 enabled: true,
28456 handle: 0,
28457 in_menu_bar: false,
28458 hierarchical: false,
28459 visible_in_menu_bar: false,
28460 });
28461
28462 let item_hit_addr = 0x300000u32;
28463 bus.write_word(item_hit_addr, 0);
28464 cpu.write_reg(Register::A7, TEST_SP);
28465 disp.dialog_tracking = Some(crate::trap::dispatch::DialogTrackingState {
28466 dialog_ptr,
28467 bounds: dialog_bounds,
28468 title: String::new(),
28469 proc_id: 2,
28470 items: vec![DialogItem {
28471 item_type: 7,
28472 rect: (10, 20, 30, 130),
28473 text: String::new(),
28474 resource_id: 0,
28475 proc_ptr: 0,
28476 sel_start: 0,
28477 sel_end: 0,
28478 }],
28479 default_item: 0,
28480 cancel_item: 0,
28481 edit_text: String::new(),
28482 edit_item: 0,
28483 saved_pixels: Vec::new(),
28484 stack_ptr: TEST_SP,
28485 item_hit_ptr: item_hit_addr,
28486 rendered_pixels: Vec::new(),
28487 flash_remaining: 0,
28488 flash_delay: 0,
28489 flash_item: 0,
28490 edit_text_modified: false,
28491 draw_proc_queue: VecDeque::new(),
28492 draw_procs_done: true,
28493 rendered_pixels_final: true,
28494 filter_proc: 0,
28495 game_managed: false,
28496 last_filter_event: None,
28497 popup_draws: Vec::new(),
28498 active_popup: None,
28499 active_button: None,
28500 active_user_item: None,
28501 });
28502 disp.mouse_button = true;
28503 disp.mouse_pos = (115, 125);
28504 disp.event_queue
28505 .push_back(crate::trap::dispatch::QueuedEvent {
28506 what: 1,
28507 message: 0,
28508 where_v: 115,
28509 where_h: 125,
28510 modifiers: 0,
28511 });
28512
28513 disp.dispatch_dialog(true, 0x191, &mut cpu, &mut bus)
28514 .unwrap()
28515 .unwrap();
28516 assert!(disp
28517 .dialog_tracking
28518 .as_ref()
28519 .and_then(|tracking| tracking.active_popup.as_ref())
28520 .is_some());
28521 assert_eq!(cpu.read_reg(Register::A7), TEST_SP);
28522 let stale_open_popup_pixels = disp.save_dialog_pixels(&bus, dialog_bounds);
28523 let tracking = disp.dialog_tracking.as_mut().unwrap();
28524 tracking.rendered_pixels = stale_open_popup_pixels.clone();
28525 tracking.rendered_pixels_final = true;
28526
28527 let (dropdown_top, dropdown_left, _, _) = disp
28528 .dialog_tracking
28529 .as_ref()
28530 .and_then(|tracking| tracking.active_popup.as_ref())
28531 .map(|popup| popup.dropdown_rect)
28532 .expect("popup tracking should expose the live dropdown rect");
28533 disp.mouse_pos = (dropdown_top + 1 + 16 + 1, dropdown_left + 5);
28534 disp.dispatch_dialog(true, 0x191, &mut cpu, &mut bus)
28535 .unwrap()
28536 .unwrap();
28537 assert_eq!(
28538 disp.dialog_tracking
28539 .as_ref()
28540 .and_then(|tracking| tracking.active_popup.as_ref())
28541 .map(|popup| popup.highlighted_item),
28542 Some(2)
28543 );
28544
28545 disp.mouse_button = false;
28546 disp.event_queue
28547 .push_back(crate::trap::dispatch::QueuedEvent {
28548 what: 2,
28549 message: 0,
28550 where_v: 148,
28551 where_h: 125,
28552 modifiers: 0,
28553 });
28554 disp.dispatch_dialog(true, 0x191, &mut cpu, &mut bus)
28555 .unwrap()
28556 .unwrap();
28557
28558 assert!(disp.dialog_tracking.is_none());
28559 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 8);
28560 assert_eq!(bus.read_word(item_hit_addr), 1);
28561 assert_eq!(bus.read_word(ctrl_ptr + 18) as i16, 2);
28562 assert_eq!(disp.dialog_control_values.get(&(dialog_ptr, 1)), Some(&2));
28563
28564 let retained = disp.dialog_visible_snapshots.get(&dialog_ptr).unwrap();
28565 let current_pixels = disp.save_dialog_pixels(&bus, dialog_bounds);
28566 assert!(
28567 retained.pixels == current_pixels,
28568 "retained snapshot must match the closed popup framebuffer"
28569 );
28570 assert!(
28571 retained.pixels != stale_open_popup_pixels,
28572 "retained snapshot kept stale open-popup pixels"
28573 );
28574 }
28575
28576 #[test]
28577 fn modal_dialog_game_managed_dialog_still_queues_user_item_draw_procs() {
28578 let (mut disp, mut cpu, mut bus) = setup();
28579 let dialog_ptr = 0x200000u32;
28580 let item_hit_addr = 0x300000u32;
28581
28582 disp.front_window = dialog_ptr;
28583 disp.window_bounds = (92, 95, 415, 704);
28584 disp.window_proc_id = 2;
28585 disp.window_title.clear();
28586 disp.dialog_items.insert(
28587 dialog_ptr,
28588 vec![
28589 DialogItem {
28590 item_type: 0,
28591 rect: (8, 353, 148, 603),
28592 text: String::new(),
28593 resource_id: 0,
28594 proc_ptr: 0x500000,
28595 sel_start: 0,
28596 sel_end: 0,
28597 },
28598 DialogItem {
28599 item_type: 0x80,
28600 rect: (159, 381, 184, 581),
28601 text: String::new(),
28602 resource_id: 0,
28603 proc_ptr: 0x500100,
28604 sel_start: 0,
28605 sel_end: 0,
28606 },
28607 DialogItem {
28608 item_type: 0x80,
28609 rect: (324, 650, 349, 771),
28610 text: String::new(),
28611 resource_id: 0,
28612 proc_ptr: 0x500200,
28613 sel_start: 0,
28614 sel_end: 0,
28615 },
28616 ],
28617 );
28618
28619 bus.write_long(TEST_SP, item_hit_addr);
28620 bus.write_long(TEST_SP + 4, 0x149F0);
28621
28622 let result = disp.dispatch_dialog(true, 0x191, &mut cpu, &mut bus);
28623 assert!(result.unwrap().is_ok());
28624 assert_eq!(cpu.read_reg(Register::A7), TEST_SP);
28625
28626 let tracking = disp.dialog_tracking.as_ref().unwrap();
28627 assert!(tracking.game_managed);
28628 assert_eq!(tracking.filter_proc, 0x149F0);
28629 assert_eq!(tracking.draw_proc_queue.len(), 2);
28630 assert_eq!(
28631 tracking
28632 .draw_proc_queue
28633 .iter()
28634 .map(|(_, item_no)| *item_no)
28635 .collect::<Vec<_>>(),
28636 vec![1, 2]
28637 );
28638 assert!(!tracking.draw_procs_done);
28639 assert!(!tracking.rendered_pixels_final);
28640 }
28641
28642 #[test]
28643 fn modal_dialog_retained_reentry_reuses_visible_snapshot_without_user_item_redraw() {
28644 let (mut disp, mut cpu, mut bus) = setup();
28645 let dialog_ptr = 0x200000u32;
28646 let item_hit_addr = 0x300000u32;
28647 let screen_base = bus.alloc((64 * 64) as u32);
28648 let row_bytes = 64u32;
28649 for i in 0..row_bytes * 64 {
28650 bus.write_byte(screen_base + i, 0x11);
28651 }
28652 bus.write_long(0x0824, screen_base);
28653 disp.screen_mode = (screen_base, row_bytes, 64, 64, 8);
28654
28655 let bounds = (10, 12, 30, 34);
28656 let snapshot_width = (bounds.3 - bounds.1 + TrapDispatcher::DBOX_FRAME_MARGIN * 2) as usize;
28657 let snapshot_height =
28658 (bounds.2 - bounds.0 + TrapDispatcher::DBOX_FRAME_MARGIN * 2) as usize;
28659 let visible_pixels = vec![0x44; snapshot_width * snapshot_height];
28660 disp.dialog_visible_snapshots.insert(
28661 dialog_ptr,
28662 PersistentDialogSnapshot {
28663 bounds,
28664 pixels: visible_pixels,
28665 },
28666 );
28667 disp.dialog_saved_pixels
28668 .insert(dialog_ptr, vec![0x22; snapshot_width * snapshot_height]);
28669 disp.dialog_modal_entered.insert(dialog_ptr);
28670
28671 disp.front_window = dialog_ptr;
28672 disp.window_bounds = bounds;
28673 disp.window_proc_id = 1;
28674 disp.window_title.clear();
28675 disp.dialog_items.insert(
28676 dialog_ptr,
28677 vec![DialogItem {
28678 item_type: 0x80,
28679 rect: (0, 0, 20, 22),
28680 text: String::new(),
28681 resource_id: 0,
28682 proc_ptr: 0x500000,
28683 sel_start: 0,
28684 sel_end: 0,
28685 }],
28686 );
28687
28688 bus.write_long(TEST_SP, item_hit_addr);
28689 bus.write_long(TEST_SP + 4, 0);
28690
28691 let result = disp.dispatch_dialog(true, 0x191, &mut cpu, &mut bus);
28692 assert!(result.unwrap().is_ok());
28693 assert_eq!(cpu.read_reg(Register::A7), TEST_SP);
28694
28695 let tracking = disp.dialog_tracking.as_ref().unwrap();
28696 assert!(tracking.draw_proc_queue.is_empty());
28697 assert!(tracking.draw_procs_done);
28698 assert!(tracking.rendered_pixels_final);
28699 assert_eq!(
28700 bus.read_byte(screen_base + 20 * row_bytes + 20),
28701 0x44,
28702 "retained visible snapshot should be restored on re-entry"
28703 );
28704 }
28705
28706 #[test]
28707 fn modal_dialog_first_entry_with_showwindow_snapshot_still_queues_user_item_draw_proc() {
28708 let (mut disp, mut cpu, mut bus) = setup();
28709 let dialog_ptr = 0x200000u32;
28710 let item_hit_addr = 0x300000u32;
28711 let screen_base = bus.alloc((64 * 64) as u32);
28712 let row_bytes = 64u32;
28713 bus.write_long(0x0824, screen_base);
28714 disp.screen_mode = (screen_base, row_bytes, 64, 64, 8);
28715
28716 let bounds = (10, 12, 30, 34);
28717 let snapshot_width = (bounds.3 - bounds.1 + TrapDispatcher::DBOX_FRAME_MARGIN * 2) as usize;
28718 let snapshot_height =
28719 (bounds.2 - bounds.0 + TrapDispatcher::DBOX_FRAME_MARGIN * 2) as usize;
28720 disp.dialog_visible_snapshots.insert(
28721 dialog_ptr,
28722 PersistentDialogSnapshot {
28723 bounds,
28724 pixels: vec![0x44; snapshot_width * snapshot_height],
28725 },
28726 );
28727
28728 disp.front_window = dialog_ptr;
28729 disp.window_bounds = bounds;
28730 disp.window_proc_id = 1;
28731 disp.window_title.clear();
28732 disp.dialog_items.insert(
28733 dialog_ptr,
28734 vec![DialogItem {
28735 item_type: 0x80,
28736 rect: (0, 0, 20, 22),
28737 text: String::new(),
28738 resource_id: 0,
28739 proc_ptr: 0x500000,
28740 sel_start: 0,
28741 sel_end: 0,
28742 }],
28743 );
28744
28745 bus.write_long(TEST_SP, item_hit_addr);
28746 bus.write_long(TEST_SP + 4, 0);
28747
28748 let result = disp.dispatch_dialog(true, 0x191, &mut cpu, &mut bus);
28749 assert!(result.unwrap().is_ok());
28750
28751 let tracking = disp.dialog_tracking.as_ref().unwrap();
28752 assert_eq!(tracking.draw_proc_queue.len(), 1);
28753 assert_eq!(tracking.draw_proc_queue[0], (0x500000, 1));
28754 assert!(!tracking.draw_procs_done);
28755 assert!(!tracking.rendered_pixels_final);
28756 }
28757
28758 #[test]
28759 fn dialog_game_managed_ignores_offscreen_placeholder_items() {
28760 let bounds = (200, 146, 400, 510);
28761 let items = vec![
28762 DialogItem {
28763 item_type: 0,
28764 rect: (167, 279, 192, 357),
28765 text: String::new(),
28766 resource_id: 0,
28767 proc_ptr: 0,
28768 sel_start: 0,
28769 sel_end: 0,
28770 },
28771 DialogItem {
28772 item_type: 0xC0, rect: (217, 179, 247, 247),
28774 text: String::new(),
28775 resource_id: 1431,
28776 proc_ptr: 0,
28777 sel_start: 0,
28778 sel_end: 0,
28779 },
28780 DialogItem {
28781 item_type: 0x80, rect: (7, 8, 157, 358),
28783 text: String::new(),
28784 resource_id: 0,
28785 proc_ptr: 0,
28786 sel_start: 0,
28787 sel_end: 0,
28788 },
28789 ];
28790
28791 assert!(TrapDispatcher::dialog_is_game_managed(bounds, &items));
28792
28793 let mut standard_items = items;
28794 standard_items.push(DialogItem {
28795 item_type: 8,
28796 rect: (20, 20, 40, 100),
28797 text: "Standard".to_string(),
28798 resource_id: 0,
28799 proc_ptr: 0,
28800 sel_start: 0,
28801 sel_end: 0,
28802 });
28803 assert!(!TrapDispatcher::dialog_is_game_managed(
28804 bounds,
28805 &standard_items
28806 ));
28807 }
28808
28809 #[test]
28810 fn redraw_dialog_window_contents_does_not_snapshot_game_managed_shell() {
28811 let (mut disp, _cpu, mut bus) = setup();
28812 let dialog_ptr = 0x200000u32;
28813 let screen_base = bus.alloc(64 * 64);
28814 for offset in 0..64u32 * 64 {
28815 bus.write_byte(screen_base + offset, 0x11);
28816 }
28817 bus.write_long(0x0824, screen_base);
28818 disp.screen_mode = (screen_base, 64, 64, 64, 8);
28819
28820 bus.write_long(dialog_ptr + 2, screen_base);
28821 bus.write_word(dialog_ptr + 6, 64);
28822 bus.write_word(dialog_ptr + 8, 0);
28823 bus.write_word(dialog_ptr + 10, 0);
28824 bus.write_word(dialog_ptr + 12, 20);
28825 bus.write_word(dialog_ptr + 14, 20);
28826 bus.write_word(dialog_ptr + 16, 0);
28827 bus.write_word(dialog_ptr + 18, 0);
28828 bus.write_word(dialog_ptr + 20, 20);
28829 bus.write_word(dialog_ptr + 22, 20);
28830 bus.write_word(dialog_ptr + 108, 2);
28831 bus.write_byte(dialog_ptr + 110, 0xFF);
28832 disp.window_proc_ids.insert(dialog_ptr, 2);
28833 disp.dialog_items.insert(
28834 dialog_ptr,
28835 vec![DialogItem {
28836 item_type: 0x80,
28837 rect: (2, 2, 10, 10),
28838 text: String::new(),
28839 resource_id: 0,
28840 proc_ptr: 0,
28841 sel_start: 0,
28842 sel_end: 0,
28843 }],
28844 );
28845 disp.dialog_visible_snapshots.insert(
28846 dialog_ptr,
28847 PersistentDialogSnapshot {
28848 bounds: (0, 0, 20, 20),
28849 pixels: vec![0x44; 30 * 30],
28850 },
28851 );
28852
28853 disp.redraw_dialog_window_contents(&mut bus, dialog_ptr);
28854
28855 assert!(
28856 !disp.dialog_visible_snapshots.contains_key(&dialog_ptr),
28857 "ShowWindow redraw must not retain a stale shell for app-drawn all-userItem dialogs"
28858 );
28859 }
28860
28861 #[test]
28862 fn modal_dialog_resnapshot_redraws_popup_controls_after_user_item_draw_procs() {
28863 let (mut disp, mut cpu, mut bus) = setup();
28864 let screen_base = bus.alloc((800 * 600) as u32);
28865 for i in 0..800u32 * 600 {
28866 bus.write_byte(screen_base + i, 0xFF);
28867 }
28868 bus.write_long(0x0824, screen_base);
28869 disp.screen_mode = (screen_base, 800, 800, 600, 8);
28870
28871 disp.dialog_tracking = Some(crate::trap::dispatch::DialogTrackingState {
28872 dialog_ptr: 0x200000,
28873 bounds: (0, 0, 100, 220),
28874 title: String::new(),
28875 proc_id: 2,
28876 items: Vec::new(),
28877 default_item: 0,
28878 cancel_item: 0,
28879 edit_text: String::new(),
28880 edit_item: 0,
28881 saved_pixels: Vec::new(),
28882 stack_ptr: TEST_SP,
28883 item_hit_ptr: 0,
28884 rendered_pixels: Vec::new(),
28885 flash_remaining: 0,
28886 flash_delay: 0,
28887 flash_item: 0,
28888 edit_text_modified: false,
28889 draw_proc_queue: VecDeque::new(),
28890 draw_procs_done: true,
28891 rendered_pixels_final: false,
28892 filter_proc: 0,
28893 game_managed: false,
28894 last_filter_event: None,
28895 popup_draws: vec![DialogPopupDraw {
28896 rect: (20, 30, 42, 180),
28897 title: String::new(),
28898 enabled: true,
28899 pressed: false,
28900 }],
28901 active_popup: None,
28902 active_button: None,
28903 active_user_item: None,
28904 });
28905
28906 let result = disp.dispatch_dialog(true, 0x191, &mut cpu, &mut bus);
28907 assert!(result.unwrap().is_ok());
28908
28909 let interior = screen_base + 30 * 800 + 80;
28910 assert_eq!(
28911 bus.read_byte(interior),
28912 0,
28913 "popup interior must be redrawn over the guest userItem pixels"
28914 );
28915 assert!(
28916 disp.dialog_tracking
28917 .as_ref()
28918 .is_some_and(|tracking| tracking.rendered_pixels_final),
28919 "ModalDialog should re-snapshot after popup redraw"
28920 );
28921 }
28922
28923 #[test]
28924 fn modal_dialog_popup_resnapshot_preserves_theme_pressed_and_inactive_states() {
28925 let (mut disp, mut cpu, mut bus) = setup();
28926 disp.set_ui_theme_id(UiThemeId::SystemlessDefault);
28927
28928 let row_bytes = 64u32;
28929 let screen_base = bus.alloc(row_bytes * 160);
28930 for i in 0..row_bytes * 160 {
28931 bus.write_byte(screen_base + i, 0);
28932 }
28933 disp.set_screen_mode_for_test(screen_base, row_bytes, 512, 160, 1);
28934 cpu.write_reg(Register::A7, TEST_SP);
28935
28936 disp.dialog_tracking = Some(crate::trap::dispatch::DialogTrackingState {
28937 dialog_ptr: 0x200000,
28938 bounds: (0, 0, 120, 220),
28939 title: String::new(),
28940 proc_id: 2,
28941 items: Vec::new(),
28942 default_item: 0,
28943 cancel_item: 0,
28944 edit_text: String::new(),
28945 edit_item: 0,
28946 saved_pixels: Vec::new(),
28947 stack_ptr: TEST_SP,
28948 item_hit_ptr: 0,
28949 rendered_pixels: Vec::new(),
28950 flash_remaining: 0,
28951 flash_delay: 0,
28952 flash_item: 0,
28953 edit_text_modified: false,
28954 draw_proc_queue: VecDeque::new(),
28955 draw_procs_done: true,
28956 rendered_pixels_final: false,
28957 filter_proc: 0,
28958 game_managed: false,
28959 last_filter_event: None,
28960 popup_draws: vec![
28961 DialogPopupDraw {
28962 rect: (20, 30, 42, 180),
28963 title: String::new(),
28964 enabled: true,
28965 pressed: true,
28966 },
28967 DialogPopupDraw {
28968 rect: (60, 30, 82, 180),
28969 title: String::new(),
28970 enabled: false,
28971 pressed: false,
28972 },
28973 ],
28974 active_popup: None,
28975 active_button: None,
28976 active_user_item: None,
28977 });
28978
28979 let result = disp.dispatch_dialog(true, 0x191, &mut cpu, &mut bus);
28980 assert!(result.unwrap().is_ok());
28981
28982 assert!(
28983 screen_pixel_is_set(&bus, screen_base, row_bytes, 35, 25),
28984 "pressed popup redraw should preserve the provider pressed fill"
28985 );
28986 assert!(
28987 screen_pixel_is_set(&bus, screen_base, row_bytes, 32, 62),
28988 "inactive popup redraw should preserve the provider inactive frame"
28989 );
28990 assert!(
28991 disp.dialog_tracking
28992 .as_ref()
28993 .is_some_and(|tracking| tracking.rendered_pixels_final),
28994 "ModalDialog should re-snapshot after stateful popup redraw"
28995 );
28996 }
28997
28998 #[test]
28999 fn modal_dialog_resnapshot_blits_dialog_port_after_user_item_draw_procs() {
29000 let (mut disp, mut cpu, mut bus) = setup();
29001 let screen_base = bus.alloc((32 * 32) as u32);
29002 for i in 0..32u32 * 32 {
29003 bus.write_byte(screen_base + i, 0xFF);
29004 }
29005 bus.write_long(0x0824, screen_base);
29006 disp.screen_mode = (screen_base, 32, 32, 32, 8);
29007
29008 let dialog_ptr = bus.alloc(64);
29009 let offscreen_base = bus.alloc(20 * 20);
29010 for i in 0..20u32 * 20 {
29011 bus.write_byte(offscreen_base + i, 0x00);
29012 }
29013 let pixmap_handle = bus.alloc(4);
29014 let pixmap_ptr = bus.alloc(50);
29015 bus.write_long(pixmap_handle, pixmap_ptr);
29016 bus.write_long(pixmap_ptr, offscreen_base);
29017 bus.write_word(pixmap_ptr + 4, 0x8000 | 20);
29018 bus.write_word(pixmap_ptr + 32, 8);
29019 bus.write_long(dialog_ptr + 2, pixmap_handle);
29020 bus.write_word(dialog_ptr + 6, 0xC000);
29021 bus.write_word(dialog_ptr + 16, 0);
29022 bus.write_word(dialog_ptr + 18, 0);
29023 bus.write_word(dialog_ptr + 20, 20);
29024 bus.write_word(dialog_ptr + 22, 20);
29025
29026 bus.write_byte(offscreen_base + 7 * 20 + 9, 0x44);
29027 disp.front_window = dialog_ptr;
29028 disp.window_bounds = (0, 0, 20, 20);
29029 disp.dialog_tracking = Some(crate::trap::dispatch::DialogTrackingState {
29030 dialog_ptr,
29031 bounds: (0, 0, 20, 20),
29032 title: String::new(),
29033 proc_id: 2,
29034 items: Vec::new(),
29035 default_item: 0,
29036 cancel_item: 0,
29037 edit_text: String::new(),
29038 edit_item: 0,
29039 saved_pixels: Vec::new(),
29040 stack_ptr: TEST_SP,
29041 item_hit_ptr: 0,
29042 rendered_pixels: Vec::new(),
29043 flash_remaining: 0,
29044 flash_delay: 0,
29045 flash_item: 0,
29046 edit_text_modified: false,
29047 draw_proc_queue: VecDeque::new(),
29048 draw_procs_done: true,
29049 rendered_pixels_final: false,
29050 filter_proc: 0,
29051 game_managed: false,
29052 last_filter_event: None,
29053 popup_draws: Vec::new(),
29054 active_popup: None,
29055 active_button: None,
29056 active_user_item: None,
29057 });
29058
29059 let result = disp.dispatch_dialog(true, 0x191, &mut cpu, &mut bus);
29060 assert!(result.unwrap().is_ok());
29061
29062 let screen_pixel = screen_base + 7 * 32 + 9;
29063 assert_eq!(
29064 bus.read_byte(screen_pixel),
29065 0x44,
29066 "ModalDialog should composite dialog-port userItem pixels before snapshot"
29067 );
29068 let tracking = disp.dialog_tracking.as_ref().unwrap();
29069 assert!(tracking.rendered_pixels_final);
29070 let snapshot_width = 20 + (TrapDispatcher::DBOX_FRAME_MARGIN as usize * 2);
29071 let snapshot_height = 20 + (TrapDispatcher::DBOX_FRAME_MARGIN as usize * 2);
29072 assert_eq!(
29073 tracking.rendered_pixels.len(),
29074 snapshot_width * snapshot_height
29075 );
29076 let snapshot_index = (7 + TrapDispatcher::DBOX_FRAME_MARGIN as usize) * snapshot_width
29077 + (9 + TrapDispatcher::DBOX_FRAME_MARGIN as usize);
29078 assert_eq!(tracking.rendered_pixels[snapshot_index], 0x44);
29079 }
29080
29081 #[test]
29082 fn modal_dialog_update_event_restores_existing_snapshot_before_resnapshot() {
29083 let (mut disp, mut cpu, mut bus) = setup();
29084 let screen_base = bus.alloc((32 * 32) as u32);
29085 for i in 0..32u32 * 32 {
29086 bus.write_byte(screen_base + i, 0x00);
29087 }
29088 bus.write_long(0x0824, screen_base);
29089 disp.screen_mode = (screen_base, 32, 32, 32, 8);
29090
29091 let dialog_ptr = bus.alloc(64);
29092 disp.front_window = dialog_ptr;
29093 disp.window_bounds = (0, 0, 20, 20);
29094
29095 let snapshot_width = 20 + (TrapDispatcher::DBOX_FRAME_MARGIN as usize * 2);
29096 let snapshot_height = 20 + (TrapDispatcher::DBOX_FRAME_MARGIN as usize * 2);
29097 let mut rendered_pixels = vec![0x00; snapshot_width * snapshot_height];
29098 let snapshot_index = (7 + TrapDispatcher::DBOX_FRAME_MARGIN as usize) * snapshot_width
29099 + (9 + TrapDispatcher::DBOX_FRAME_MARGIN as usize);
29100 rendered_pixels[snapshot_index] = 0x44;
29101
29102 disp.dialog_tracking = Some(crate::trap::dispatch::DialogTrackingState {
29103 dialog_ptr,
29104 bounds: (0, 0, 20, 20),
29105 title: String::new(),
29106 proc_id: 2,
29107 items: Vec::new(),
29108 default_item: 0,
29109 cancel_item: 0,
29110 edit_text: String::new(),
29111 edit_item: 0,
29112 saved_pixels: Vec::new(),
29113 stack_ptr: TEST_SP,
29114 item_hit_ptr: 0,
29115 rendered_pixels,
29116 flash_remaining: 0,
29117 flash_delay: 0,
29118 flash_item: 0,
29119 edit_text_modified: false,
29120 draw_proc_queue: VecDeque::new(),
29121 draw_procs_done: true,
29122 rendered_pixels_final: true,
29123 filter_proc: 0,
29124 game_managed: false,
29125 last_filter_event: None,
29126 popup_draws: Vec::new(),
29127 active_popup: None,
29128 active_button: None,
29129 active_user_item: None,
29130 });
29131 disp.event_queue
29132 .push_back(crate::trap::dispatch::QueuedEvent {
29133 what: 6,
29134 message: dialog_ptr,
29135 where_v: 0,
29136 where_h: 0,
29137 modifiers: 0,
29138 });
29139
29140 let result = disp.dispatch_dialog(true, 0x191, &mut cpu, &mut bus);
29141 assert!(result.unwrap().is_ok());
29142
29143 let screen_pixel = screen_base + 7 * 32 + 9;
29144 assert_eq!(
29145 bus.read_byte(screen_pixel),
29146 0x44,
29147 "ModalDialog update events must preserve userItem-rendered dialog pixels"
29148 );
29149 let tracking = disp.dialog_tracking.as_ref().unwrap();
29150 assert_eq!(tracking.rendered_pixels[snapshot_index], 0x44);
29151 }
29152
29153 #[test]
29154 fn finalize_dialog_draw_procs_snapshots_completed_user_item_pixels() {
29155 let (mut disp, _cpu, mut bus) = setup();
29156 let screen_base = bus.alloc((32 * 32) as u32);
29157 for i in 0..32u32 * 32 {
29158 bus.write_byte(screen_base + i, 0x00);
29159 }
29160 bus.write_long(0x0824, screen_base);
29161 disp.screen_mode = (screen_base, 32, 32, 32, 8);
29162
29163 let dialog_ptr = bus.alloc(64);
29164 let screen_pixel = screen_base + 7 * 32 + 9;
29165 bus.write_byte(screen_pixel, 0x44);
29166 disp.front_window = dialog_ptr;
29167 disp.window_bounds = (0, 0, 20, 20);
29168 disp.dialog_tracking = Some(crate::trap::dispatch::DialogTrackingState {
29169 dialog_ptr,
29170 bounds: (0, 0, 20, 20),
29171 title: String::new(),
29172 proc_id: 2,
29173 items: Vec::new(),
29174 default_item: 0,
29175 cancel_item: 0,
29176 edit_text: String::new(),
29177 edit_item: 0,
29178 saved_pixels: Vec::new(),
29179 stack_ptr: TEST_SP,
29180 item_hit_ptr: 0,
29181 rendered_pixels: Vec::new(),
29182 flash_remaining: 0,
29183 flash_delay: 0,
29184 flash_item: 0,
29185 edit_text_modified: false,
29186 draw_proc_queue: VecDeque::new(),
29187 draw_procs_done: false,
29188 rendered_pixels_final: false,
29189 filter_proc: 0,
29190 game_managed: false,
29191 last_filter_event: None,
29192 popup_draws: Vec::new(),
29193 active_popup: None,
29194 active_button: None,
29195 active_user_item: None,
29196 });
29197
29198 disp.finalize_dialog_draw_procs_if_idle(&mut bus);
29199
29200 let tracking = disp.dialog_tracking.as_ref().unwrap();
29201 assert!(tracking.draw_procs_done);
29202 assert!(tracking.rendered_pixels_final);
29203 let snapshot_width = 20 + (TrapDispatcher::DBOX_FRAME_MARGIN as usize * 2);
29204 let snapshot_index = (7 + TrapDispatcher::DBOX_FRAME_MARGIN as usize) * snapshot_width
29205 + (9 + TrapDispatcher::DBOX_FRAME_MARGIN as usize);
29206 assert_eq!(tracking.rendered_pixels[snapshot_index], 0x44);
29207 }
29208
29209 #[test]
29210 fn modal_dialog_mouse_down_return_consumes_queued_mouse_up() {
29211 let (mut disp, mut cpu, mut bus) = setup();
29212 let dialog_ptr = 0x200000u32;
29213 let item_hit_ptr = 0x300000u32;
29214
29215 disp.dialog_tracking = Some(crate::trap::dispatch::DialogTrackingState {
29216 dialog_ptr,
29217 bounds: (100, 200, 200, 360),
29218 title: String::new(),
29219 proc_id: 2,
29220 items: vec![DialogItem {
29221 item_type: 0,
29222 rect: (20, 30, 60, 110),
29223 text: String::new(),
29224 resource_id: 0,
29225 proc_ptr: 0,
29226 sel_start: 0,
29227 sel_end: 0,
29228 }],
29229 default_item: 1,
29230 cancel_item: 2,
29231 edit_text: String::new(),
29232 edit_item: 0,
29233 saved_pixels: Vec::new(),
29234 stack_ptr: TEST_SP,
29235 item_hit_ptr,
29236 rendered_pixels: Vec::new(),
29237 flash_remaining: 0,
29238 flash_delay: 0,
29239 flash_item: 0,
29240 edit_text_modified: false,
29241 draw_proc_queue: VecDeque::new(),
29242 draw_procs_done: true,
29243 rendered_pixels_final: true,
29244 filter_proc: 0,
29245 game_managed: true,
29246 last_filter_event: None,
29247 popup_draws: Vec::new(),
29248 active_popup: None,
29249 active_button: None,
29250 active_user_item: None,
29251 });
29252 disp.event_queue
29253 .push_back(crate::trap::dispatch::QueuedEvent {
29254 what: 1,
29255 message: 0,
29256 where_v: 130,
29257 where_h: 240,
29258 modifiers: 0,
29259 });
29260 disp.event_queue
29261 .push_back(crate::trap::dispatch::QueuedEvent {
29262 what: 2,
29263 message: 0,
29264 where_v: 130,
29265 where_h: 240,
29266 modifiers: 0x0080,
29267 });
29268
29269 let result = disp.dispatch_dialog(true, 0x191, &mut cpu, &mut bus);
29270 assert!(result.unwrap().is_ok());
29271 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 8);
29272 assert_eq!(bus.read_word(item_hit_ptr), 1);
29273 assert!(disp.event_queue.iter().all(|event| event.what != 2));
29274 }
29275
29276 #[test]
29277 fn modal_dialog_plain_user_item_returns_on_mouse_down() {
29278 let (mut disp, mut cpu, mut bus) = setup();
29285 let dialog_ptr = 0x200000u32;
29286 let item_hit_ptr = 0x300000u32;
29287
29288 disp.dialog_tracking = Some(crate::trap::dispatch::DialogTrackingState {
29289 dialog_ptr,
29290 bounds: (100, 200, 200, 360),
29291 title: String::new(),
29292 proc_id: 2,
29293 items: vec![DialogItem {
29294 item_type: 0,
29295 rect: (20, 30, 60, 110),
29296 text: String::new(),
29297 resource_id: 0,
29298 proc_ptr: 0,
29299 sel_start: 0,
29300 sel_end: 0,
29301 }],
29302 default_item: 1,
29303 cancel_item: 2,
29304 edit_text: String::new(),
29305 edit_item: 0,
29306 saved_pixels: Vec::new(),
29307 stack_ptr: TEST_SP,
29308 item_hit_ptr,
29309 rendered_pixels: Vec::new(),
29310 flash_remaining: 0,
29311 flash_delay: 0,
29312 flash_item: 0,
29313 edit_text_modified: false,
29314 draw_proc_queue: VecDeque::new(),
29315 draw_procs_done: true,
29316 rendered_pixels_final: true,
29317 filter_proc: 0,
29318 game_managed: false,
29319 last_filter_event: None,
29320 popup_draws: Vec::new(),
29321 active_popup: None,
29322 active_button: None,
29323 active_user_item: None,
29324 });
29325 disp.mouse_button = true;
29326 disp.mouse_pos = (130, 240);
29327 disp.event_queue
29328 .push_back(crate::trap::dispatch::QueuedEvent {
29329 what: 1,
29330 message: 0,
29331 where_v: 130,
29332 where_h: 240,
29333 modifiers: 0,
29334 });
29335
29336 let result = disp.dispatch_dialog(true, 0x191, &mut cpu, &mut bus);
29337 assert!(result.unwrap().is_ok());
29338 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 8);
29341 assert_eq!(bus.read_word(item_hit_ptr), 1);
29342 assert!(disp.dialog_tracking.is_none());
29343 }
29344
29345 #[test]
29346 fn modal_dialog_popup_candidate_user_item_returns_on_mouse_down_in_original_rect() {
29347 let (mut disp, mut cpu, mut bus) = setup();
29348 let dialog_ptr = 0x200000u32;
29349 let item_hit_ptr = 0x300000u32;
29350
29351 disp.dialog_tracking = Some(crate::trap::dispatch::DialogTrackingState {
29352 dialog_ptr,
29353 bounds: (100, 200, 200, 420),
29354 title: String::new(),
29355 proc_id: 2,
29356 items: vec![DialogItem {
29357 item_type: 0,
29358 rect: (20, 90, 40, 110),
29359 text: String::new(),
29360 resource_id: 0,
29361 proc_ptr: 0,
29362 sel_start: 0,
29363 sel_end: 0,
29364 }],
29365 default_item: 1,
29366 cancel_item: 2,
29367 edit_text: String::new(),
29368 edit_item: 0,
29369 saved_pixels: Vec::new(),
29370 stack_ptr: TEST_SP,
29371 item_hit_ptr,
29372 rendered_pixels: Vec::new(),
29373 flash_remaining: 0,
29374 flash_delay: 0,
29375 flash_item: 0,
29376 edit_text_modified: false,
29377 draw_proc_queue: VecDeque::new(),
29378 draw_procs_done: true,
29379 rendered_pixels_final: true,
29380 filter_proc: 0,
29381 game_managed: false,
29382 last_filter_event: None,
29383 popup_draws: Vec::new(),
29384 active_popup: None,
29385 active_button: None,
29386 active_user_item: None,
29387 });
29388 disp.dialog_popup_original_rects
29389 .insert((dialog_ptr, 1), (20, 30, 40, 150));
29390 disp.dialog_popup_candidate_items.insert((dialog_ptr, 1));
29391 disp.mouse_button = true;
29392 disp.mouse_pos = (130, 260);
29393 disp.event_queue
29394 .push_back(crate::trap::dispatch::QueuedEvent {
29395 what: 1,
29396 message: 0,
29397 where_v: 130,
29398 where_h: 260,
29399 modifiers: 0,
29400 });
29401
29402 let result = disp.dispatch_dialog(true, 0x191, &mut cpu, &mut bus);
29403 assert!(result.unwrap().is_ok());
29404 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 8);
29405 assert_eq!(bus.read_word(item_hit_ptr), 1);
29406 assert_eq!(
29407 disp.dialog_tracking
29408 .as_ref()
29409 .and_then(|tracking| tracking.active_user_item.as_ref())
29410 .map(|active| active.item_no),
29411 None,
29412 "popup-candidate userItems must return on mouse-down so the app can run PopUpMenuSelect"
29413 );
29414 }
29415
29416 #[test]
29417 fn modal_dialog_button_waits_for_late_mouse_up_before_returning() {
29418 let (mut disp, mut cpu, mut bus) = setup();
29419 let dialog_ptr = 0x200000u32;
29420 let item_hit_ptr = 0x300000u32;
29421
29422 disp.dialog_tracking = Some(crate::trap::dispatch::DialogTrackingState {
29423 dialog_ptr,
29424 bounds: (100, 200, 200, 360),
29425 title: String::new(),
29426 proc_id: 2,
29427 items: vec![DialogItem {
29428 item_type: 4,
29429 rect: (20, 30, 60, 110),
29430 text: String::from("OK"),
29431 resource_id: 0,
29432 proc_ptr: 0,
29433 sel_start: 0,
29434 sel_end: 0,
29435 }],
29436 default_item: 1,
29437 cancel_item: 0,
29438 edit_text: String::new(),
29439 edit_item: 0,
29440 saved_pixels: Vec::new(),
29441 stack_ptr: TEST_SP,
29442 item_hit_ptr,
29443 rendered_pixels: Vec::new(),
29444 flash_remaining: 0,
29445 flash_delay: 0,
29446 flash_item: 0,
29447 edit_text_modified: false,
29448 draw_proc_queue: VecDeque::new(),
29449 draw_procs_done: true,
29450 rendered_pixels_final: true,
29451 filter_proc: 0,
29452 game_managed: true,
29453 last_filter_event: None,
29454 popup_draws: Vec::new(),
29455 active_popup: None,
29456 active_button: None,
29457 active_user_item: None,
29458 });
29459 disp.mouse_button = true;
29460 disp.mouse_pos = (130, 240);
29461 disp.event_queue
29462 .push_back(crate::trap::dispatch::QueuedEvent {
29463 what: 1,
29464 message: 0,
29465 where_v: 130,
29466 where_h: 240,
29467 modifiers: 0,
29468 });
29469
29470 let result = disp.dispatch_dialog(true, 0x191, &mut cpu, &mut bus);
29471 assert!(result.unwrap().is_ok());
29472 assert_eq!(cpu.read_reg(Register::A7), TEST_SP);
29473 assert_eq!(bus.read_word(item_hit_ptr), 0);
29474 assert!(disp
29475 .dialog_tracking
29476 .as_ref()
29477 .and_then(|tracking| tracking.active_button.as_ref())
29478 .is_some());
29479
29480 disp.mouse_button = false;
29481 disp.event_queue
29482 .push_back(crate::trap::dispatch::QueuedEvent {
29483 what: 2,
29484 message: 0,
29485 where_v: 130,
29486 where_h: 240,
29487 modifiers: 0x0080,
29488 });
29489 let result = disp.dispatch_dialog(true, 0x191, &mut cpu, &mut bus);
29490 assert!(result.unwrap().is_ok());
29491 assert!(disp.event_queue.iter().all(|event| event.what != 2));
29492 assert!(disp
29493 .dialog_tracking
29494 .as_ref()
29495 .and_then(|tracking| tracking.active_button.as_ref())
29496 .is_none());
29497 assert_eq!(cpu.read_reg(Register::A7), TEST_SP);
29498
29499 {
29500 let tracking = disp.dialog_tracking.as_mut().unwrap();
29501 tracking.flash_remaining = 1;
29502 tracking.flash_delay = 0;
29503 }
29504 let result = disp.dispatch_dialog(true, 0x191, &mut cpu, &mut bus);
29505 assert!(result.unwrap().is_ok());
29506 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 8);
29507 assert_eq!(bus.read_word(item_hit_ptr), 1);
29508 }
29509
29510 #[test]
29511 fn modal_dialog_filter_handled_mouse_down_consumes_queued_mouse_up() {
29512 let (mut disp, mut cpu, mut bus) = setup();
29513 let dialog_ptr = 0x200000u32;
29514 let item_hit_ptr = 0x300000u32;
29515 let result_addr = 0x300100u32;
29516
29517 bus.write_word(result_addr, 0xFFFF);
29518 bus.write_word(item_hit_ptr, 12);
29519 disp.dialog_filter_result_addr = result_addr;
29520 disp.dialog_tracking = Some(crate::trap::dispatch::DialogTrackingState {
29521 dialog_ptr,
29522 bounds: (100, 200, 200, 360),
29523 title: String::new(),
29524 proc_id: 2,
29525 items: vec![DialogItem {
29526 item_type: 0,
29527 rect: (20, 30, 60, 110),
29528 text: String::new(),
29529 resource_id: 0,
29530 proc_ptr: 0,
29531 sel_start: 0,
29532 sel_end: 0,
29533 }],
29534 default_item: 1,
29535 cancel_item: 2,
29536 edit_text: String::new(),
29537 edit_item: 0,
29538 saved_pixels: Vec::new(),
29539 stack_ptr: TEST_SP,
29540 item_hit_ptr,
29541 rendered_pixels: Vec::new(),
29542 flash_remaining: 0,
29543 flash_delay: 0,
29544 flash_item: 0,
29545 edit_text_modified: false,
29546 draw_proc_queue: VecDeque::new(),
29547 draw_procs_done: true,
29548 rendered_pixels_final: true,
29549 filter_proc: 0x149F0,
29550 game_managed: true,
29551 last_filter_event: Some(crate::trap::dispatch::QueuedEvent {
29552 what: 1,
29553 message: 0,
29554 where_v: 130,
29555 where_h: 240,
29556 modifiers: 0,
29557 }),
29558 popup_draws: Vec::new(),
29559 active_popup: None,
29560 active_button: None,
29561 active_user_item: None,
29562 });
29563 disp.event_queue
29564 .push_back(crate::trap::dispatch::QueuedEvent {
29565 what: 2,
29566 message: 0,
29567 where_v: 130,
29568 where_h: 240,
29569 modifiers: 0x0080,
29570 });
29571
29572 let result = disp.dispatch_dialog(true, 0x191, &mut cpu, &mut bus);
29573 assert!(result.unwrap().is_ok());
29574 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 8);
29575 assert!(disp.event_queue.iter().all(|event| event.what != 2));
29576 }
29577
29578 #[test]
29579 fn modal_dialog_return_key_activates_default_button_after_filter_false() {
29580 let (mut disp, mut cpu, mut bus) = setup();
29581 let dialog_ptr = 0x200000u32;
29582 let item_hit_ptr = 0x300000u32;
29583 let result_addr = 0x300100u32;
29584
29585 bus.write_word(result_addr, 0);
29586 bus.write_word(item_hit_ptr, 0);
29587 disp.dialog_filter_result_addr = result_addr;
29588 disp.dialog_tracking = Some(crate::trap::dispatch::DialogTrackingState {
29589 dialog_ptr,
29590 bounds: (100, 200, 200, 360),
29591 title: String::new(),
29592 proc_id: 2,
29593 items: vec![DialogItem {
29594 item_type: 4,
29595 rect: (20, 30, 60, 110),
29596 text: String::from("OK"),
29597 resource_id: 0,
29598 proc_ptr: 0,
29599 sel_start: 0,
29600 sel_end: 0,
29601 }],
29602 default_item: 1,
29603 cancel_item: 0,
29604 edit_text: String::new(),
29605 edit_item: 0,
29606 saved_pixels: Vec::new(),
29607 stack_ptr: TEST_SP,
29608 item_hit_ptr,
29609 rendered_pixels: Vec::new(),
29610 flash_remaining: 0,
29611 flash_delay: 0,
29612 flash_item: 0,
29613 edit_text_modified: false,
29614 draw_proc_queue: VecDeque::new(),
29615 draw_procs_done: true,
29616 rendered_pixels_final: true,
29617 filter_proc: 0x149F0,
29618 game_managed: true,
29619 last_filter_event: Some(crate::trap::dispatch::QueuedEvent {
29620 what: 3,
29621 message: 0x0000_240D,
29622 where_v: 130,
29623 where_h: 240,
29624 modifiers: 0,
29625 }),
29626 popup_draws: Vec::new(),
29627 active_popup: None,
29628 active_button: None,
29629 active_user_item: None,
29630 });
29631
29632 let result = disp.dispatch_dialog(true, 0x191, &mut cpu, &mut bus);
29633 assert!(result.unwrap().is_ok());
29634 assert_eq!(cpu.read_reg(Register::A7), TEST_SP);
29635 assert_eq!(bus.read_word(item_hit_ptr), 0);
29636 {
29637 let tracking = disp.dialog_tracking.as_ref().unwrap();
29638 assert_eq!(tracking.flash_item, 1);
29639 assert!(tracking.flash_remaining > 0);
29640 }
29641
29642 {
29643 let tracking = disp.dialog_tracking.as_mut().unwrap();
29644 tracking.flash_remaining = 1;
29645 tracking.flash_delay = 0;
29646 }
29647 let result = disp.dispatch_dialog(true, 0x191, &mut cpu, &mut bus);
29648 assert!(result.unwrap().is_ok());
29649 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 8);
29650 assert_eq!(bus.read_word(item_hit_ptr), 1);
29651 assert!(disp.dialog_tracking.is_none());
29652 }
29653
29654 #[test]
29655 fn modal_dialog_filter_low_byte_nonzero_result_is_false() {
29656 let (mut disp, mut cpu, mut bus) = setup();
29657 let dialog_ptr = 0x200000u32;
29658 let item_hit_ptr = 0x300000u32;
29659 let result_addr = 0x300100u32;
29660
29661 bus.write_word(result_addr, 0x0001);
29662 bus.write_word(item_hit_ptr, 0);
29663 disp.dialog_filter_result_addr = result_addr;
29664 disp.dialog_tracking = Some(crate::trap::dispatch::DialogTrackingState {
29665 dialog_ptr,
29666 bounds: (100, 200, 200, 360),
29667 title: String::new(),
29668 proc_id: 2,
29669 items: vec![DialogItem {
29670 item_type: 4,
29671 rect: (20, 30, 60, 110),
29672 text: String::from("OK"),
29673 resource_id: 0,
29674 proc_ptr: 0,
29675 sel_start: 0,
29676 sel_end: 0,
29677 }],
29678 default_item: 1,
29679 cancel_item: 0,
29680 edit_text: String::new(),
29681 edit_item: 0,
29682 saved_pixels: Vec::new(),
29683 stack_ptr: TEST_SP,
29684 item_hit_ptr,
29685 rendered_pixels: Vec::new(),
29686 flash_remaining: 0,
29687 flash_delay: 0,
29688 flash_item: 0,
29689 edit_text_modified: false,
29690 draw_proc_queue: VecDeque::new(),
29691 draw_procs_done: true,
29692 rendered_pixels_final: true,
29693 filter_proc: 0x149F0,
29694 game_managed: false,
29695 last_filter_event: Some(crate::trap::dispatch::QueuedEvent {
29696 what: 1,
29697 message: 0,
29698 where_v: 130,
29699 where_h: 240,
29700 modifiers: 0,
29701 }),
29702 popup_draws: Vec::new(),
29703 active_popup: None,
29704 active_button: None,
29705 active_user_item: None,
29706 });
29707 disp.mouse_button = true;
29708 disp.mouse_pos = (130, 240);
29709
29710 let result = disp.dispatch_dialog(true, 0x191, &mut cpu, &mut bus);
29711 assert!(result.unwrap().is_ok());
29712 assert_eq!(cpu.read_reg(Register::A7), TEST_SP);
29713 assert_eq!(bus.read_word(item_hit_ptr), 0);
29714 assert!(disp
29715 .dialog_tracking
29716 .as_ref()
29717 .and_then(|tracking| tracking.active_button.as_ref())
29718 .is_some());
29719 }
29720
29721 #[test]
29722 fn modal_dialog_filter_true_zero_hit_returns_to_app() {
29723 let (mut disp, mut cpu, mut bus) = setup();
29724 let dialog_ptr = 0x200000u32;
29725 let item_hit_ptr = 0x300000u32;
29726 let result_addr = 0x300100u32;
29727
29728 bus.write_word(result_addr, 0xFFFF);
29729 bus.write_word(item_hit_ptr, 0);
29730 disp.dialog_filter_result_addr = result_addr;
29731 disp.dialog_tracking = Some(crate::trap::dispatch::DialogTrackingState {
29732 dialog_ptr,
29733 bounds: (100, 200, 200, 360),
29734 title: String::new(),
29735 proc_id: 2,
29736 items: vec![DialogItem {
29737 item_type: 4,
29738 rect: (20, 30, 60, 110),
29739 text: String::from("OK"),
29740 resource_id: 0,
29741 proc_ptr: 0,
29742 sel_start: 0,
29743 sel_end: 0,
29744 }],
29745 default_item: 1,
29746 cancel_item: 2,
29747 edit_text: String::new(),
29748 edit_item: 0,
29749 saved_pixels: Vec::new(),
29750 stack_ptr: TEST_SP,
29751 item_hit_ptr,
29752 rendered_pixels: Vec::new(),
29753 flash_remaining: 0,
29754 flash_delay: 0,
29755 flash_item: 0,
29756 edit_text_modified: false,
29757 draw_proc_queue: VecDeque::new(),
29758 draw_procs_done: true,
29759 rendered_pixels_final: true,
29760 filter_proc: 0x149F0,
29761 game_managed: false,
29762 last_filter_event: Some(crate::trap::dispatch::QueuedEvent {
29763 what: 3,
29764 message: 0x0000_240D,
29765 where_v: 130,
29766 where_h: 240,
29767 modifiers: 0,
29768 }),
29769 popup_draws: Vec::new(),
29770 active_popup: None,
29771 active_button: None,
29772 active_user_item: None,
29773 });
29774
29775 let result = disp.dispatch_dialog(true, 0x191, &mut cpu, &mut bus);
29776 assert!(result.unwrap().is_ok());
29777 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 8);
29778 assert_eq!(bus.read_word(item_hit_ptr), 0);
29779 assert!(disp.dialog_tracking.is_none());
29780 }
29781
29782 #[test]
29785 fn teinit_first_call_allocates_empty_scrap_handle_and_zeros_length() {
29786 let (mut disp, mut cpu, mut bus) = setup();
29789 let sp_before = cpu.read_reg(Register::A7);
29790 bus.write_word(crate::memory::globals::addr::TE_SCRP_LENGTH, 0xFFFF);
29791 bus.write_long(crate::memory::globals::addr::TE_SCRP_HANDLE, 0);
29792
29793 let result = disp.dispatch_dialog(true, 0x1CC, &mut cpu, &mut bus);
29794 assert!(result.unwrap().is_ok());
29795 assert_eq!(cpu.read_reg(Register::A7), sp_before);
29796
29797 let scrap_handle = bus.read_long(crate::memory::globals::addr::TE_SCRP_HANDLE);
29798 assert_ne!(scrap_handle, 0, "TEInit must allocate TEScrpHandle");
29799 assert_eq!(
29800 bus.read_long(scrap_handle),
29801 0,
29802 "TEInit scrap handle should initially reference empty data"
29803 );
29804 assert_eq!(
29805 bus.read_word(crate::memory::globals::addr::TE_SCRP_LENGTH),
29806 0
29807 );
29808 }
29809
29810 #[test]
29811 fn teinit_reuses_existing_scrap_handle_and_resets_length() {
29812 let (mut disp, mut cpu, mut bus) = setup();
29815 let existing_handle = TrapDispatcher::allocate_handle_with_data(&mut bus, 3);
29816 let existing_ptr = bus.read_long(existing_handle);
29817 bus.write_bytes(existing_ptr, b"xyz");
29818 bus.write_long(
29819 crate::memory::globals::addr::TE_SCRP_HANDLE,
29820 existing_handle,
29821 );
29822 bus.write_word(crate::memory::globals::addr::TE_SCRP_LENGTH, 3);
29823
29824 let result = disp.dispatch_dialog(true, 0x1CC, &mut cpu, &mut bus);
29825 assert!(result.unwrap().is_ok());
29826 assert_eq!(cpu.read_reg(Register::A7), TEST_SP);
29827 assert_eq!(
29828 bus.read_long(crate::memory::globals::addr::TE_SCRP_HANDLE),
29829 existing_handle
29830 );
29831 assert_eq!(
29832 bus.read_word(crate::memory::globals::addr::TE_SCRP_LENGTH),
29833 0
29834 );
29835 assert_eq!(bus.read_long(existing_handle), existing_ptr);
29836 assert_eq!(bus.read_bytes(existing_ptr, 3), b"xyz".to_vec());
29837 }
29838
29839 fn make_te_with_text(
29840 disp: &mut TrapDispatcher,
29841 bus: &mut crate::memory::MacMemoryBus,
29842 text: &[u8],
29843 ) -> u32 {
29844 let te_handle = TrapDispatcher::allocate_te_handle(bus);
29845 disp.current_port = 0x181000;
29846 disp.tx_font = 4;
29847 disp.tx_face = 0;
29848 disp.tx_mode = 0;
29849 disp.tx_size = 10;
29850 disp.initialize_te_record(bus, te_handle, (0, 0, 60, 160), (0, 0, 60, 160));
29851 disp.te_set_text_contents(bus, te_handle, text);
29852 te_handle
29853 }
29854
29855 #[test]
29858 fn tenew_pointer_arg_convention_initializes_destrect_viewrect_and_returns_non_nil_handle() {
29859 let (mut disp, mut cpu, mut bus) = setup();
29867
29868 let dest_rect_ptr: u32 = 0x190000;
29870 bus.write_word(dest_rect_ptr, (-1000i16) as u16); bus.write_word(dest_rect_ptr + 2, (-1000i16) as u16); bus.write_word(dest_rect_ptr + 4, (-700i16) as u16); bus.write_word(dest_rect_ptr + 6, (-800i16) as u16); let view_rect_ptr: u32 = 0x190010;
29875 bus.write_word(view_rect_ptr, (-900i16) as u16); bus.write_word(view_rect_ptr + 2, (-1100i16) as u16); bus.write_word(view_rect_ptr + 4, (-600i16) as u16); bus.write_word(view_rect_ptr + 6, (-700i16) as u16); bus.write_long(TEST_SP, view_rect_ptr);
29887 bus.write_long(TEST_SP + 4, dest_rect_ptr);
29888 bus.write_long(TEST_SP + 8, 0xDEAD_BEEF);
29889
29890 let result = disp.dispatch_dialog(true, 0x1D2, &mut cpu, &mut bus);
29891 assert!(result.unwrap().is_ok());
29892 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 8);
29893
29894 let te_handle = bus.read_long(TEST_SP + 8);
29895 assert_ne!(te_handle, 0, "TENew returned NIL TEHandle");
29896 let te_ptr = bus.read_long(te_handle);
29897 assert_ne!(te_ptr, 0, "TEHandle master pointer is NIL");
29898
29899 assert_eq!(
29901 bus.read_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET) as i16,
29902 -1000
29903 );
29904 assert_eq!(
29905 bus.read_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET + 2) as i16,
29906 -1000
29907 );
29908 assert_eq!(
29909 bus.read_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET + 4) as i16,
29910 -700
29911 );
29912 assert_eq!(
29913 bus.read_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET + 6) as i16,
29914 -800
29915 );
29916
29917 assert_eq!(
29919 bus.read_word(te_ptr + TrapDispatcher::TE_VIEW_RECT_OFFSET) as i16,
29920 -900
29921 );
29922 assert_eq!(
29923 bus.read_word(te_ptr + TrapDispatcher::TE_VIEW_RECT_OFFSET + 2) as i16,
29924 -1100
29925 );
29926 assert_eq!(
29927 bus.read_word(te_ptr + TrapDispatcher::TE_VIEW_RECT_OFFSET + 4) as i16,
29928 -600
29929 );
29930 assert_eq!(
29931 bus.read_word(te_ptr + TrapDispatcher::TE_VIEW_RECT_OFFSET + 6) as i16,
29932 -700
29933 );
29934 }
29935
29936 #[test]
29937 fn tenew_fresh_terec_has_zero_telength_and_empty_selection_per_im_i_373() {
29938 let (mut disp, mut cpu, mut bus) = setup();
29942
29943 let rect_ptr: u32 = 0x190020;
29944 bus.write_word(rect_ptr, 10); bus.write_word(rect_ptr + 2, 20); bus.write_word(rect_ptr + 4, 110); bus.write_word(rect_ptr + 6, 220); bus.write_long(TEST_SP, rect_ptr);
29950 bus.write_long(TEST_SP + 4, rect_ptr);
29951
29952 let result = disp.dispatch_dialog(true, 0x1D2, &mut cpu, &mut bus);
29953 assert!(result.unwrap().is_ok());
29954
29955 let te_handle = bus.read_long(TEST_SP + 8);
29956 let te_ptr = bus.read_long(te_handle);
29957
29958 assert_eq!(
29959 bus.read_word(te_ptr + TrapDispatcher::TE_LENGTH_OFFSET),
29960 0,
29961 "fresh TERec must have teLength == 0"
29962 );
29963 assert_eq!(
29964 bus.read_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET),
29965 0,
29966 "fresh TERec must have selStart == 0"
29967 );
29968 assert_eq!(
29969 bus.read_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET),
29970 0,
29971 "fresh TERec must have selEnd == 0"
29972 );
29973 let h_text = bus.read_long(te_ptr + TrapDispatcher::TE_HTEXT_OFFSET);
29974 assert_ne!(h_text, 0, "fresh TERec must have a non-NIL hText handle");
29975 }
29976
29977 #[test]
29978 fn tenew_function_protocol_consumes_two_pointer_args_and_writes_4_byte_result() {
29979 let (mut disp, mut cpu, mut bus) = setup();
29984
29985 let rect_ptr: u32 = 0x190030;
29986 bus.write_word(rect_ptr, 0);
29987 bus.write_word(rect_ptr + 2, 0);
29988 bus.write_word(rect_ptr + 4, 100);
29989 bus.write_word(rect_ptr + 6, 200);
29990
29991 bus.write_long(TEST_SP, rect_ptr);
29992 bus.write_long(TEST_SP + 4, rect_ptr);
29993 bus.write_long(TEST_SP + 8, 0xDEAD_BEEF);
29994 bus.write_long(TEST_SP + 12, 0xCAFE_BABE); let result = disp.dispatch_dialog(true, 0x1D2, &mut cpu, &mut bus);
29997 assert!(result.unwrap().is_ok());
29998 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 8);
29999
30000 let te_handle = bus.read_long(TEST_SP + 8);
30001 assert_ne!(te_handle, 0xDEAD_BEEF, "result slot must be overwritten");
30002 assert_ne!(te_handle, 0);
30003 assert_eq!(
30004 bus.read_long(TEST_SP + 12),
30005 0xCAFE_BABE,
30006 "sentinel past 4-byte result slot must survive"
30007 );
30008 }
30009
30010 #[test]
30011 fn tenew_pointer_arg_convention_accepts_wide_ordered_destrect() {
30012 let (mut disp, mut cpu, mut bus) = setup();
30017
30018 let dest_record_ptr: u32 = 0x190040;
30019 bus.write_word(dest_record_ptr, 159);
30020 bus.write_word(dest_record_ptr + 2, 500);
30021 bus.write_word(dest_record_ptr + 4, 171);
30022 bus.write_word(dest_record_ptr + 6, 10500);
30023
30024 let view_rect_ptr: u32 = 0x190060;
30025 bus.write_word(view_rect_ptr, 159);
30026 bus.write_word(view_rect_ptr + 2, 500);
30027 bus.write_word(view_rect_ptr + 4, 171);
30028 bus.write_word(view_rect_ptr + 6, 10500);
30029
30030 bus.write_long(TEST_SP, view_rect_ptr);
30031 bus.write_long(TEST_SP + 4, dest_record_ptr);
30032 bus.write_long(TEST_SP + 8, 0xDEAD_BEEF);
30033 bus.write_long(TEST_SP + 12, 0xCAFE_BABE);
30034
30035 let result = disp.dispatch_dialog(true, 0x1D2, &mut cpu, &mut bus);
30036 assert!(result.unwrap().is_ok());
30037 assert_eq!(
30038 cpu.read_reg(Register::A7),
30039 TEST_SP + 8,
30040 "TENew pointer convention must pop exactly two pointers"
30041 );
30042
30043 let te_handle = bus.read_long(TEST_SP + 8);
30044 assert_ne!(te_handle, 0);
30045 assert_ne!(te_handle, 0xDEAD_BEEF);
30046 assert_eq!(
30047 bus.read_long(TEST_SP + 12),
30048 0xCAFE_BABE,
30049 "TENew must not write past the 4-byte function-result slot"
30050 );
30051
30052 let te_ptr = bus.read_long(te_handle);
30053 assert_ne!(te_ptr, 0);
30054 assert_eq!(
30055 bus.read_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET + 6) as i16,
30056 10500,
30057 "wide destRect.right must round-trip from caller memory"
30058 );
30059 assert_eq!(
30060 bus.read_word(te_ptr + TrapDispatcher::TE_VIEW_RECT_OFFSET + 6) as i16,
30061 10500,
30062 "wide viewRect.right must round-trip from caller memory"
30063 );
30064 }
30065
30066 #[test]
30067 fn tenew_pointer_arg_convention_is_based_on_valid_pointers_not_rect_shape() {
30068 let (mut disp, mut cpu, mut bus) = setup();
30073
30074 let dest_rect_ptr: u32 = 0x190080;
30075 bus.write_word(dest_rect_ptr, 552);
30076 bus.write_word(dest_rect_ptr + 2, 525);
30077 bus.write_word(dest_rect_ptr + 4, 520);
30078 bus.write_word(dest_rect_ptr + 6, 10525);
30079
30080 let view_rect_ptr: u32 = 0x1900A0;
30081 bus.write_word(view_rect_ptr, 552);
30082 bus.write_word(view_rect_ptr + 2, 525);
30083 bus.write_word(view_rect_ptr + 4, 520);
30084 bus.write_word(view_rect_ptr + 6, 10525);
30085
30086 bus.write_long(TEST_SP, view_rect_ptr);
30087 bus.write_long(TEST_SP + 4, dest_rect_ptr);
30088 bus.write_long(TEST_SP + 8, 0xDEAD_BEEF);
30089 bus.write_long(TEST_SP + 12, 0xCAFE_BABE);
30090
30091 let result = disp.dispatch_dialog(true, 0x1D2, &mut cpu, &mut bus);
30092 assert!(result.unwrap().is_ok());
30093 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 8);
30094
30095 let te_handle = bus.read_long(TEST_SP + 8);
30096 assert_ne!(te_handle, 0);
30097 assert_ne!(te_handle, 0xDEAD_BEEF);
30098 assert_eq!(bus.read_long(TEST_SP + 12), 0xCAFE_BABE);
30099
30100 let te_ptr = bus.read_long(te_handle);
30101 assert_eq!(
30102 bus.read_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET) as i16,
30103 552
30104 );
30105 assert_eq!(
30106 bus.read_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET + 4) as i16,
30107 520
30108 );
30109 assert_eq!(
30110 bus.read_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET + 6) as i16,
30111 10525
30112 );
30113 }
30114
30115 #[test]
30118 fn tedispose_releases_te_record_text_handle_and_pops_arg() {
30119 let (mut disp, mut cpu, mut bus) = setup();
30121 let te_handle = make_te_with_text(&mut disp, &mut bus, b"HELLO");
30122 let te_ptr = bus.read_long(te_handle);
30123 let text_handle = bus.read_long(te_ptr + TrapDispatcher::TE_HTEXT_OFFSET);
30124 let text_ptr = bus.read_long(text_handle);
30125 assert_ne!(te_handle, 0);
30126 assert_ne!(te_ptr, 0);
30127 assert_ne!(text_handle, 0);
30128 assert_ne!(text_ptr, 0);
30129
30130 bus.write_long(TEST_SP, te_handle);
30131 let result = disp.dispatch_dialog(true, 0x1CD, &mut cpu, &mut bus);
30132 assert!(result.unwrap().is_ok());
30133 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 4);
30134
30135 assert_eq!(bus.get_alloc_size(text_ptr), None);
30136 assert_eq!(bus.get_alloc_size(text_handle), None);
30137 assert_eq!(bus.get_alloc_size(te_ptr), None);
30138 assert_eq!(bus.get_alloc_size(te_handle), None);
30139 }
30140
30141 #[test]
30142 fn tecaltext_recomputes_line_metadata_and_pops_arg() {
30143 let (mut disp, mut cpu, mut bus) = setup();
30145 let te_handle = make_te_with_text(&mut disp, &mut bus, b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
30146 let te_ptr = bus.read_long(te_handle);
30147 let text_len = TrapDispatcher::te_text_bytes(&bus, te_handle).len() as u16;
30148 assert_eq!(text_len, 32);
30149
30150 bus.write_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET + 6, 12);
30153 bus.write_word(te_ptr + TrapDispatcher::TE_LENGTH_OFFSET, 0);
30154 bus.write_word(te_ptr + TrapDispatcher::TE_N_LINES_OFFSET, 0x7777);
30155 bus.write_word(te_ptr + TrapDispatcher::TE_LINE_STARTS_OFFSET, 0x7777);
30156
30157 bus.write_long(TEST_SP, te_handle);
30158 let result = disp.dispatch_dialog(true, 0x1D0, &mut cpu, &mut bus);
30159 assert!(result.unwrap().is_ok());
30160 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 4);
30161
30162 let te_ptr = bus.read_long(te_handle);
30163 let n_lines = bus.read_word(te_ptr + TrapDispatcher::TE_N_LINES_OFFSET);
30164 assert_ne!(n_lines, 0x7777);
30165 assert!(n_lines > 1);
30166 assert_eq!(
30167 bus.read_word(te_ptr + TrapDispatcher::TE_LENGTH_OFFSET),
30168 text_len
30169 );
30170 assert_eq!(
30171 bus.read_word(te_ptr + TrapDispatcher::TE_LINE_STARTS_OFFSET),
30172 0
30173 );
30174 assert_eq!(
30175 bus.read_word(te_ptr + TrapDispatcher::TE_LINE_STARTS_OFFSET + u32::from(n_lines) * 2),
30176 text_len
30177 );
30178 }
30179
30180 #[test]
30181 fn tegettext_returns_htext_handle_from_terec_and_pops_te_handle_arg() {
30182 let (mut disp, mut cpu, mut bus) = setup();
30186 let te_handle = make_te_with_text(&mut disp, &mut bus, b"HELLO");
30187 let te_ptr = bus.read_long(te_handle);
30188 let expected_h_text = bus.read_long(te_ptr + TrapDispatcher::TE_HTEXT_OFFSET);
30189 assert_ne!(expected_h_text, 0);
30190
30191 bus.write_long(TEST_SP, te_handle);
30192 bus.write_long(TEST_SP + 4, 0xDEAD_BEEF); let result = disp.dispatch_dialog(true, 0x1CB, &mut cpu, &mut bus);
30194 assert!(result.unwrap().is_ok());
30195 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 4);
30196
30197 assert_eq!(bus.read_long(TEST_SP + 4), expected_h_text);
30199
30200 let text_ptr = bus.read_long(expected_h_text);
30202 assert_ne!(text_ptr, 0);
30203 assert_eq!(bus.read_bytes(text_ptr, 5), b"HELLO".to_vec());
30204 }
30205
30206 #[test]
30207 fn tegettext_returns_nil_handle_when_te_handle_is_zero() {
30208 let (mut disp, mut cpu, mut bus) = setup();
30212
30213 bus.write_long(TEST_SP, 0); bus.write_long(TEST_SP + 4, 0xCAFE_BABE); let result = disp.dispatch_dialog(true, 0x1CB, &mut cpu, &mut bus);
30216 assert!(result.unwrap().is_ok());
30217 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 4);
30218 assert_eq!(bus.read_long(TEST_SP + 4), 0);
30219 }
30220
30221 #[test]
30222 fn tesetselect_clamps_selend_to_text_length_and_pops_args() {
30223 let (mut disp, mut cpu, mut bus) = setup();
30225 let te_handle = make_te_with_text(&mut disp, &mut bus, b"HELLO");
30226 let te_ptr = bus.read_long(te_handle);
30227 assert_eq!(bus.read_word(te_ptr + TrapDispatcher::TE_LENGTH_OFFSET), 5);
30228
30229 bus.write_long(TEST_SP, te_handle); bus.write_long(TEST_SP + 4, 999); bus.write_long(TEST_SP + 8, 2); let result = disp.dispatch_dialog(true, 0x1D1, &mut cpu, &mut bus);
30233 assert!(result.unwrap().is_ok());
30234 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 12);
30235
30236 let te_ptr = bus.read_long(te_handle);
30237 assert_eq!(
30238 bus.read_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET),
30239 2
30240 );
30241 assert_eq!(bus.read_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET), 5);
30242 }
30243
30244 #[test]
30245 fn tedelete_removes_selection_without_touching_scrap_and_pops_arg() {
30246 let (mut disp, mut cpu, mut bus) = setup();
30248 let existing_scrap = TrapDispatcher::allocate_handle_with_data(&mut bus, 2);
30249 let existing_ptr = bus.read_long(existing_scrap);
30250 bus.write_bytes(existing_ptr, b"QQ");
30251 bus.write_long(crate::memory::globals::addr::TE_SCRP_HANDLE, existing_scrap);
30252 bus.write_word(crate::memory::globals::addr::TE_SCRP_LENGTH, 2);
30253
30254 let te_handle = make_te_with_text(&mut disp, &mut bus, b"HELLO");
30255 let te_ptr = bus.read_long(te_handle);
30256 bus.write_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET, 1);
30257 bus.write_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET, 4);
30258
30259 bus.write_long(TEST_SP, te_handle);
30260 let result = disp.dispatch_dialog(true, 0x1D7, &mut cpu, &mut bus);
30261 assert!(result.unwrap().is_ok());
30262 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 4);
30263 assert_eq!(
30264 TrapDispatcher::te_text_bytes(&bus, te_handle),
30265 b"HO".to_vec()
30266 );
30267
30268 let te_ptr = bus.read_long(te_handle);
30269 assert_eq!(
30270 bus.read_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET),
30271 1
30272 );
30273 assert_eq!(bus.read_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET), 1);
30274 assert_eq!(
30275 bus.read_long(crate::memory::globals::addr::TE_SCRP_HANDLE),
30276 existing_scrap
30277 );
30278 assert_eq!(
30279 bus.read_word(crate::memory::globals::addr::TE_SCRP_LENGTH),
30280 2
30281 );
30282 assert_eq!(bus.read_bytes(existing_ptr, 2), b"QQ".to_vec());
30283 }
30284
30285 #[test]
30286 fn tedelete_insertion_point_selection_is_noop_and_pops_arg() {
30287 let (mut disp, mut cpu, mut bus) = setup();
30289 let te_handle = make_te_with_text(&mut disp, &mut bus, b"HELLO");
30290 let te_ptr = bus.read_long(te_handle);
30291 bus.write_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET, 3);
30292 bus.write_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET, 3);
30293
30294 bus.write_long(TEST_SP, te_handle);
30295 let result = disp.dispatch_dialog(true, 0x1D7, &mut cpu, &mut bus);
30296 assert!(result.unwrap().is_ok());
30297 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 4);
30298 assert_eq!(
30299 TrapDispatcher::te_text_bytes(&bus, te_handle),
30300 b"HELLO".to_vec()
30301 );
30302 assert_eq!(
30303 bus.read_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET),
30304 3
30305 );
30306 assert_eq!(bus.read_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET), 3);
30307 }
30308
30309 #[test]
30310 fn teupdate_pascal_form_redraws_text_and_pops_inline_rect_and_tehandle() {
30311 let (mut disp, mut cpu, mut bus) = setup_with_port();
30313 let te_handle = make_te_with_text(&mut disp, &mut bus, b"HELLO");
30314 let (screen_base, row_bytes, _screen_w, _screen_h, _pixel_size) = disp.screen_mode;
30315 for i in 0..(row_bytes * 80) {
30316 bus.write_byte(screen_base + i, 0);
30317 }
30318
30319 bus.write_long(TEST_SP, te_handle);
30320 bus.write_word(TEST_SP + 4, 0); bus.write_word(TEST_SP + 6, 0); bus.write_word(TEST_SP + 8, 40); bus.write_word(TEST_SP + 10, 120); let result = disp.dispatch_dialog(true, 0x1D3, &mut cpu, &mut bus);
30326 assert!(result.unwrap().is_ok());
30327 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 12);
30328 assert_eq!(
30329 TrapDispatcher::te_text_bytes(&bus, te_handle),
30330 b"HELLO".to_vec()
30331 );
30332 let drew_any_pixel =
30333 (0..(row_bytes * 80)).any(|offset| bus.read_byte(screen_base + offset) != 0);
30334 assert!(
30335 drew_any_pixel,
30336 "TEUpdate should draw at least one non-white pixel for visible text"
30337 );
30338 }
30339
30340 #[test]
30341 fn teupdate_systemless_theme_routes_active_caret_through_provider() {
30342 let (mut classic, mut classic_cpu, mut classic_bus) = setup_with_port();
30343 let classic_te_handle = make_te_with_text(&mut classic, &mut classic_bus, b"");
30344 let classic_te_ptr = classic_bus.read_long(classic_te_handle);
30345 classic_bus.write_word(classic_te_ptr + TrapDispatcher::TE_ACTIVE_OFFSET, 1);
30346 classic_bus.write_word(classic_te_ptr + TrapDispatcher::TE_SEL_START_OFFSET, 0);
30347 classic_bus.write_word(classic_te_ptr + TrapDispatcher::TE_SEL_END_OFFSET, 0);
30348 let (screen_base, row_bytes, _screen_w, _screen_h, _pixel_size) = classic.screen_mode;
30349 for i in 0..(row_bytes * 80) {
30350 classic_bus.write_byte(screen_base + i, 0);
30351 }
30352 classic_bus.write_long(TEST_SP, classic_te_handle);
30353 classic_bus.write_word(TEST_SP + 4, 0);
30354 classic_bus.write_word(TEST_SP + 6, 0);
30355 classic_bus.write_word(TEST_SP + 8, 40);
30356 classic_bus.write_word(TEST_SP + 10, 120);
30357 let result = classic.dispatch_dialog(true, 0x1D3, &mut classic_cpu, &mut classic_bus);
30358 assert!(result.unwrap().is_ok());
30359
30360 let (mut themed, mut themed_cpu, mut themed_bus) = setup_with_port();
30361 themed.set_ui_theme_id(UiThemeId::SystemlessDefault);
30362 let themed_te_handle = make_te_with_text(&mut themed, &mut themed_bus, b"");
30363 let themed_te_ptr = themed_bus.read_long(themed_te_handle);
30364 themed_bus.write_word(themed_te_ptr + TrapDispatcher::TE_ACTIVE_OFFSET, 1);
30365 themed_bus.write_word(themed_te_ptr + TrapDispatcher::TE_SEL_START_OFFSET, 0);
30366 themed_bus.write_word(themed_te_ptr + TrapDispatcher::TE_SEL_END_OFFSET, 0);
30367 for i in 0..(row_bytes * 80) {
30368 themed_bus.write_byte(screen_base + i, 0);
30369 }
30370 themed_bus.write_long(TEST_SP, themed_te_handle);
30371 themed_bus.write_word(TEST_SP + 4, 0);
30372 themed_bus.write_word(TEST_SP + 6, 0);
30373 themed_bus.write_word(TEST_SP + 8, 40);
30374 themed_bus.write_word(TEST_SP + 10, 120);
30375 let result = themed.dispatch_dialog(true, 0x1D3, &mut themed_cpu, &mut themed_bus);
30376 assert!(result.unwrap().is_ok());
30377
30378 assert!(
30379 screen_pixel_is_set(&classic_bus, screen_base, row_bytes, 1, 0),
30380 "classic TEUpdate should draw a one-pixel caret for an active empty insertion point"
30381 );
30382 assert!(
30383 screen_pixel_is_set(&themed_bus, screen_base, row_bytes, 0, 0),
30384 "systemless-default TEUpdate should draw provider-owned caret chrome"
30385 );
30386 }
30387
30388 #[test]
30389 fn teactivate_and_tedeactivate_repaint_empty_insertion_caret() {
30390 let (mut disp, mut cpu, mut bus) = setup_with_port();
30394 let te_handle = make_te_with_text(&mut disp, &mut bus, b"");
30395 let te_ptr = bus.read_long(te_handle);
30396 let (screen_base, row_bytes, _screen_w, _screen_h, _pixel_size) = disp.screen_mode;
30397 for i in 0..(row_bytes * 80) {
30398 bus.write_byte(screen_base + i, 0);
30399 }
30400 bus.write_word(te_ptr + TrapDispatcher::TE_ACTIVE_OFFSET, 0);
30401 bus.write_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET, 0);
30402 bus.write_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET, 0);
30403
30404 cpu.write_reg(Register::A7, TEST_SP);
30405 bus.write_long(TEST_SP, te_handle);
30406 let result = disp.dispatch_dialog(true, 0x1D8, &mut cpu, &mut bus);
30407 assert!(result.unwrap().is_ok());
30408 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 4);
30409 assert_eq!(bus.read_word(te_ptr + TrapDispatcher::TE_ACTIVE_OFFSET), 1);
30410 assert_eq!(
30411 bus.read_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET),
30412 0
30413 );
30414 assert_eq!(bus.read_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET), 0);
30415 assert!(
30416 screen_pixel_is_set(&bus, screen_base, row_bytes, 1, 0),
30417 "TEActivate should paint the visible insertion caret"
30418 );
30419
30420 cpu.write_reg(Register::A7, TEST_SP);
30421 bus.write_long(TEST_SP, te_handle);
30422 let result = disp.dispatch_dialog(true, 0x1D9, &mut cpu, &mut bus);
30423 assert!(result.unwrap().is_ok());
30424 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 4);
30425 assert_eq!(bus.read_word(te_ptr + TrapDispatcher::TE_ACTIVE_OFFSET), 0);
30426 assert_eq!(
30427 bus.read_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET),
30428 0
30429 );
30430 assert_eq!(bus.read_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET), 0);
30431 assert!(
30432 !screen_pixel_is_set(&bus, screen_base, row_bytes, 1, 0),
30433 "TEDeactivate should erase the insertion caret"
30434 );
30435 }
30436
30437 #[test]
30438 fn teupdate_classic_active_selection_inverts_selected_text_range() {
30439 fn render_classic_selection(selected: bool) -> u64 {
30440 let (mut disp, mut cpu, mut bus) = setup_with_port();
30441 let te_handle = make_te_with_text(&mut disp, &mut bus, b"HELLO");
30442 let te_ptr = bus.read_long(te_handle);
30443 bus.write_word(te_ptr + TrapDispatcher::TE_ACTIVE_OFFSET, 1);
30444 bus.write_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET, 0);
30445 bus.write_word(
30446 te_ptr + TrapDispatcher::TE_SEL_END_OFFSET,
30447 if selected { 3 } else { 0 },
30448 );
30449 let line_height = bus.read_word(te_ptr + TrapDispatcher::TE_LINE_HEIGHT_OFFSET) as i16;
30450 let port = bus.read_long(te_ptr + TrapDispatcher::TE_IN_PORT_OFFSET);
30451 let base = bus.read_long(port + 2);
30452 let row_bytes = u32::from(bus.read_word(port + 6) & 0x3FFF);
30453 for i in 0..(row_bytes * 80) {
30454 bus.write_byte(base + i, 0);
30455 }
30456 bus.write_long(TEST_SP, te_handle);
30457 bus.write_word(TEST_SP + 4, 0);
30458 bus.write_word(TEST_SP + 6, 0);
30459 bus.write_word(TEST_SP + 8, 40);
30460 bus.write_word(TEST_SP + 10, 120);
30461 let result = disp.dispatch_dialog(true, 0x1D3, &mut cpu, &mut bus);
30462 assert!(result.unwrap().is_ok());
30463 sum_screen_bytes(&bus, base, row_bytes, 0, 0, line_height, 32)
30464 }
30465
30466 let selected = render_classic_selection(true);
30471 let unselected = render_classic_selection(false);
30472 assert!(
30473 selected != unselected,
30474 "classic TEUpdate should invert active non-empty selection pixels; selected={selected}, unselected={unselected}"
30475 );
30476 }
30477
30478 #[test]
30479 fn teupdate_systemless_theme_routes_multiline_selection_through_provider() {
30480 fn render_themed_multiline_selection(selected: bool) -> (u16, u16, i16, u64, u64) {
30481 let (mut disp, mut cpu, mut bus) = setup_with_port();
30482 disp.set_ui_theme_id(UiThemeId::SystemlessDefault);
30483 let te_handle = make_te_with_text(&mut disp, &mut bus, b"A\rB");
30484 let te_ptr = bus.read_long(te_handle);
30485 bus.write_word(te_ptr + TrapDispatcher::TE_ACTIVE_OFFSET, 1);
30486 bus.write_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET, 0);
30487 bus.write_word(
30488 te_ptr + TrapDispatcher::TE_SEL_END_OFFSET,
30489 if selected { 3 } else { 0 },
30490 );
30491 let line_height = bus.read_word(te_ptr + TrapDispatcher::TE_LINE_HEIGHT_OFFSET) as i16;
30492 let (base, row_bytes, _screen_w, _screen_h, _pixel_size) = disp.screen_mode;
30493 for i in 0..(row_bytes * 80) {
30494 bus.write_byte(base + i, 0);
30495 }
30496 bus.write_long(TEST_SP, te_handle);
30497 bus.write_word(TEST_SP + 4, 0);
30498 bus.write_word(TEST_SP + 6, 0);
30499 bus.write_word(TEST_SP + 8, 40);
30500 bus.write_word(TEST_SP + 10, 120);
30501 let result = disp.dispatch_dialog(true, 0x1D3, &mut cpu, &mut bus);
30502 assert!(result.unwrap().is_ok());
30503
30504 let first_line = sum_screen_bytes(&bus, base, row_bytes, 0, 0, line_height, 24);
30505 let second_line =
30506 sum_screen_bytes(&bus, base, row_bytes, line_height, 0, line_height * 2, 24);
30507 (
30508 bus.read_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET),
30509 bus.read_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET),
30510 line_height,
30511 first_line,
30512 second_line,
30513 )
30514 }
30515
30516 let selected = render_themed_multiline_selection(true);
30517 let unselected = render_themed_multiline_selection(false);
30518
30519 assert_eq!((selected.0, selected.1), (0, 3));
30523 assert_eq!((unselected.0, unselected.1), (0, 0));
30524 assert_eq!(selected.2, unselected.2);
30525 assert!(
30526 selected.4 != unselected.4,
30527 "systemless-default TEUpdate should draw provider selection chrome on the second selected line"
30528 );
30529 }
30530
30531 #[test]
30532 fn teupdate_systemless_theme_renders_reversed_selection_without_rewriting_offsets() {
30533 fn render_themed_selection(sel_start: u16, sel_end: u16) -> (u16, u16, u16, i16, u32, u32) {
30534 let (mut disp, mut cpu, mut bus) = setup_with_port();
30535 disp.set_ui_theme_id(UiThemeId::SystemlessDefault);
30536 let te_handle = make_te_with_text(&mut disp, &mut bus, b"A\rB");
30537 let te_ptr = bus.read_long(te_handle);
30538 bus.write_word(te_ptr + TrapDispatcher::TE_ACTIVE_OFFSET, 1);
30539 bus.write_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET, sel_start);
30540 bus.write_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET, sel_end);
30541 let line_height = bus.read_word(te_ptr + TrapDispatcher::TE_LINE_HEIGHT_OFFSET) as i16;
30542 let (base, row_bytes, _screen_w, _screen_h, _pixel_size) = disp.screen_mode;
30543 for i in 0..(row_bytes * 80) {
30544 bus.write_byte(base + i, 0);
30545 }
30546
30547 bus.write_long(TEST_SP, te_handle);
30548 bus.write_word(TEST_SP + 4, 0);
30549 bus.write_word(TEST_SP + 6, 0);
30550 bus.write_word(TEST_SP + 8, 40);
30551 bus.write_word(TEST_SP + 10, 120);
30552 let result = disp.dispatch_dialog(true, 0x1D3, &mut cpu, &mut bus);
30553 assert!(result.unwrap().is_ok());
30554 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 12);
30555
30556 let first_line_bottom_band =
30557 count_set_pixels(&bus, base, row_bytes, line_height - 2, 0, line_height, 24);
30558 let second_line_bottom_band = count_set_pixels(
30559 &bus,
30560 base,
30561 row_bytes,
30562 line_height * 2 - 2,
30563 0,
30564 line_height * 2,
30565 24,
30566 );
30567
30568 (
30569 bus.read_word(te_ptr + TrapDispatcher::TE_LENGTH_OFFSET),
30570 bus.read_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET),
30571 bus.read_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET),
30572 line_height,
30573 first_line_bottom_band,
30574 second_line_bottom_band,
30575 )
30576 }
30577
30578 let forward = render_themed_selection(0, 3);
30579 let reversed = render_themed_selection(3, 0);
30580
30581 assert_eq!(forward.0, 3);
30586 assert_eq!(reversed.0, 3);
30587 assert_eq!((forward.1, forward.2), (0, 3));
30588 assert_eq!((reversed.1, reversed.2), (3, 0));
30589 assert_eq!(forward.3, reversed.3);
30590 assert!(forward.4 > 0 && forward.5 > 0);
30591 assert_eq!(
30592 (reversed.4, reversed.5),
30593 (forward.4, forward.5),
30594 "systemless-default TEUpdate should render reversed selection ranges like their normalized range without rewriting selStart/selEnd"
30595 );
30596 }
30597
30598 #[test]
30599 fn teupdate_systemless_theme_routes_inactive_outline_selection_through_provider() {
30600 let (mut inactive_plain, mut inactive_plain_cpu, mut inactive_plain_bus) =
30601 setup_with_port();
30602 inactive_plain.set_ui_theme_id(UiThemeId::SystemlessDefault);
30603 let inactive_plain_te_handle =
30604 make_te_with_text(&mut inactive_plain, &mut inactive_plain_bus, b"HELLO");
30605 let inactive_plain_te_ptr = inactive_plain_bus.read_long(inactive_plain_te_handle);
30606 inactive_plain_bus.write_word(inactive_plain_te_ptr + TrapDispatcher::TE_ACTIVE_OFFSET, 0);
30607 inactive_plain_bus.write_word(
30608 inactive_plain_te_ptr + TrapDispatcher::TE_SEL_START_OFFSET,
30609 0,
30610 );
30611 inactive_plain_bus.write_word(inactive_plain_te_ptr + TrapDispatcher::TE_SEL_END_OFFSET, 3);
30612 let (plain_base, plain_row_bytes, _screen_w, _screen_h, _pixel_size) =
30613 inactive_plain.screen_mode;
30614 for i in 0..(plain_row_bytes * 80) {
30615 inactive_plain_bus.write_byte(plain_base + i, 0);
30616 }
30617 inactive_plain_bus.write_long(TEST_SP, inactive_plain_te_handle);
30618 inactive_plain_bus.write_word(TEST_SP + 4, 0);
30619 inactive_plain_bus.write_word(TEST_SP + 6, 0);
30620 inactive_plain_bus.write_word(TEST_SP + 8, 40);
30621 inactive_plain_bus.write_word(TEST_SP + 10, 120);
30622 let result = inactive_plain.dispatch_dialog(
30623 true,
30624 0x1D3,
30625 &mut inactive_plain_cpu,
30626 &mut inactive_plain_bus,
30627 );
30628 assert!(result.unwrap().is_ok());
30629
30630 let (mut inactive_outline, mut inactive_outline_cpu, mut inactive_outline_bus) =
30631 setup_with_port();
30632 inactive_outline.set_ui_theme_id(UiThemeId::SystemlessDefault);
30633 let inactive_outline_te_handle =
30634 make_te_with_text(&mut inactive_outline, &mut inactive_outline_bus, b"HELLO");
30635 let inactive_outline_te_ptr = inactive_outline_bus.read_long(inactive_outline_te_handle);
30636 inactive_outline_bus.write_word(
30637 inactive_outline_te_ptr + TrapDispatcher::TE_ACTIVE_OFFSET,
30638 0,
30639 );
30640 inactive_outline_bus.write_word(
30641 inactive_outline_te_ptr + TrapDispatcher::TE_SEL_START_OFFSET,
30642 0,
30643 );
30644 inactive_outline_bus.write_word(
30645 inactive_outline_te_ptr + TrapDispatcher::TE_SEL_END_OFFSET,
30646 3,
30647 );
30648 let inactive_line_height = inactive_outline_bus
30649 .read_word(inactive_outline_te_ptr + TrapDispatcher::TE_LINE_HEIGHT_OFFSET)
30650 as i16;
30651 let (outline_base, outline_row_bytes, _screen_w, _screen_h, _pixel_size) =
30652 inactive_outline.screen_mode;
30653 for i in 0..(outline_row_bytes * 80) {
30654 inactive_outline_bus.write_byte(outline_base + i, 0);
30655 }
30656
30657 inactive_outline_cpu.write_reg(Register::A7, TEST_SP);
30658 inactive_outline_bus.write_word(TEST_SP, 0x000E);
30659 inactive_outline_bus.write_long(TEST_SP + 2, inactive_outline_te_handle);
30660 inactive_outline_bus.write_word(TEST_SP + 6, TrapDispatcher::TE_BIT_SET as u16);
30661 inactive_outline_bus.write_word(TEST_SP + 8, TrapDispatcher::TE_FEATURE_OUTLINE_HILITE);
30662 inactive_outline_bus.write_word(TEST_SP + 10, 0xBEEF);
30663 let result = inactive_outline.dispatch_dialog(
30664 true,
30665 0x03D,
30666 &mut inactive_outline_cpu,
30667 &mut inactive_outline_bus,
30668 );
30669 assert!(result.unwrap().is_ok());
30670 assert_eq!(inactive_outline_bus.read_word(TEST_SP + 10), 0);
30671
30672 inactive_outline_cpu.write_reg(Register::A7, TEST_SP);
30673 inactive_outline_bus.write_long(TEST_SP, inactive_outline_te_handle);
30674 inactive_outline_bus.write_word(TEST_SP + 4, 0);
30675 inactive_outline_bus.write_word(TEST_SP + 6, 0);
30676 inactive_outline_bus.write_word(TEST_SP + 8, 40);
30677 inactive_outline_bus.write_word(TEST_SP + 10, 120);
30678 let result = inactive_outline.dispatch_dialog(
30679 true,
30680 0x1D3,
30681 &mut inactive_outline_cpu,
30682 &mut inactive_outline_bus,
30683 );
30684 assert!(result.unwrap().is_ok());
30685
30686 let (mut active, mut active_cpu, mut active_bus) = setup_with_port();
30687 active.set_ui_theme_id(UiThemeId::SystemlessDefault);
30688 let active_te_handle = make_te_with_text(&mut active, &mut active_bus, b"HELLO");
30689 let active_te_ptr = active_bus.read_long(active_te_handle);
30690 active_bus.write_word(active_te_ptr + TrapDispatcher::TE_ACTIVE_OFFSET, 1);
30691 active_bus.write_word(active_te_ptr + TrapDispatcher::TE_SEL_START_OFFSET, 0);
30692 active_bus.write_word(active_te_ptr + TrapDispatcher::TE_SEL_END_OFFSET, 3);
30693 let active_line_height =
30694 active_bus.read_word(active_te_ptr + TrapDispatcher::TE_LINE_HEIGHT_OFFSET) as i16;
30695 let (active_base, active_row_bytes, _screen_w, _screen_h, _pixel_size) = active.screen_mode;
30696 for i in 0..(active_row_bytes * 80) {
30697 active_bus.write_byte(active_base + i, 0);
30698 }
30699 active_bus.write_long(TEST_SP, active_te_handle);
30700 active_bus.write_word(TEST_SP + 4, 0);
30701 active_bus.write_word(TEST_SP + 6, 0);
30702 active_bus.write_word(TEST_SP + 8, 40);
30703 active_bus.write_word(TEST_SP + 10, 120);
30704 let result = active.dispatch_dialog(true, 0x1D3, &mut active_cpu, &mut active_bus);
30705 assert!(result.unwrap().is_ok());
30706
30707 let inactive_plain_top_band = count_set_pixels(
30710 &inactive_plain_bus,
30711 plain_base,
30712 plain_row_bytes,
30713 0,
30714 0,
30715 1,
30716 24,
30717 );
30718 let inactive_outline_top_band = count_set_pixels(
30719 &inactive_outline_bus,
30720 outline_base,
30721 outline_row_bytes,
30722 0,
30723 0,
30724 1,
30725 24,
30726 );
30727 assert_eq!(
30728 inactive_plain_top_band, 0,
30729 "inactive TextEdit selection without teFOutlineHilite should not draw provider selection chrome"
30730 );
30731 assert!(
30732 inactive_outline_top_band > 0,
30733 "inactive TextEdit selection with teFOutlineHilite should draw provider outline chrome"
30734 );
30735 assert_eq!(
30736 inactive_outline_bus
30737 .read_word(inactive_outline_te_ptr + TrapDispatcher::TE_SEL_START_OFFSET),
30738 0
30739 );
30740 assert_eq!(
30741 inactive_outline_bus
30742 .read_word(inactive_outline_te_ptr + TrapDispatcher::TE_SEL_END_OFFSET),
30743 3
30744 );
30745 assert_eq!(inactive_line_height, active_line_height);
30746
30747 let inactive_bottom_band = count_set_pixels(
30748 &inactive_outline_bus,
30749 outline_base,
30750 outline_row_bytes,
30751 inactive_line_height - 2,
30752 0,
30753 inactive_line_height,
30754 24,
30755 );
30756 let active_bottom_band = count_set_pixels(
30757 &active_bus,
30758 active_base,
30759 active_row_bytes,
30760 active_line_height - 2,
30761 0,
30762 active_line_height,
30763 24,
30764 );
30765 assert!(
30766 active_bottom_band > inactive_bottom_band,
30767 "inactive outline selection should stay outline-only instead of drawing active bottom-stripe chrome"
30768 );
30769 }
30770
30771 #[test]
30772 fn teupdate_c_rect_pointer_form_is_accepted_and_pops_eight_bytes() {
30773 let (mut disp, mut cpu, mut bus) = setup_with_port();
30776 let te_handle = make_te_with_text(&mut disp, &mut bus, b"HELLO");
30777 let rect_ptr = bus.alloc(8);
30778 bus.write_word(rect_ptr, 10);
30779 bus.write_word(rect_ptr + 2, 10);
30780 bus.write_word(rect_ptr + 4, 40);
30781 bus.write_word(rect_ptr + 6, 120);
30782
30783 bus.write_long(TEST_SP, te_handle);
30784 bus.write_long(TEST_SP + 4, rect_ptr);
30785
30786 let result = disp.dispatch_dialog(true, 0x1D3, &mut cpu, &mut bus);
30787 assert!(result.unwrap().is_ok());
30788 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 8);
30789 assert_eq!(
30790 TrapDispatcher::te_text_bytes(&bus, te_handle),
30791 b"HELLO".to_vec()
30792 );
30793 }
30794
30795 #[test]
30796 fn teupdate_preserves_te_record_state_across_redraw() {
30797 let (mut disp, mut cpu, mut bus) = setup_with_port();
30803 let te_handle = make_te_with_text(&mut disp, &mut bus, b"HELLO");
30804 let te_ptr = bus.read_long(te_handle);
30805 bus.write_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET, 1);
30806 bus.write_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET, 4);
30807
30808 let rect_ptr = bus.alloc(8);
30810 bus.write_word(rect_ptr, 10);
30811 bus.write_word(rect_ptr + 2, 10);
30812 bus.write_word(rect_ptr + 4, 40);
30813 bus.write_word(rect_ptr + 6, 120);
30814 bus.write_long(TEST_SP, te_handle);
30815 bus.write_long(TEST_SP + 4, rect_ptr);
30816 let result = disp.dispatch_dialog(true, 0x1D3, &mut cpu, &mut bus);
30817 assert!(result.unwrap().is_ok());
30818 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 8);
30819
30820 cpu.write_reg(Register::A7, TEST_SP);
30823 bus.write_long(TEST_SP, te_handle);
30824 bus.write_word(TEST_SP + 4, 10);
30825 bus.write_word(TEST_SP + 6, 10);
30826 bus.write_word(TEST_SP + 8, 40);
30827 bus.write_word(TEST_SP + 10, 120);
30828 let result = disp.dispatch_dialog(true, 0x1D3, &mut cpu, &mut bus);
30829 assert!(result.unwrap().is_ok());
30830 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 12);
30831
30832 assert_eq!(
30834 TrapDispatcher::te_text_bytes(&bus, te_handle),
30835 b"HELLO".to_vec()
30836 );
30837 assert_eq!(bus.read_word(te_ptr + TrapDispatcher::TE_LENGTH_OFFSET), 5);
30838 assert_eq!(
30839 bus.read_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET),
30840 1
30841 );
30842 assert_eq!(bus.read_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET), 4);
30843 }
30844
30845 #[test]
30846 fn teactivate_sets_active_flag_preserves_selection_and_pops_arg() {
30847 let (mut disp, mut cpu, mut bus) = setup();
30849 let te_handle = make_te_with_text(&mut disp, &mut bus, b"HELLO");
30850 let te_ptr = bus.read_long(te_handle);
30851 bus.write_word(te_ptr + TrapDispatcher::TE_ACTIVE_OFFSET, 0);
30852 bus.write_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET, 1);
30853 bus.write_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET, 4);
30854
30855 bus.write_long(TEST_SP, te_handle);
30856 let result = disp.dispatch_dialog(true, 0x1D8, &mut cpu, &mut bus);
30857 assert!(result.unwrap().is_ok());
30858 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 4);
30859 assert_eq!(bus.read_word(te_ptr + TrapDispatcher::TE_ACTIVE_OFFSET), 1);
30860 assert_eq!(
30861 bus.read_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET),
30862 1
30863 );
30864 assert_eq!(bus.read_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET), 4);
30865 }
30866
30867 #[test]
30868 fn tedeactivate_clears_active_flag_preserves_selection_and_pops_arg() {
30869 let (mut disp, mut cpu, mut bus) = setup();
30871 let te_handle = make_te_with_text(&mut disp, &mut bus, b"HELLO");
30872 let te_ptr = bus.read_long(te_handle);
30873 bus.write_word(te_ptr + TrapDispatcher::TE_ACTIVE_OFFSET, 1);
30874 bus.write_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET, 2);
30875 bus.write_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET, 5);
30876
30877 bus.write_long(TEST_SP, te_handle);
30878 let result = disp.dispatch_dialog(true, 0x1D9, &mut cpu, &mut bus);
30879 assert!(result.unwrap().is_ok());
30880 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 4);
30881 assert_eq!(bus.read_word(te_ptr + TrapDispatcher::TE_ACTIVE_OFFSET), 0);
30882 assert_eq!(
30883 bus.read_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET),
30884 2
30885 );
30886 assert_eq!(bus.read_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET), 5);
30887 }
30888
30889 #[test]
30890 fn teclick_consumes_point_extend_and_tehandle_arguments() {
30891 let (mut disp, mut cpu, mut bus) = setup();
30894 let te_handle = make_te_with_text(&mut disp, &mut bus, b"HELLO");
30895
30896 bus.write_long(TEST_SP, te_handle); bus.write_word(TEST_SP + 4, 0xFF00); bus.write_word(TEST_SP + 6, 18); bus.write_word(TEST_SP + 8, 27); let result = disp.dispatch_dialog(true, 0x1D4, &mut cpu, &mut bus);
30902 assert!(result.unwrap().is_ok());
30903 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 10);
30904 }
30905
30906 #[test]
30907 fn teclick_empty_text_keeps_insertion_point_at_zero() {
30908 let (mut disp, mut cpu, mut bus) = setup();
30912 let te_handle = make_te_with_text(&mut disp, &mut bus, b"");
30913 let te_ptr = bus.read_long(te_handle);
30914 bus.write_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET, 0);
30915 bus.write_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET, 0);
30916
30917 bus.write_long(TEST_SP, te_handle); bus.write_word(TEST_SP + 4, 0x0000); bus.write_word(TEST_SP + 6, 12); bus.write_word(TEST_SP + 8, 40); let result = disp.dispatch_dialog(true, 0x1D4, &mut cpu, &mut bus);
30923 assert!(result.unwrap().is_ok());
30924 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 10);
30925 assert_eq!(
30926 bus.read_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET),
30927 0
30928 );
30929 assert_eq!(bus.read_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET), 0);
30930 }
30931
30932 #[test]
30933 fn teclick_preserves_terec_active_flag_and_telength() {
30934 let (mut disp, mut cpu, mut bus) = setup();
30941 let te_handle = make_te_with_text(&mut disp, &mut bus, b"HELLO");
30942 let te_ptr = bus.read_long(te_handle);
30943 bus.write_word(te_ptr + TrapDispatcher::TE_ACTIVE_OFFSET, 0xFF);
30944 bus.write_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET, 2);
30945 bus.write_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET, 4);
30946 let pre_active = bus.read_word(te_ptr + TrapDispatcher::TE_ACTIVE_OFFSET);
30947 let pre_telength = bus.read_word(te_ptr + TrapDispatcher::TE_LENGTH_OFFSET);
30948
30949 bus.write_long(TEST_SP, te_handle);
30950 bus.write_word(TEST_SP + 4, 0x0000); bus.write_word(TEST_SP + 6, 8); bus.write_word(TEST_SP + 8, 32); let result = disp.dispatch_dialog(true, 0x1D4, &mut cpu, &mut bus);
30954 assert!(result.unwrap().is_ok());
30955 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 10);
30956 assert_eq!(
30957 bus.read_word(te_ptr + TrapDispatcher::TE_ACTIVE_OFFSET),
30958 pre_active
30959 );
30960 assert_eq!(
30961 bus.read_word(te_ptr + TrapDispatcher::TE_LENGTH_OFFSET),
30962 pre_telength
30963 );
30964 }
30965
30966 #[test]
30967 fn teclick_repeated_calls_balance_stack_no_drift() {
30968 let (mut disp, mut cpu, mut bus) = setup();
30974 let te_handle = make_te_with_text(&mut disp, &mut bus, b"HELLO");
30975
30976 for k in 0..8i16 {
30977 cpu.write_reg(Register::A7, TEST_SP);
30978 bus.write_long(TEST_SP, te_handle);
30979 bus.write_word(TEST_SP + 4, 0x0000); bus.write_word(TEST_SP + 6, (4 + k) as u16); bus.write_word(TEST_SP + 8, (16 + k * 3) as u16); let result = disp.dispatch_dialog(true, 0x1D4, &mut cpu, &mut bus);
30983 assert!(result.unwrap().is_ok());
30984 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 10);
30985 }
30986 }
30987
30988 fn textedit_selection_replacement_for_theme(
30989 theme_id: UiThemeId,
30990 ) -> (Vec<u8>, u16, u16, u16, u32) {
30991 let (mut disp, mut cpu, mut bus) = setup_with_port();
30992 disp.set_ui_theme_id(theme_id);
30993 let te_handle = make_te_with_text(&mut disp, &mut bus, b"HELLO");
30994 let te_ptr = bus.read_long(te_handle);
30995 bus.write_word(te_ptr + TrapDispatcher::TE_ACTIVE_OFFSET, 1);
30996 bus.write_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET, 1);
30997 bus.write_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET, 4);
30998
30999 bus.write_long(TEST_SP, te_handle);
31000 bus.write_word(TEST_SP + 4, 0);
31001 bus.write_word(TEST_SP + 6, 0);
31002 bus.write_word(TEST_SP + 8, 40);
31003 bus.write_word(TEST_SP + 10, 120);
31004 let result = disp.dispatch_dialog(true, 0x1D3, &mut cpu, &mut bus);
31005 assert!(result.unwrap().is_ok());
31006 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 12);
31007
31008 cpu.write_reg(Register::A7, TEST_SP);
31009 bus.write_long(TEST_SP, te_handle);
31010 bus.write_word(TEST_SP + 4, u16::from(b'Y'));
31011 let result = disp.dispatch_dialog(true, 0x1DC, &mut cpu, &mut bus);
31012 assert!(result.unwrap().is_ok());
31013
31014 (
31015 TrapDispatcher::te_text_bytes(&bus, te_handle),
31016 bus.read_word(te_ptr + TrapDispatcher::TE_LENGTH_OFFSET),
31017 bus.read_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET),
31018 bus.read_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET),
31019 cpu.read_reg(Register::A7),
31020 )
31021 }
31022
31023 fn textedit_private_scrap_editing_results_for_theme(
31024 theme_id: UiThemeId,
31025 ) -> TextEditPrivateScrapEditingSnapshot {
31026 let (mut disp, mut cpu, mut bus) = setup();
31027 disp.set_ui_theme_id(theme_id);
31028
31029 let copy_te = make_te_with_text(&mut disp, &mut bus, b"HELLO");
31030 let copy_ptr = bus.read_long(copy_te);
31031 bus.write_word(copy_ptr + TrapDispatcher::TE_SEL_START_OFFSET, 1);
31032 bus.write_word(copy_ptr + TrapDispatcher::TE_SEL_END_OFFSET, 4);
31033 cpu.write_reg(Register::A7, TEST_SP);
31034 bus.write_long(TEST_SP, copy_te);
31035 disp.dispatch_dialog(true, 0x1D5, &mut cpu, &mut bus)
31036 .unwrap()
31037 .unwrap();
31038 let copy = textedit_scrap_edit_case(&bus, copy_te, cpu.read_reg(Register::A7));
31039
31040 let cut_te = make_te_with_text(&mut disp, &mut bus, b"HELLO");
31041 let cut_ptr = bus.read_long(cut_te);
31042 bus.write_word(cut_ptr + TrapDispatcher::TE_SEL_START_OFFSET, 1);
31043 bus.write_word(cut_ptr + TrapDispatcher::TE_SEL_END_OFFSET, 4);
31044 cpu.write_reg(Register::A7, TEST_SP);
31045 bus.write_long(TEST_SP, cut_te);
31046 disp.dispatch_dialog(true, 0x1D6, &mut cpu, &mut bus)
31047 .unwrap()
31048 .unwrap();
31049 let cut = textedit_scrap_edit_case(&bus, cut_te, cpu.read_reg(Register::A7));
31050
31051 let paste_te = make_te_with_text(&mut disp, &mut bus, b"HEXXO");
31052 let paste_ptr = bus.read_long(paste_te);
31053 bus.write_word(paste_ptr + TrapDispatcher::TE_SEL_START_OFFSET, 2);
31054 bus.write_word(paste_ptr + TrapDispatcher::TE_SEL_END_OFFSET, 4);
31055 let paste_scrap = TrapDispatcher::allocate_handle_with_data(&mut bus, 2);
31056 let paste_scrap_ptr = bus.read_long(paste_scrap);
31057 bus.write_bytes(paste_scrap_ptr, b"LL");
31058 bus.write_long(crate::memory::globals::addr::TE_SCRP_HANDLE, paste_scrap);
31059 bus.write_word(crate::memory::globals::addr::TE_SCRP_LENGTH, 2);
31060 cpu.write_reg(Register::A7, TEST_SP);
31061 bus.write_long(TEST_SP, paste_te);
31062 disp.dispatch_dialog(true, 0x1DB, &mut cpu, &mut bus)
31063 .unwrap()
31064 .unwrap();
31065 let paste = textedit_scrap_edit_case(&bus, paste_te, cpu.read_reg(Register::A7));
31066
31067 let delete_te = make_te_with_text(&mut disp, &mut bus, b"HELLO");
31068 let delete_ptr = bus.read_long(delete_te);
31069 bus.write_word(delete_ptr + TrapDispatcher::TE_SEL_START_OFFSET, 1);
31070 bus.write_word(delete_ptr + TrapDispatcher::TE_SEL_END_OFFSET, 4);
31071 let delete_scrap = TrapDispatcher::allocate_handle_with_data(&mut bus, 2);
31072 let delete_scrap_ptr = bus.read_long(delete_scrap);
31073 bus.write_bytes(delete_scrap_ptr, b"QQ");
31074 bus.write_long(crate::memory::globals::addr::TE_SCRP_HANDLE, delete_scrap);
31075 bus.write_word(crate::memory::globals::addr::TE_SCRP_LENGTH, 2);
31076 cpu.write_reg(Register::A7, TEST_SP);
31077 bus.write_long(TEST_SP, delete_te);
31078 disp.dispatch_dialog(true, 0x1D7, &mut cpu, &mut bus)
31079 .unwrap()
31080 .unwrap();
31081 let delete = textedit_scrap_edit_case(&bus, delete_te, cpu.read_reg(Register::A7));
31082
31083 TextEditPrivateScrapEditingSnapshot {
31084 copy,
31085 cut,
31086 paste,
31087 delete,
31088 }
31089 }
31090
31091 #[test]
31092 fn systemless_theme_does_not_change_textedit_selection_offsets() {
31093 let classic = textedit_selection_replacement_for_theme(UiThemeId::ClassicSystem7);
31098 let themed = textedit_selection_replacement_for_theme(UiThemeId::SystemlessDefault);
31099
31100 assert_eq!(classic.0, b"HYO".to_vec());
31101 assert_eq!(classic.1, 3);
31102 assert_eq!(classic.2, 2);
31103 assert_eq!(classic.3, 2);
31104 assert_eq!(classic.4, TEST_SP + 6);
31105 assert_eq!(
31106 themed, classic,
31107 "systemless-default must not change TextEdit replacement selection offsets"
31108 );
31109 }
31110
31111 #[test]
31112 fn systemless_theme_does_not_change_textedit_private_scrap_editing() {
31113 let classic = textedit_private_scrap_editing_results_for_theme(UiThemeId::ClassicSystem7);
31120 let themed = textedit_private_scrap_editing_results_for_theme(UiThemeId::SystemlessDefault);
31121
31122 assert_eq!(classic.copy.text, b"HELLO".to_vec());
31123 assert_eq!(classic.copy.selection, (1, 4));
31124 assert_eq!(classic.copy.scrap_length, 3);
31125 assert_eq!(classic.copy.scrap_bytes, b"ELL".to_vec());
31126 assert_eq!(classic.copy.stack_after, TEST_SP + 4);
31127
31128 assert_eq!(classic.cut.text, b"HO".to_vec());
31129 assert_eq!(classic.cut.selection, (1, 1));
31130 assert_eq!(classic.cut.scrap_length, 3);
31131 assert_eq!(classic.cut.scrap_bytes, b"ELL".to_vec());
31132 assert_eq!(classic.cut.stack_after, TEST_SP + 4);
31133
31134 assert_eq!(classic.paste.text, b"HELLO".to_vec());
31135 assert_eq!(classic.paste.selection, (4, 4));
31136 assert_eq!(classic.paste.scrap_length, 2);
31137 assert_eq!(classic.paste.scrap_bytes, b"LL".to_vec());
31138 assert_eq!(classic.paste.stack_after, TEST_SP + 4);
31139
31140 assert_eq!(classic.delete.text, b"HO".to_vec());
31141 assert_eq!(classic.delete.selection, (1, 1));
31142 assert_eq!(classic.delete.scrap_length, 2);
31143 assert_eq!(classic.delete.scrap_bytes, b"QQ".to_vec());
31144 assert_eq!(classic.delete.stack_after, TEST_SP + 4);
31145 assert_eq!(
31146 themed, classic,
31147 "systemless-default must not change TextEdit private-scrap edit semantics"
31148 );
31149 }
31150
31151 #[test]
31152 fn teidle_consumes_tehandle_argument() {
31153 let (mut disp, mut cpu, mut bus) = setup();
31156 let te_handle = make_te_with_text(&mut disp, &mut bus, b"HELLO");
31157
31158 bus.write_long(TEST_SP, te_handle);
31159 let result = disp.dispatch_dialog(true, 0x1DA, &mut cpu, &mut bus);
31160 assert!(result.unwrap().is_ok());
31161 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 4);
31162 }
31163
31164 #[test]
31165 fn teidle_toggles_visible_insertion_caret_after_blink_interval() {
31166 let (mut disp, mut cpu, mut bus) = setup_with_port();
31170 let te_handle = make_te_with_text(&mut disp, &mut bus, b"");
31171 let te_ptr = bus.read_long(te_handle);
31172 let (screen_base, row_bytes, _screen_w, _screen_h, _pixel_size) = disp.screen_mode;
31173 for i in 0..(row_bytes * 80) {
31174 bus.write_byte(screen_base + i, 0);
31175 }
31176 bus.write_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET, 0);
31177 bus.write_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET, 0);
31178
31179 disp.tick_count = 100;
31180 bus.write_long(crate::memory::globals::addr::TICKS, 100);
31181 cpu.write_reg(Register::A7, TEST_SP);
31182 bus.write_long(TEST_SP, te_handle);
31183 let result = disp.dispatch_dialog(true, 0x1D8, &mut cpu, &mut bus);
31184 assert!(result.unwrap().is_ok());
31185 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 4);
31186 assert!(
31187 screen_pixel_is_set(&bus, screen_base, row_bytes, 1, 0),
31188 "TEActivate should show the insertion caret before TEIdle"
31189 );
31190 assert_eq!(
31191 bus.read_long(te_ptr + TrapDispatcher::TE_CARET_TIME_OFFSET),
31192 100
31193 );
31194 assert_eq!(
31195 bus.read_word(te_ptr + TrapDispatcher::TE_CARET_STATE_OFFSET),
31196 0
31197 );
31198
31199 disp.tick_count = 131;
31200 bus.write_long(crate::memory::globals::addr::TICKS, 131);
31201 cpu.write_reg(Register::A7, TEST_SP);
31202 bus.write_long(TEST_SP, te_handle);
31203 let result = disp.dispatch_dialog(true, 0x1DA, &mut cpu, &mut bus);
31204 assert!(result.unwrap().is_ok());
31205 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 4);
31206 assert!(
31207 screen_pixel_is_set(&bus, screen_base, row_bytes, 1, 0),
31208 "TEIdle before 32 ticks should leave the caret visible"
31209 );
31210 assert_eq!(
31211 bus.read_word(te_ptr + TrapDispatcher::TE_CARET_STATE_OFFSET),
31212 0
31213 );
31214
31215 disp.tick_count = 132;
31216 bus.write_long(crate::memory::globals::addr::TICKS, 132);
31217 cpu.write_reg(Register::A7, TEST_SP);
31218 bus.write_long(TEST_SP, te_handle);
31219 let result = disp.dispatch_dialog(true, 0x1DA, &mut cpu, &mut bus);
31220 assert!(result.unwrap().is_ok());
31221 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 4);
31222 assert!(
31223 !screen_pixel_is_set(&bus, screen_base, row_bytes, 1, 0),
31224 "TEIdle at the 32-tick boundary should hide the caret"
31225 );
31226 assert_eq!(
31227 bus.read_long(te_ptr + TrapDispatcher::TE_CARET_TIME_OFFSET),
31228 132
31229 );
31230 assert_eq!(
31231 bus.read_word(te_ptr + TrapDispatcher::TE_CARET_STATE_OFFSET),
31232 1
31233 );
31234
31235 disp.tick_count = 164;
31236 bus.write_long(crate::memory::globals::addr::TICKS, 164);
31237 cpu.write_reg(Register::A7, TEST_SP);
31238 bus.write_long(TEST_SP, te_handle);
31239 let result = disp.dispatch_dialog(true, 0x1DA, &mut cpu, &mut bus);
31240 assert!(result.unwrap().is_ok());
31241 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 4);
31242 assert!(
31243 screen_pixel_is_set(&bus, screen_base, row_bytes, 1, 0),
31244 "the next elapsed interval should show the caret again"
31245 );
31246 assert_eq!(
31247 bus.read_long(te_ptr + TrapDispatcher::TE_CARET_TIME_OFFSET),
31248 164
31249 );
31250 assert_eq!(
31251 bus.read_word(te_ptr + TrapDispatcher::TE_CARET_STATE_OFFSET),
31252 0
31253 );
31254 }
31255
31256 #[test]
31257 fn teidle_preserves_active_flag_and_selection_offsets() {
31258 let (mut disp, mut cpu, mut bus) = setup();
31263 let te_handle = make_te_with_text(&mut disp, &mut bus, b"HELLO");
31264 let te_ptr = bus.read_long(te_handle);
31265 bus.write_word(te_ptr + TrapDispatcher::TE_ACTIVE_OFFSET, 0xFF);
31266 bus.write_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET, 2);
31267 bus.write_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET, 4);
31268 let pre_telength = bus.read_word(te_ptr + TrapDispatcher::TE_LENGTH_OFFSET);
31269
31270 bus.write_long(TEST_SP, te_handle);
31271 let result = disp.dispatch_dialog(true, 0x1DA, &mut cpu, &mut bus);
31272 assert!(result.unwrap().is_ok());
31273 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 4);
31274 assert_eq!(
31275 bus.read_word(te_ptr + TrapDispatcher::TE_ACTIVE_OFFSET),
31276 0xFF
31277 );
31278 assert_eq!(
31279 bus.read_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET),
31280 2
31281 );
31282 assert_eq!(bus.read_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET), 4);
31283 assert_eq!(
31284 bus.read_word(te_ptr + TrapDispatcher::TE_LENGTH_OFFSET),
31285 pre_telength
31286 );
31287 }
31288
31289 #[test]
31290 fn teidle_repeated_calls_balance_stack_and_preserve_terec_state() {
31291 let (mut disp, mut cpu, mut bus) = setup();
31299 let te_handle = make_te_with_text(&mut disp, &mut bus, b"WORLD");
31300 let te_ptr = bus.read_long(te_handle);
31301 bus.write_word(te_ptr + TrapDispatcher::TE_ACTIVE_OFFSET, 0xFF);
31302 bus.write_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET, 1);
31303 bus.write_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET, 3);
31304 let pre_active = bus.read_word(te_ptr + TrapDispatcher::TE_ACTIVE_OFFSET);
31305 let pre_sel_start = bus.read_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET);
31306 let pre_sel_end = bus.read_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET);
31307 let pre_telength = bus.read_word(te_ptr + TrapDispatcher::TE_LENGTH_OFFSET);
31308
31309 for _ in 0..8 {
31310 cpu.write_reg(Register::A7, TEST_SP);
31311 bus.write_long(TEST_SP, te_handle);
31312 let result = disp.dispatch_dialog(true, 0x1DA, &mut cpu, &mut bus);
31313 assert!(result.unwrap().is_ok());
31314 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 4);
31315 }
31316
31317 assert_eq!(
31318 bus.read_word(te_ptr + TrapDispatcher::TE_ACTIVE_OFFSET),
31319 pre_active
31320 );
31321 assert_eq!(
31322 bus.read_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET),
31323 pre_sel_start
31324 );
31325 assert_eq!(
31326 bus.read_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET),
31327 pre_sel_end
31328 );
31329 assert_eq!(
31330 bus.read_word(te_ptr + TrapDispatcher::TE_LENGTH_OFFSET),
31331 pre_telength
31332 );
31333 }
31334
31335 #[test]
31336 fn tescroll_offsets_destrect_by_requested_delta_and_pops_args() {
31337 let (mut disp, mut cpu, mut bus) = setup_with_port();
31339 let te_handle = make_te_with_text(&mut disp, &mut bus, b"HELLO");
31340 let te_ptr = bus.read_long(te_handle);
31341 bus.write_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET, 10);
31342 bus.write_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET + 2, 20);
31343 bus.write_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET + 4, 70);
31344 bus.write_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET + 6, 180);
31345
31346 bus.write_long(TEST_SP, te_handle);
31347 bus.write_word(TEST_SP + 4, (-3i16) as u16); bus.write_word(TEST_SP + 6, 5); let result = disp.dispatch_dialog(true, 0x1DD, &mut cpu, &mut bus);
31350 assert!(result.unwrap().is_ok());
31351 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 8);
31352 assert_eq!(
31353 bus.read_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET),
31354 7
31355 );
31356 assert_eq!(
31357 bus.read_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET + 2),
31358 25
31359 );
31360 assert_eq!(
31361 bus.read_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET + 4),
31362 67
31363 );
31364 assert_eq!(
31365 bus.read_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET + 6),
31366 185
31367 );
31368 }
31369
31370 #[test]
31371 fn teinsert_inserts_before_selection_shifts_range_and_preserves_scrap() {
31372 let (mut disp, mut cpu, mut bus) = setup();
31379 let existing_scrap = TrapDispatcher::allocate_handle_with_data(&mut bus, 2);
31380 let existing_scrap_ptr = bus.read_long(existing_scrap);
31381 bus.write_bytes(existing_scrap_ptr, b"QQ");
31382 bus.write_long(crate::memory::globals::addr::TE_SCRP_HANDLE, existing_scrap);
31383 bus.write_word(crate::memory::globals::addr::TE_SCRP_LENGTH, 2);
31384
31385 let te_handle = make_te_with_text(&mut disp, &mut bus, b"HELLO");
31386 let te_ptr = bus.read_long(te_handle);
31387 bus.write_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET, 1);
31388 bus.write_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET, 4);
31389
31390 let insert_ptr = bus.alloc(3);
31391 bus.write_bytes(insert_ptr, b"XYZ");
31392 bus.write_long(TEST_SP, te_handle);
31393 bus.write_long(TEST_SP + 4, 3);
31394 bus.write_long(TEST_SP + 8, insert_ptr);
31395 let result = disp.dispatch_dialog(true, 0x1DE, &mut cpu, &mut bus);
31396 assert!(result.unwrap().is_ok());
31397 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 12);
31398
31399 assert_eq!(
31400 TrapDispatcher::te_text_bytes(&bus, te_handle),
31401 b"HXYZELLO".to_vec()
31402 );
31403 assert_eq!(
31404 bus.read_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET),
31405 4
31406 );
31407 assert_eq!(bus.read_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET), 7);
31408 assert_eq!(
31409 bus.read_long(crate::memory::globals::addr::TE_SCRP_HANDLE),
31410 existing_scrap
31411 );
31412 assert_eq!(
31413 bus.read_word(crate::memory::globals::addr::TE_SCRP_LENGTH),
31414 2
31415 );
31416 assert_eq!(bus.read_bytes(existing_scrap_ptr, 2), b"QQ".to_vec());
31417 }
31418
31419 #[test]
31420 fn teinsert_insertion_point_inserts_before_caret_and_shifts_caret() {
31421 let (mut disp, mut cpu, mut bus) = setup();
31427 let te_handle = make_te_with_text(&mut disp, &mut bus, b"HELLO");
31428 let te_ptr = bus.read_long(te_handle);
31429 bus.write_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET, 2);
31430 bus.write_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET, 2);
31431
31432 let insert_ptr = bus.alloc(1);
31433 bus.write_byte(insert_ptr, b'X');
31434 bus.write_long(TEST_SP, te_handle);
31435 bus.write_long(TEST_SP + 4, 1);
31436 bus.write_long(TEST_SP + 8, insert_ptr);
31437 let result = disp.dispatch_dialog(true, 0x1DE, &mut cpu, &mut bus);
31438 assert!(result.unwrap().is_ok());
31439 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 12);
31440
31441 assert_eq!(
31442 TrapDispatcher::te_text_bytes(&bus, te_handle),
31443 b"HEXLLO".to_vec()
31444 );
31445 assert_eq!(
31446 bus.read_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET),
31447 3
31448 );
31449 assert_eq!(bus.read_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET), 3);
31450 }
31451
31452 #[test]
31453 fn tesetalignment_writes_just_field_and_pops_args() {
31454 let (mut disp, mut cpu, mut bus) = setup();
31456 let te_handle = make_te_with_text(&mut disp, &mut bus, b"HELLO");
31457 let te_ptr = bus.read_long(te_handle);
31458 bus.write_word(te_ptr + TrapDispatcher::TE_JUST_OFFSET, 0);
31459
31460 bus.write_long(TEST_SP, te_handle);
31461 bus.write_word(TEST_SP + 4, (-1i16) as u16);
31462 let result = disp.dispatch_dialog(true, 0x1DF, &mut cpu, &mut bus);
31463 assert!(result.unwrap().is_ok());
31464 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 6);
31465 assert_eq!(
31466 bus.read_word(te_ptr + TrapDispatcher::TE_JUST_OFFSET) as i16,
31467 -1
31468 );
31469 }
31470
31471 #[test]
31474 fn tecopy_nonempty_selection_updates_textedit_scrap_globals() {
31475 let (mut disp, mut cpu, mut bus) = setup();
31477 let te_handle = make_te_with_text(&mut disp, &mut bus, b"HELLO");
31478 let te_ptr = bus.read_long(te_handle);
31479 bus.write_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET, 1);
31480 bus.write_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET, 4);
31481 bus.write_long(TEST_SP, te_handle);
31482
31483 let result = disp.dispatch_dialog(true, 0x1D5, &mut cpu, &mut bus);
31484 assert!(result.unwrap().is_ok());
31485 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 4);
31486
31487 let scrap_handle = bus.read_long(crate::memory::globals::addr::TE_SCRP_HANDLE);
31488 let scrap_ptr = bus.read_long(scrap_handle);
31489 assert_ne!(scrap_handle, 0);
31490 assert_ne!(scrap_ptr, 0);
31491 assert_eq!(
31492 bus.read_word(crate::memory::globals::addr::TE_SCRP_LENGTH),
31493 3
31494 );
31495 assert_eq!(bus.read_bytes(scrap_ptr, 3), b"ELL".to_vec());
31496 }
31497
31498 #[test]
31499 fn tecopy_empty_selection_preserves_existing_scrap_contents() {
31500 let (mut disp, mut cpu, mut bus) = setup();
31502 let existing_scrap = TrapDispatcher::allocate_handle_with_data(&mut bus, 2);
31503 let existing_ptr = bus.read_long(existing_scrap);
31504 bus.write_bytes(existing_ptr, b"ZZ");
31505 bus.write_long(crate::memory::globals::addr::TE_SCRP_HANDLE, existing_scrap);
31506 bus.write_word(crate::memory::globals::addr::TE_SCRP_LENGTH, 2);
31507
31508 let te_handle = make_te_with_text(&mut disp, &mut bus, b"HELLO");
31509 let te_ptr = bus.read_long(te_handle);
31510 bus.write_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET, 2);
31511 bus.write_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET, 2);
31512 bus.write_long(TEST_SP, te_handle);
31513
31514 let result = disp.dispatch_dialog(true, 0x1D5, &mut cpu, &mut bus);
31515 assert!(result.unwrap().is_ok());
31516 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 4);
31517 assert_eq!(
31518 bus.read_long(crate::memory::globals::addr::TE_SCRP_HANDLE),
31519 existing_scrap
31520 );
31521 assert_eq!(
31522 bus.read_word(crate::memory::globals::addr::TE_SCRP_LENGTH),
31523 2
31524 );
31525 assert_eq!(bus.read_bytes(existing_ptr, 2), b"ZZ".to_vec());
31526 }
31527
31528 #[test]
31529 fn tecut_nonempty_selection_copies_to_scrap_and_deletes_selected_text() {
31530 let (mut disp, mut cpu, mut bus) = setup();
31532 let te_handle = make_te_with_text(&mut disp, &mut bus, b"HELLO");
31533 let te_ptr = bus.read_long(te_handle);
31534 bus.write_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET, 1);
31535 bus.write_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET, 4);
31536 bus.write_long(TEST_SP, te_handle);
31537
31538 let result = disp.dispatch_dialog(true, 0x1D6, &mut cpu, &mut bus);
31539 assert!(result.unwrap().is_ok());
31540 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 4);
31541 assert_eq!(
31542 TrapDispatcher::te_text_bytes(&bus, te_handle),
31543 b"HO".to_vec()
31544 );
31545 assert_eq!(
31546 bus.read_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET),
31547 1
31548 );
31549 assert_eq!(bus.read_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET), 1);
31550
31551 let scrap_handle = bus.read_long(crate::memory::globals::addr::TE_SCRP_HANDLE);
31552 let scrap_ptr = bus.read_long(scrap_handle);
31553 assert_eq!(
31554 bus.read_word(crate::memory::globals::addr::TE_SCRP_LENGTH),
31555 3
31556 );
31557 assert_eq!(bus.read_bytes(scrap_ptr, 3), b"ELL".to_vec());
31558 }
31559
31560 #[test]
31561 fn tecut_empty_selection_preserves_text_and_scrap_contents() {
31562 let (mut disp, mut cpu, mut bus) = setup();
31564 let existing_scrap = TrapDispatcher::allocate_handle_with_data(&mut bus, 2);
31565 let existing_ptr = bus.read_long(existing_scrap);
31566 bus.write_bytes(existing_ptr, b"QQ");
31567 bus.write_long(crate::memory::globals::addr::TE_SCRP_HANDLE, existing_scrap);
31568 bus.write_word(crate::memory::globals::addr::TE_SCRP_LENGTH, 2);
31569
31570 let te_handle = make_te_with_text(&mut disp, &mut bus, b"HELLO");
31571 let te_ptr = bus.read_long(te_handle);
31572 bus.write_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET, 3);
31573 bus.write_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET, 3);
31574 bus.write_long(TEST_SP, te_handle);
31575
31576 let result = disp.dispatch_dialog(true, 0x1D6, &mut cpu, &mut bus);
31577 assert!(result.unwrap().is_ok());
31578 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 4);
31579 assert_eq!(
31580 TrapDispatcher::te_text_bytes(&bus, te_handle),
31581 b"HELLO".to_vec()
31582 );
31583 assert_eq!(
31584 bus.read_long(crate::memory::globals::addr::TE_SCRP_HANDLE),
31585 existing_scrap
31586 );
31587 assert_eq!(
31588 bus.read_word(crate::memory::globals::addr::TE_SCRP_LENGTH),
31589 2
31590 );
31591 assert_eq!(bus.read_bytes(existing_ptr, 2), b"QQ".to_vec());
31592 }
31593
31594 #[test]
31595 fn tepaste_nonempty_scrap_replaces_selection_and_advances_insertion_point() {
31596 let (mut disp, mut cpu, mut bus) = setup();
31598 let te_handle = make_te_with_text(&mut disp, &mut bus, b"HLO");
31599 let te_ptr = bus.read_long(te_handle);
31600 bus.write_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET, 1);
31601 bus.write_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET, 1);
31602
31603 let scrap_handle = TrapDispatcher::allocate_handle_with_data(&mut bus, 2);
31604 let scrap_ptr = bus.read_long(scrap_handle);
31605 bus.write_bytes(scrap_ptr, b"EL");
31606 bus.write_long(crate::memory::globals::addr::TE_SCRP_HANDLE, scrap_handle);
31607 bus.write_word(crate::memory::globals::addr::TE_SCRP_LENGTH, 2);
31608
31609 bus.write_long(TEST_SP, te_handle);
31610 let result = disp.dispatch_dialog(true, 0x1DB, &mut cpu, &mut bus);
31611 assert!(result.unwrap().is_ok());
31612 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 4);
31613 assert_eq!(
31614 TrapDispatcher::te_text_bytes(&bus, te_handle),
31615 b"HELLO".to_vec()
31616 );
31617 assert_eq!(
31618 bus.read_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET),
31619 3
31620 );
31621 assert_eq!(bus.read_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET), 3);
31622 }
31623
31624 #[test]
31625 fn tepaste_empty_scrap_is_noop() {
31626 let (mut disp, mut cpu, mut bus) = setup();
31628 let te_handle = make_te_with_text(&mut disp, &mut bus, b"HELLO");
31629 let te_ptr = bus.read_long(te_handle);
31630 bus.write_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET, 1);
31631 bus.write_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET, 3);
31632
31633 let scrap_handle = TrapDispatcher::allocate_handle_with_data(&mut bus, 2);
31634 let scrap_ptr = bus.read_long(scrap_handle);
31635 bus.write_bytes(scrap_ptr, b"ZZ");
31636 bus.write_long(crate::memory::globals::addr::TE_SCRP_HANDLE, scrap_handle);
31637 bus.write_word(crate::memory::globals::addr::TE_SCRP_LENGTH, 0);
31638
31639 bus.write_long(TEST_SP, te_handle);
31640 let result = disp.dispatch_dialog(true, 0x1DB, &mut cpu, &mut bus);
31641 assert!(result.unwrap().is_ok());
31642 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 4);
31643 assert_eq!(
31644 TrapDispatcher::te_text_bytes(&bus, te_handle),
31645 b"HELLO".to_vec()
31646 );
31647 assert_eq!(
31648 bus.read_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET),
31649 1
31650 );
31651 assert_eq!(bus.read_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET), 3);
31652 }
31653
31654 #[test]
31655 fn te_new_initializes_basic_record_fields() {
31656 let (mut disp, mut cpu, mut bus) = setup();
31657
31658 bus.write_word(TEST_SP, 0); bus.write_word(TEST_SP + 2, 0); bus.write_word(TEST_SP + 4, 100); bus.write_word(TEST_SP + 6, 120); bus.write_word(TEST_SP + 8, 10); bus.write_word(TEST_SP + 10, 20); bus.write_word(TEST_SP + 12, 90); bus.write_word(TEST_SP + 14, 80); let result = disp.dispatch_dialog(true, 0x1D2, &mut cpu, &mut bus);
31668 assert!(result.unwrap().is_ok());
31669 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 16);
31670
31671 let te_handle = bus.read_long(TEST_SP + 16);
31672 let te_ptr = bus.read_long(te_handle);
31673 assert_eq!(
31674 bus.read_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET),
31675 10
31676 );
31677 assert_eq!(
31678 bus.read_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET + 2),
31679 20
31680 );
31681 assert_eq!(
31682 bus.read_word(te_ptr + TrapDispatcher::TE_VIEW_RECT_OFFSET + 4),
31683 100
31684 );
31685 assert_eq!(
31686 bus.read_word(te_ptr + TrapDispatcher::TE_VIEW_RECT_OFFSET + 6),
31687 120
31688 );
31689 assert_eq!(bus.read_word(te_ptr + TrapDispatcher::TE_LENGTH_OFFSET), 0);
31690 let h_text = bus.read_long(te_ptr + TrapDispatcher::TE_HTEXT_OFFSET);
31691 assert_ne!(h_text, 0);
31692 assert_eq!(bus.read_long(h_text), 0);
31693 }
31694
31695 #[test]
31699 fn testylenew_returns_styled_handle_and_initializes_sentinel_fields() {
31700 let (mut disp, mut cpu, mut bus) = setup();
31701
31702 bus.write_word(TEST_SP, 0); bus.write_word(TEST_SP + 2, 0); bus.write_word(TEST_SP + 4, 100); bus.write_word(TEST_SP + 6, 120); bus.write_word(TEST_SP + 8, 10); bus.write_word(TEST_SP + 10, 20); bus.write_word(TEST_SP + 12, 90); bus.write_word(TEST_SP + 14, 80); let result = disp.dispatch_dialog(true, 0x03E, &mut cpu, &mut bus);
31712 assert!(result.unwrap().is_ok());
31713 assert_eq!(
31714 cpu.read_reg(Register::A7),
31715 TEST_SP + 16,
31716 "TEStyleNew should pop two Rect arguments (16 bytes)"
31717 );
31718
31719 let te_handle = bus.read_long(TEST_SP + 16);
31720 assert_ne!(te_handle, 0, "TEStyleNew should return a non-NIL TEHandle");
31721
31722 let te_ptr = bus.read_long(te_handle);
31723 assert_ne!(te_ptr, 0, "returned TEHandle should point to a TERec");
31724 assert_eq!(
31725 bus.read_word(te_ptr + TrapDispatcher::TE_TX_SIZE_OFFSET),
31726 0xFFFF
31727 );
31728 assert_eq!(
31729 bus.read_word(te_ptr + TrapDispatcher::TE_LINE_HEIGHT_OFFSET),
31730 0xFFFF
31731 );
31732 assert_eq!(
31733 bus.read_word(te_ptr + TrapDispatcher::TE_FONT_ASCENT_OFFSET),
31734 0xFFFF
31735 );
31736
31737 let style_handle = bus.read_long(te_ptr + TrapDispatcher::TE_TX_FONT_OFFSET);
31738 assert_ne!(style_handle, 0, "styled record should carry a style handle");
31739 let style_ptr = bus.read_long(style_handle);
31740 assert_ne!(
31741 style_ptr, 0,
31742 "style handle should dereference to a style record"
31743 );
31744 assert_ne!(
31745 bus.read_long(style_ptr + TrapDispatcher::TE_STYLE_NULL_STYLE_OFFSET),
31746 0,
31747 "style record should include a non-NIL null-style handle"
31748 );
31749 }
31750
31751 #[test]
31759 fn testylenew_pointer_arg_convention_initializes_destrect_viewrect_and_styled_sentinels() {
31760 let (mut disp, mut cpu, mut bus) = setup();
31761
31762 let dest_rect_ptr: u32 = 0x1A0000;
31764 let view_rect_ptr: u32 = 0x1A0010;
31765 bus.write_word(dest_rect_ptr, (-1200_i16) as u16);
31766 bus.write_word(dest_rect_ptr + 2, (-1200_i16) as u16);
31767 bus.write_word(dest_rect_ptr + 4, (-900_i16) as u16);
31768 bus.write_word(dest_rect_ptr + 6, (-1000_i16) as u16);
31769 bus.write_word(view_rect_ptr, (-1100_i16) as u16);
31770 bus.write_word(view_rect_ptr + 2, (-1300_i16) as u16);
31771 bus.write_word(view_rect_ptr + 4, (-800_i16) as u16);
31772 bus.write_word(view_rect_ptr + 6, (-900_i16) as u16);
31773
31774 bus.write_long(TEST_SP, view_rect_ptr);
31777 bus.write_long(TEST_SP + 4, dest_rect_ptr);
31778
31779 let result = disp.dispatch_dialog(true, 0x03E, &mut cpu, &mut bus);
31780 assert!(result.unwrap().is_ok());
31781 assert_eq!(
31782 cpu.read_reg(Register::A7),
31783 TEST_SP + 8,
31784 "TEStyleNew pointer convention should pop 8 bytes (two pointer args)"
31785 );
31786
31787 let te_handle = bus.read_long(TEST_SP + 8);
31788 assert_ne!(te_handle, 0, "TEStyleNew should return a non-NIL TEHandle");
31789 let te_ptr = bus.read_long(te_handle);
31790 assert_ne!(te_ptr, 0, "TEHandle should dereference to a TERec");
31791
31792 assert_eq!(
31794 bus.read_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET) as i16,
31795 -1200,
31796 "destRect.top must round-trip from caller-supplied destRect_ptr"
31797 );
31798 assert_eq!(
31799 bus.read_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET + 2) as i16,
31800 -1200,
31801 "destRect.left must round-trip from caller-supplied destRect_ptr"
31802 );
31803 assert_eq!(
31804 bus.read_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET + 4) as i16,
31805 -900,
31806 "destRect.bottom must round-trip from caller-supplied destRect_ptr"
31807 );
31808 assert_eq!(
31809 bus.read_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET + 6) as i16,
31810 -1000,
31811 "destRect.right must round-trip from caller-supplied destRect_ptr"
31812 );
31813
31814 assert_eq!(
31816 bus.read_word(te_ptr + TrapDispatcher::TE_VIEW_RECT_OFFSET) as i16,
31817 -1100,
31818 "viewRect.top must round-trip from caller-supplied viewRect_ptr"
31819 );
31820 assert_eq!(
31821 bus.read_word(te_ptr + TrapDispatcher::TE_VIEW_RECT_OFFSET + 2) as i16,
31822 -1300,
31823 "viewRect.left must round-trip from caller-supplied viewRect_ptr"
31824 );
31825 assert_eq!(
31826 bus.read_word(te_ptr + TrapDispatcher::TE_VIEW_RECT_OFFSET + 4) as i16,
31827 -800,
31828 "viewRect.bottom must round-trip from caller-supplied viewRect_ptr"
31829 );
31830 assert_eq!(
31831 bus.read_word(te_ptr + TrapDispatcher::TE_VIEW_RECT_OFFSET + 6) as i16,
31832 -900,
31833 "viewRect.right must round-trip from caller-supplied viewRect_ptr"
31834 );
31835
31836 assert_eq!(
31838 bus.read_word(te_ptr + TrapDispatcher::TE_TX_SIZE_OFFSET),
31839 0xFFFF,
31840 "TEStyleNew must set txSize to -1 (styled-record sentinel)"
31841 );
31842 assert_eq!(
31843 bus.read_word(te_ptr + TrapDispatcher::TE_LINE_HEIGHT_OFFSET),
31844 0xFFFF,
31845 "TEStyleNew must set lineHeight to -1 (styled-record sentinel)"
31846 );
31847 assert_eq!(
31848 bus.read_word(te_ptr + TrapDispatcher::TE_FONT_ASCENT_OFFSET),
31849 0xFFFF,
31850 "TEStyleNew must set fontAscent to -1 (styled-record sentinel)"
31851 );
31852
31853 let style_handle = bus.read_long(te_ptr + TrapDispatcher::TE_TX_FONT_OFFSET);
31855 assert_ne!(
31856 style_handle, 0,
31857 "TEStyleNew must install a non-NIL TEStyleHandle in the txFont/txFace overlay"
31858 );
31859 let style_ptr = bus.read_long(style_handle);
31860 assert_ne!(
31861 style_ptr, 0,
31862 "TEStyleHandle should dereference to an allocated TEStyleRec"
31863 );
31864 }
31865
31866 #[test]
31872 fn testylenew_function_protocol_consumes_two_pointer_args_and_writes_4_byte_result() {
31873 let (mut disp, mut cpu, mut bus) = setup();
31874
31875 let dest_rect_ptr: u32 = 0x1A0020;
31876 let view_rect_ptr: u32 = 0x1A0030;
31877 bus.write_word(dest_rect_ptr, (-500_i16) as u16);
31880 bus.write_word(dest_rect_ptr + 2, (-500_i16) as u16);
31881 bus.write_word(dest_rect_ptr + 4, (-300_i16) as u16);
31882 bus.write_word(dest_rect_ptr + 6, (-400_i16) as u16);
31883 bus.write_word(view_rect_ptr, (-450_i16) as u16);
31884 bus.write_word(view_rect_ptr + 2, (-550_i16) as u16);
31885 bus.write_word(view_rect_ptr + 4, (-250_i16) as u16);
31886 bus.write_word(view_rect_ptr + 6, (-350_i16) as u16);
31887
31888 bus.write_long(TEST_SP, view_rect_ptr);
31889 bus.write_long(TEST_SP + 4, dest_rect_ptr);
31890 bus.write_long(TEST_SP + 8, 0xDEADBEEF);
31892 bus.write_long(TEST_SP + 12, 0xCAFEBABE);
31893
31894 let result = disp.dispatch_dialog(true, 0x03E, &mut cpu, &mut bus);
31895 assert!(result.unwrap().is_ok());
31896
31897 assert_eq!(
31899 cpu.read_reg(Register::A7),
31900 TEST_SP + 8,
31901 "TEStyleNew pointer-arg convention must pop exactly 8 bytes"
31902 );
31903
31904 let te_handle = bus.read_long(TEST_SP + 8);
31907 assert_ne!(
31908 te_handle, 0xDEADBEEF,
31909 "TEStyleNew result slot must be overwritten by a real TEHandle, not the poison sentinel"
31910 );
31911 assert_ne!(te_handle, 0, "TEStyleNew result must be a non-NIL TEHandle");
31912
31913 assert_eq!(
31916 bus.read_long(TEST_SP + 12),
31917 0xCAFEBABE,
31918 "TEStyleNew must not write past the 4-byte function-result slot"
31919 );
31920 }
31921
31922 #[test]
31923 fn testyleinsert_clears_exhausted_nul_continuation() {
31924 let (mut disp, mut cpu, mut bus) = setup();
31925 let text_ptr = bus.alloc(1);
31926 bus.write_byte(text_ptr, 0);
31927 cpu.write_reg(Register::A3, text_ptr);
31928
31929 bus.write_word(TEST_SP, 0x0007);
31930 bus.write_long(TEST_SP + 2, 0);
31931 bus.write_long(TEST_SP + 6, 0);
31932 bus.write_long(TEST_SP + 10, 0);
31933 bus.write_long(TEST_SP + 14, text_ptr);
31934
31935 let result = disp.dispatch_dialog(true, 0x03D, &mut cpu, &mut bus);
31936 assert!(result.unwrap().is_ok());
31937 assert_eq!(cpu.read_reg(Register::A3), 0);
31938 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 18);
31939 }
31940
31941 #[test]
31942 fn testyleinsert_applies_style_scrap_runs_and_line_metrics() {
31943 let (mut disp, mut cpu, mut bus) = setup();
31944
31945 disp.tx_font = 0;
31946 disp.tx_face = 0;
31947 disp.tx_size = 12;
31948 let te_handle = TrapDispatcher::allocate_te_handle(&mut bus);
31949 disp.initialize_styled_te_record(&mut bus, te_handle, (0, 0, 80, 200), (0, 0, 80, 200));
31950
31951 let text = b"small\rLARGE";
31952 let text_ptr = bus.alloc(text.len() as u32);
31953 bus.write_bytes(text_ptr, text);
31954 let style_scrap = make_style_scrap(
31955 &mut bus,
31956 &[
31957 (0, 10, 8, 0, 1, 9, (0, 0, 0)),
31958 (6, 14, 11, 0, 0, 12, (0x1111, 0x2222, 0x3333)),
31959 ],
31960 );
31961
31962 bus.write_word(TEST_SP, 0x0007);
31963 bus.write_long(TEST_SP + 2, te_handle);
31964 bus.write_long(TEST_SP + 6, style_scrap);
31965 bus.write_long(TEST_SP + 10, text.len() as u32);
31966 bus.write_long(TEST_SP + 14, text_ptr);
31967
31968 let result = disp.dispatch_dialog(true, 0x03D, &mut cpu, &mut bus);
31969 assert!(result.unwrap().is_ok());
31970 assert_eq!(
31971 cpu.read_reg(Register::A7),
31972 TEST_SP + 18,
31973 "TEStyleInsert must consume selector + hTE + hST + length + text pointer"
31974 );
31975
31976 let te_ptr = bus.read_long(te_handle);
31977 assert_eq!(
31978 bus.read_word(te_ptr + TrapDispatcher::TE_LENGTH_OFFSET) as usize,
31979 text.len()
31980 );
31981 assert_eq!(
31982 bus.read_word(te_ptr + TrapDispatcher::TE_N_LINES_OFFSET),
31983 2,
31984 "explicit CR should produce two TextEdit lines"
31985 );
31986
31987 let style_handle = bus.read_long(te_ptr + TrapDispatcher::TE_TX_FONT_OFFSET);
31988 let style_ptr = bus.read_long(style_handle);
31989 assert_eq!(
31990 bus.read_word(style_ptr + TrapDispatcher::TE_STYLE_N_RUNS_OFFSET),
31991 2
31992 );
31993 assert_eq!(
31994 bus.read_word(style_ptr + TrapDispatcher::TE_STYLE_N_STYLES_OFFSET),
31995 2
31996 );
31997 assert_eq!(
31998 bus.read_word(style_ptr + TrapDispatcher::TE_STYLE_RUNS_OFFSET),
31999 0
32000 );
32001 assert_eq!(
32002 bus.read_word(style_ptr + TrapDispatcher::TE_STYLE_RUNS_OFFSET + 4),
32003 6
32004 );
32005
32006 let style_table = bus.read_long(style_ptr + TrapDispatcher::TE_STYLE_STYLE_TABLE_OFFSET);
32007 let style_table_ptr = bus.read_long(style_table);
32008 assert_eq!(
32009 bus.read_byte(style_table_ptr + TrapDispatcher::ST_ELEMENT_FACE_OFFSET),
32010 1,
32011 "first style run should preserve the bold face from the style scrap"
32012 );
32013 assert_eq!(
32014 bus.read_word(style_table_ptr + TrapDispatcher::ST_ELEMENT_SIZE_OFFSET),
32015 9,
32016 "first style run should preserve the smaller body size"
32017 );
32018 let second_style = style_table_ptr + TrapDispatcher::ST_ELEMENT_SIZE;
32019 assert_eq!(
32020 bus.read_word(second_style + TrapDispatcher::ST_ELEMENT_SIZE_OFFSET),
32021 12
32022 );
32023 assert_eq!(
32024 (
32025 bus.read_word(second_style + TrapDispatcher::ST_ELEMENT_COLOR_OFFSET),
32026 bus.read_word(second_style + TrapDispatcher::ST_ELEMENT_COLOR_OFFSET + 2),
32027 bus.read_word(second_style + TrapDispatcher::ST_ELEMENT_COLOR_OFFSET + 4),
32028 ),
32029 (0x1111, 0x2222, 0x3333)
32030 );
32031
32032 let lh_table = bus.read_long(style_ptr + TrapDispatcher::TE_STYLE_LH_TABLE_OFFSET);
32033 let lh_ptr = bus.read_long(lh_table);
32034 assert_eq!(
32035 bus.read_word(lh_ptr + TrapDispatcher::LH_ELEMENT_HEIGHT_OFFSET),
32036 10,
32037 "first TextEdit line should use first style's line height"
32038 );
32039 assert_eq!(
32040 bus.read_word(
32041 lh_ptr + TrapDispatcher::LH_ELEMENT_SIZE + TrapDispatcher::LH_ELEMENT_HEIGHT_OFFSET
32042 ),
32043 14,
32044 "second TextEdit line should use second style's line height"
32045 );
32046
32047 let runs = disp.te_style_runs(&bus, te_handle, text.len());
32048 assert_eq!(runs.len(), 2);
32049 assert_eq!(runs[0].start, 0);
32050 assert_eq!(runs[0].style_index, 0);
32051 assert_eq!(runs[0].style.size, 9);
32052 assert_eq!(runs[1].start, 6);
32053 assert_eq!(runs[1].style_index, 1);
32054 assert_eq!(runs[1].style.size, 12);
32055 }
32056
32057 #[test]
32058 fn testyleinsert_parses_stscrprec_style_table_after_count_word() {
32059 let (mut disp, mut cpu, mut bus) = setup();
32062
32063 disp.tx_font = 0;
32064 disp.tx_face = 0;
32065 disp.tx_size = 12;
32066 let te_handle = TrapDispatcher::allocate_te_handle(&mut bus);
32067 disp.initialize_styled_te_record(&mut bus, te_handle, (0, 0, 80, 200), (0, 0, 80, 200));
32068
32069 let text = b"TITLE body";
32070 let text_ptr = bus.alloc(text.len() as u32);
32071 bus.write_bytes(text_ptr, text);
32072
32073 let style_scrap = TrapDispatcher::allocate_handle_with_data(&mut bus, 2 + 2 * 0x14);
32074 let scrap_ptr = bus.read_long(style_scrap);
32075 bus.write_word(scrap_ptr, 2);
32076
32077 let first = scrap_ptr + 2;
32078 bus.write_long(first, 0);
32079 bus.write_word(first + 4, 10);
32080 bus.write_word(first + 6, 8);
32081 bus.write_word(first + 8, 0);
32082 bus.write_byte(first + 10, 1);
32083 bus.write_word(first + 12, 9);
32084 bus.write_word(first + 14, 0);
32085 bus.write_word(first + 16, 0);
32086 bus.write_word(first + 18, 0);
32087
32088 let second = first + 0x14;
32089 bus.write_long(second, 6);
32090 bus.write_word(second + 4, 14);
32091 bus.write_word(second + 6, 11);
32092 bus.write_word(second + 8, 3);
32093 bus.write_byte(second + 10, 0);
32094 bus.write_word(second + 12, 12);
32095 bus.write_word(second + 14, 0x1111);
32096 bus.write_word(second + 16, 0x2222);
32097 bus.write_word(second + 18, 0x3333);
32098
32099 bus.write_word(TEST_SP, 0x0007);
32100 bus.write_long(TEST_SP + 2, te_handle);
32101 bus.write_long(TEST_SP + 6, style_scrap);
32102 bus.write_long(TEST_SP + 10, text.len() as u32);
32103 bus.write_long(TEST_SP + 14, text_ptr);
32104
32105 let result = disp.dispatch_dialog(true, 0x03D, &mut cpu, &mut bus);
32106 assert!(result.unwrap().is_ok());
32107
32108 let runs = disp.te_style_runs(&bus, te_handle, text.len());
32109 assert_eq!(runs.len(), 2);
32110 assert_eq!(runs[0].start, 0);
32111 assert_eq!(runs[0].style.face, 1);
32112 assert_eq!(runs[0].style.size, 9);
32113 assert_eq!(runs[1].start, 6);
32114 assert_eq!(runs[1].style.font, 3);
32115 assert_eq!(runs[1].style.size, 12);
32116 assert_eq!(runs[1].style.color, (0x1111, 0x2222, 0x3333));
32117 }
32118
32119 #[test]
32123 fn teautoview_reads_hte_from_sp_plus_0_and_fauto_from_sp_plus_4() {
32124 let (mut disp, mut cpu, mut bus) = setup();
32125 let te_handle = TrapDispatcher::allocate_te_handle(&mut bus);
32126
32127 bus.write_long(TEST_SP, te_handle); bus.write_byte(TEST_SP + 4, 1); let result = disp.dispatch_dialog(true, 0x013, &mut cpu, &mut bus);
32132 assert!(result.unwrap().is_ok());
32133 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 6);
32134 assert!(
32135 disp.te_feature_bit(te_handle, TrapDispatcher::TE_FEATURE_AUTO_SCROLL),
32136 "TEAutoView must enable auto-scroll on the TE handle at SP+0"
32137 );
32138 }
32139
32140 #[test]
32141 fn teautoview_clears_feature_when_fauto_is_false() {
32142 let (mut disp, mut cpu, mut bus) = setup();
32143 let te_handle = TrapDispatcher::allocate_te_handle(&mut bus);
32144
32145 disp.set_te_feature_bit(te_handle, TrapDispatcher::TE_FEATURE_AUTO_SCROLL, true);
32147 assert!(disp.te_feature_bit(te_handle, TrapDispatcher::TE_FEATURE_AUTO_SCROLL));
32148
32149 bus.write_long(TEST_SP, te_handle);
32150 bus.write_word(TEST_SP + 4, 0); let result = disp.dispatch_dialog(true, 0x013, &mut cpu, &mut bus);
32153 assert!(result.unwrap().is_ok());
32154 assert_eq!(
32155 cpu.read_reg(Register::A7),
32156 TEST_SP + 6,
32157 "TEAutoView should pop one Boolean and one TEHandle"
32158 );
32159 assert!(
32160 !disp.te_feature_bit(te_handle, TrapDispatcher::TE_FEATURE_AUTO_SCROLL),
32161 "TEAutoView(FALSE) must clear the auto-scroll bit on the supplied hTE"
32162 );
32163 }
32164
32165 #[test]
32168 fn teautoview_and_tefeatureflag_observe_shared_autoscroll_state() {
32169 let (mut disp, mut cpu, mut bus) = setup();
32170 let te_handle = TrapDispatcher::allocate_te_handle(&mut bus);
32171
32172 bus.write_long(TEST_SP, te_handle);
32173 bus.write_byte(TEST_SP + 4, 1); let result = disp.dispatch_dialog(true, 0x013, &mut cpu, &mut bus);
32175 assert!(result.unwrap().is_ok());
32176
32177 cpu.write_reg(Register::A7, TEST_SP);
32178 bus.write_word(TEST_SP, 0x000E); bus.write_long(TEST_SP + 2, te_handle); bus.write_word(TEST_SP + 6, (-1i16) as u16); bus.write_word(TEST_SP + 8, TrapDispatcher::TE_FEATURE_AUTO_SCROLL);
32182 bus.write_word(TEST_SP + 10, 0xBEEF);
32183 let result = disp.dispatch_dialog(true, 0x03D, &mut cpu, &mut bus);
32184 assert!(result.unwrap().is_ok());
32185 assert_eq!(bus.read_word(TEST_SP + 10), 1);
32186
32187 cpu.write_reg(Register::A7, TEST_SP);
32188 bus.write_long(TEST_SP, te_handle);
32189 bus.write_word(TEST_SP + 4, 0); let result = disp.dispatch_dialog(true, 0x013, &mut cpu, &mut bus);
32191 assert!(result.unwrap().is_ok());
32192
32193 cpu.write_reg(Register::A7, TEST_SP);
32194 bus.write_word(TEST_SP, 0x000E); bus.write_long(TEST_SP + 2, te_handle); bus.write_word(TEST_SP + 6, (-1i16) as u16); bus.write_word(TEST_SP + 8, TrapDispatcher::TE_FEATURE_AUTO_SCROLL);
32198 bus.write_word(TEST_SP + 10, 0xBEEF);
32199 let result = disp.dispatch_dialog(true, 0x03D, &mut cpu, &mut bus);
32200 assert!(result.unwrap().is_ok());
32201 assert_eq!(bus.read_word(TEST_SP + 10), 0);
32202 }
32203
32204 #[test]
32205 fn te_dispatch_feature_flag_tracks_auto_scroll_state() {
32206 let (mut disp, mut cpu, mut bus) = setup();
32207
32208 let te_handle = TrapDispatcher::allocate_te_handle(&mut bus);
32209 bus.write_word(TEST_SP, 0x000E);
32210 bus.write_long(TEST_SP + 2, te_handle);
32211 bus.write_word(TEST_SP + 6, 1); bus.write_word(TEST_SP + 8, 0); let result = disp.dispatch_dialog(true, 0x03D, &mut cpu, &mut bus);
32215 assert!(result.unwrap().is_ok());
32216 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 10);
32217 assert_eq!(bus.read_word(TEST_SP + 10), 0);
32218 assert!(disp.te_feature_bit(te_handle, TrapDispatcher::TE_FEATURE_AUTO_SCROLL));
32219 }
32220
32221 #[test]
32225 fn te_dispatch_feature_flag_test_action_returns_current_state() {
32226 let (mut disp, mut cpu, mut bus) = setup();
32227
32228 let te_handle = TrapDispatcher::allocate_te_handle(&mut bus);
32229 disp.set_te_feature_bit(te_handle, TrapDispatcher::TE_FEATURE_AUTO_SCROLL, true);
32230
32231 bus.write_word(TEST_SP, 0x000E); bus.write_long(TEST_SP + 2, te_handle); bus.write_word(TEST_SP + 6, (-1i16) as u16); bus.write_word(TEST_SP + 8, TrapDispatcher::TE_FEATURE_AUTO_SCROLL);
32235 bus.write_word(TEST_SP + 10, 0xBEEF);
32236
32237 let result = disp.dispatch_dialog(true, 0x03D, &mut cpu, &mut bus);
32238 assert!(result.unwrap().is_ok());
32239 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 10);
32240 assert_eq!(bus.read_word(TEST_SP + 10), 1);
32241 assert!(
32242 disp.te_feature_bit(te_handle, TrapDispatcher::TE_FEATURE_AUTO_SCROLL),
32243 "teBitTest should not mutate the feature bit"
32244 );
32245 }
32246
32247 #[test]
32252 fn tefeatureflag_clear_action_returns_prior_one_state_and_clears_bit() {
32253 let (mut disp, mut cpu, mut bus) = setup();
32254
32255 let te_handle = TrapDispatcher::allocate_te_handle(&mut bus);
32256 disp.set_te_feature_bit(te_handle, TrapDispatcher::TE_FEATURE_AUTO_SCROLL, true);
32258 assert!(disp.te_feature_bit(te_handle, TrapDispatcher::TE_FEATURE_AUTO_SCROLL));
32259
32260 bus.write_word(TEST_SP, 0x000E); bus.write_long(TEST_SP + 2, te_handle); bus.write_word(TEST_SP + 6, 0); bus.write_word(TEST_SP + 8, TrapDispatcher::TE_FEATURE_AUTO_SCROLL);
32268 bus.write_word(TEST_SP + 10, 0xBEEF); let result = disp.dispatch_dialog(true, 0x03D, &mut cpu, &mut bus);
32271 assert!(result.unwrap().is_ok());
32272 assert_eq!(
32273 cpu.read_reg(Register::A7),
32274 TEST_SP + 10,
32275 "TEFeatureFlag must pop 10 bytes (selector + hTE + action + feature)"
32276 );
32277 assert_eq!(
32278 bus.read_word(TEST_SP + 10),
32279 1,
32280 "teBitClear must return PRIOR bit state (1 = was set), not the new state"
32281 );
32282 assert!(
32283 !disp.te_feature_bit(te_handle, TrapDispatcher::TE_FEATURE_AUTO_SCROLL),
32284 "teBitClear must clear the feature bit after returning its prior state"
32285 );
32286 }
32287
32288 #[test]
32295 fn tedispatch_function_protocol_consumes_threewordinline_stack_frame_for_tefeatureflag() {
32296 let (mut disp, mut cpu, mut bus) = setup();
32297
32298 let te_handle = TrapDispatcher::allocate_te_handle(&mut bus);
32299
32300 bus.write_word(TEST_SP, 0x000E); bus.write_long(TEST_SP + 2, te_handle);
32302 bus.write_word(TEST_SP + 6, (-1i16) as u16); bus.write_word(TEST_SP + 8, TrapDispatcher::TE_FEATURE_AUTO_SCROLL);
32304 bus.write_word(TEST_SP + 10, 0xDEAD);
32306 bus.write_word(TEST_SP + 12, 0xCAFE);
32307
32308 let result = disp.dispatch_dialog(true, 0x03D, &mut cpu, &mut bus);
32309 assert!(result.unwrap().is_ok());
32310
32311 assert_eq!(
32313 cpu.read_reg(Register::A7),
32314 TEST_SP + 10,
32315 "TEFeatureFlag dispatch must advance A7 by exactly 10 bytes"
32316 );
32317
32318 assert_ne!(
32320 bus.read_word(TEST_SP + 10),
32321 0xDEAD,
32322 "TEFeatureFlag must overwrite the 2-byte result slot with the prior bit state"
32323 );
32324
32325 assert_eq!(
32328 bus.read_word(TEST_SP + 12),
32329 0xCAFE,
32330 "TEFeatureFlag must not write past the 2-byte short function-result slot"
32331 );
32332 }
32333
32334 #[test]
32335 fn tesetstyle_reads_style_byte_without_treating_record_padding_as_face_bits() {
32336 let (mut disp, mut cpu, mut bus) = setup();
32340 disp.tx_face = 1;
32341 disp.tx_size = 12;
32342 let te_handle = TrapDispatcher::allocate_te_handle(&mut bus);
32343 disp.initialize_styled_te_record(&mut bus, te_handle, (0, 0, 80, 200), (0, 0, 80, 200));
32344 disp.te_set_text_contents(&mut bus, te_handle, b"A");
32345 let te_ptr = bus.read_long(te_handle);
32346 bus.write_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET, 1);
32347 bus.write_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET, 1);
32348 let text_style = bus.alloc(12);
32349 bus.write_word(text_style, 22);
32350 bus.write_byte(text_style + 2, 0); bus.write_byte(text_style + 3, 0x56); bus.write_word(text_style + 4, 12);
32353 bus.write_word(text_style + 6, 0);
32354 bus.write_word(text_style + 8, 0xFFFF);
32355 bus.write_word(text_style + 10, 0);
32356 bus.write_word(TEST_SP, 0x0001);
32357 bus.write_long(TEST_SP + 2, te_handle);
32358 bus.write_word(TEST_SP + 6, 0); bus.write_long(TEST_SP + 8, text_style);
32360 bus.write_word(TEST_SP + 12, 0x000F); let result = disp.dispatch_dialog(true, 0x03D, &mut cpu, &mut bus);
32363
32364 assert!(result.unwrap().is_ok());
32365 let style_handle = bus.read_long(te_ptr + TrapDispatcher::TE_TX_FONT_OFFSET);
32366 let style_record = bus.read_long(style_handle);
32367 let table_handle =
32368 bus.read_long(style_record + TrapDispatcher::TE_STYLE_STYLE_TABLE_OFFSET);
32369 let table = bus.read_long(table_handle);
32370 assert_eq!(
32371 bus.read_byte(table + TrapDispatcher::ST_ELEMENT_FACE_OFFSET),
32372 1,
32373 "an insertion-point style change must not restyle existing text"
32374 );
32375 let null_style_handle =
32376 bus.read_long(style_record + TrapDispatcher::TE_STYLE_NULL_STYLE_OFFSET);
32377 let null_style = bus.read_long(null_style_handle);
32378 let null_scrap_handle = bus.read_long(null_style + TrapDispatcher::NULL_STYLE_SCRAP_OFFSET);
32379 let null_scrap = bus.read_long(null_scrap_handle);
32380 assert_eq!(
32381 bus.read_byte(
32382 null_scrap
32383 + TrapDispatcher::SCRAP_STYLE_TAB_OFFSET
32384 + TrapDispatcher::SCRAP_STYLE_FACE_OFFSET
32385 ),
32386 0,
32387 "the insertion-point style must be stored in TextEdit's null scrap"
32388 );
32389
32390 let inserted = bus.alloc(1);
32391 bus.write_byte(inserted, b'B');
32392 cpu.write_reg(Register::A7, TEST_SP);
32393 bus.write_word(TEST_SP, 0x0007);
32394 bus.write_long(TEST_SP + 2, te_handle);
32395 bus.write_long(TEST_SP + 6, 0); bus.write_long(TEST_SP + 10, 1);
32397 bus.write_long(TEST_SP + 14, inserted);
32398 let result = disp.dispatch_dialog(true, 0x03D, &mut cpu, &mut bus);
32399 assert!(result.unwrap().is_ok());
32400 let runs = disp.te_style_runs(&bus, te_handle, 2);
32401 assert_eq!(runs.len(), 2);
32402 assert_eq!((runs[0].start, runs[0].style.face), (0, 1));
32403 assert_eq!((runs[1].start, runs[1].style.face), (1, 0));
32404 }
32405
32406 #[test]
32407 fn te_dispatch_continuous_style_returns_unstyled_record_style() {
32408 let (mut disp, mut cpu, mut bus) = setup();
32409
32410 let te_handle = TrapDispatcher::allocate_te_handle(&mut bus);
32411 disp.current_port = 0x181000;
32412 disp.tx_font = 4;
32413 disp.tx_face = 1;
32414 disp.tx_mode = 2;
32415 disp.tx_size = 12;
32416 disp.initialize_te_record(&mut bus, te_handle, (0, 0, 40, 80), (0, 0, 40, 80));
32417
32418 let te_ptr = bus.read_long(te_handle);
32419 bus.write_word(te_ptr + TrapDispatcher::TE_TX_FONT_OFFSET, 22);
32420 bus.write_byte(te_ptr + TrapDispatcher::TE_TX_FACE_OFFSET, 3);
32421 bus.write_word(te_ptr + TrapDispatcher::TE_TX_SIZE_OFFSET, 18);
32422
32423 let style_ptr = 0x300000u32;
32424 let mode_ptr = 0x300100u32;
32425 bus.write_word(mode_ptr, 0x000F); bus.write_word(TEST_SP, 0x000A);
32427 bus.write_long(TEST_SP + 2, te_handle);
32428 bus.write_long(TEST_SP + 6, style_ptr);
32429 bus.write_long(TEST_SP + 10, mode_ptr);
32430
32431 let result = disp.dispatch_dialog(true, 0x03D, &mut cpu, &mut bus);
32432 assert!(result.unwrap().is_ok());
32433 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 14);
32434 assert_eq!(bus.read_word(TEST_SP + 14), 0xFFFF);
32435 assert_eq!(bus.read_word(style_ptr), 22);
32436 assert_eq!(bus.read_byte(style_ptr + 2), 3);
32437 assert_eq!(bus.read_word(style_ptr + 4), 18);
32438 assert_eq!(bus.read_word(style_ptr + 6), 0);
32439 assert_eq!(bus.read_word(style_ptr + 8), 0);
32440 assert_eq!(bus.read_word(style_ptr + 10), 0);
32441 }
32442
32443 #[test]
32444 fn te_scroll_reads_handle_from_stack_top() {
32445 let (mut disp, mut cpu, mut bus) = setup();
32446
32447 let te_handle = TrapDispatcher::allocate_te_handle(&mut bus);
32448 disp.current_port = 0x181000;
32449 disp.tx_font = 4;
32450 disp.tx_face = 0;
32451 disp.tx_mode = 0;
32452 disp.tx_size = 10;
32453 disp.initialize_te_record(&mut bus, te_handle, (10, 20, 50, 80), (10, 20, 50, 80));
32454
32455 bus.write_long(TEST_SP, te_handle);
32456 bus.write_word(TEST_SP + 4, (-6i16) as u16);
32457 bus.write_word(TEST_SP + 6, 4);
32458
32459 let result = disp.dispatch_dialog(true, 0x1DD, &mut cpu, &mut bus);
32460 assert!(result.unwrap().is_ok());
32461 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 8);
32462
32463 let te_ptr = bus.read_long(te_handle);
32464 assert_eq!(
32465 bus.read_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET),
32466 4
32467 );
32468 assert_eq!(
32469 bus.read_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET + 2),
32470 24
32471 );
32472 assert_eq!(
32473 bus.read_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET + 4),
32474 44
32475 );
32476 assert_eq!(
32477 bus.read_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET + 6),
32478 84
32479 );
32480 }
32481
32482 #[test]
32483 fn te_pin_scroll_reads_handle_from_stack_top() {
32484 let (mut disp, mut cpu, mut bus) = setup();
32485
32486 let te_handle = TrapDispatcher::allocate_te_handle(&mut bus);
32487 disp.current_port = 0x181000;
32488 disp.tx_font = 4;
32489 disp.tx_face = 0;
32490 disp.tx_mode = 0;
32491 disp.tx_size = 10;
32492 disp.initialize_te_record(&mut bus, te_handle, (10, 20, 50, 80), (10, 20, 50, 80));
32493 disp.te_set_text_contents(
32494 &mut bus,
32495 te_handle,
32496 b"one two three four five six seven eight nine ten",
32497 );
32498
32499 bus.write_long(TEST_SP, te_handle);
32500 bus.write_word(TEST_SP + 4, (-6i16) as u16);
32501 bus.write_word(TEST_SP + 6, 4);
32502
32503 let result = disp.dispatch_dialog(true, 0x012, &mut cpu, &mut bus);
32504 assert!(result.unwrap().is_ok());
32505 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 8);
32506
32507 let te_ptr = bus.read_long(te_handle);
32508 assert_eq!(
32509 bus.read_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET),
32510 4
32511 );
32512 assert_eq!(
32513 bus.read_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET + 2),
32514 20
32515 );
32516 assert_eq!(
32517 bus.read_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET + 4),
32518 44
32519 );
32520 assert_eq!(
32521 bus.read_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET + 6),
32522 80
32523 );
32524 }
32525
32526 #[test]
32527 fn tepinscroll_in_range_negative_dv_offsets_destrect_top_and_bottom_exactly_by_dv() {
32528 let (mut disp, mut cpu, mut bus) = setup();
32536
32537 let te_handle = make_te_with_text(&mut disp, &mut bus, b"A\rB\rC\rD\rE\rF\rG\rH");
32538 let te_ptr = bus.read_long(te_handle);
32539 bus.write_word(
32540 te_ptr + TrapDispatcher::TE_VIEW_RECT_OFFSET,
32541 (-1000i16) as u16,
32542 );
32543 bus.write_word(
32544 te_ptr + TrapDispatcher::TE_VIEW_RECT_OFFSET + 2,
32545 (-1000i16) as u16,
32546 );
32547 bus.write_word(
32548 te_ptr + TrapDispatcher::TE_VIEW_RECT_OFFSET + 4,
32549 (-990i16) as u16,
32550 );
32551 bus.write_word(
32552 te_ptr + TrapDispatcher::TE_VIEW_RECT_OFFSET + 6,
32553 (-800i16) as u16,
32554 );
32555 bus.write_word(
32556 te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET,
32557 (-1000i16) as u16,
32558 );
32559 bus.write_word(
32560 te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET + 2,
32561 (-1000i16) as u16,
32562 );
32563 bus.write_word(
32564 te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET + 4,
32565 (-990i16) as u16,
32566 );
32567 bus.write_word(
32568 te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET + 6,
32569 (-800i16) as u16,
32570 );
32571
32572 bus.write_long(TEST_SP, te_handle);
32573 bus.write_word(TEST_SP + 4, (-3i16) as u16);
32574 bus.write_word(TEST_SP + 6, 0);
32575
32576 let result = disp.dispatch_dialog(true, 0x012, &mut cpu, &mut bus);
32577 assert!(result.unwrap().is_ok());
32578 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 8);
32579
32580 assert_eq!(
32581 bus.read_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET) as i16,
32582 -1003,
32583 "destRect.top must shift by exactly dv=-3"
32584 );
32585 assert_eq!(
32586 bus.read_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET + 2) as i16,
32587 -1000,
32588 "destRect.left must be unchanged (dh=0)"
32589 );
32590 assert_eq!(
32591 bus.read_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET + 4) as i16,
32592 -993,
32593 "destRect.bottom must shift by exactly dv=-3"
32594 );
32595 assert_eq!(
32596 bus.read_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET + 6) as i16,
32597 -800,
32598 "destRect.right must be unchanged (dh=0)"
32599 );
32600 }
32601
32602 #[test]
32603 fn tepinscroll_pascal_lr_stack_layout_reads_dh_dv_and_hte_from_correct_offsets() {
32604 let (mut disp, mut cpu, mut bus) = setup();
32615
32616 let te_handle = make_te_with_text(&mut disp, &mut bus, b"A\rB\rC\rD\rE\rF");
32617 let te_ptr = bus.read_long(te_handle);
32618 bus.write_word(
32619 te_ptr + TrapDispatcher::TE_VIEW_RECT_OFFSET,
32620 (-500i16) as u16,
32621 );
32622 bus.write_word(
32623 te_ptr + TrapDispatcher::TE_VIEW_RECT_OFFSET + 2,
32624 (-500i16) as u16,
32625 );
32626 bus.write_word(
32627 te_ptr + TrapDispatcher::TE_VIEW_RECT_OFFSET + 4,
32628 (-490i16) as u16,
32629 );
32630 bus.write_word(
32631 te_ptr + TrapDispatcher::TE_VIEW_RECT_OFFSET + 6,
32632 (-400i16) as u16,
32633 );
32634 bus.write_word(
32635 te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET,
32636 (-500i16) as u16,
32637 );
32638 bus.write_word(
32641 te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET + 2,
32642 (-510i16) as u16,
32643 );
32644 bus.write_word(
32645 te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET + 4,
32646 (-490i16) as u16,
32647 );
32648 bus.write_word(
32649 te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET + 6,
32650 (-310i16) as u16,
32651 );
32652
32653 bus.write_long(TEST_SP, te_handle);
32654 bus.write_word(TEST_SP + 4, (-2i16) as u16);
32655 bus.write_word(TEST_SP + 6, 7);
32656 bus.write_word(TEST_SP + 8, 0xCAFE);
32657
32658 let result = disp.dispatch_dialog(true, 0x012, &mut cpu, &mut bus);
32659 assert!(result.unwrap().is_ok());
32660 assert_eq!(
32661 cpu.read_reg(Register::A7),
32662 TEST_SP + 8,
32663 "Pascal PROCEDURE must pop exactly 8 bytes (dh 2 + dv 2 + hTE 4)"
32664 );
32665 assert_eq!(
32666 bus.read_word(TEST_SP + 8),
32667 0xCAFE,
32668 "sentinel past the arg frame must survive"
32669 );
32670 assert_eq!(
32674 bus.read_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET) as i16,
32675 -502,
32676 "destRect.top must shift by dv=-2"
32677 );
32678 assert_eq!(
32679 bus.read_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET + 2) as i16,
32680 -503,
32681 "destRect.left must shift by dh=+7 (clamped to max_right=10)"
32682 );
32683 }
32684
32685 #[test]
32686 fn tepinscroll_clamps_overscroll_when_last_line_is_already_visible() {
32687 let (mut disp, mut cpu, mut bus) = setup();
32690
32691 let te_handle = make_te_with_text(&mut disp, &mut bus, b"A");
32692 let te_ptr = bus.read_long(te_handle);
32693 bus.write_word(te_ptr + TrapDispatcher::TE_VIEW_RECT_OFFSET, 10);
32694 bus.write_word(te_ptr + TrapDispatcher::TE_VIEW_RECT_OFFSET + 2, 20);
32695 bus.write_word(te_ptr + TrapDispatcher::TE_VIEW_RECT_OFFSET + 4, 50);
32696 bus.write_word(te_ptr + TrapDispatcher::TE_VIEW_RECT_OFFSET + 6, 80);
32697 bus.write_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET, 10);
32698 bus.write_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET + 2, 20);
32699 bus.write_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET + 4, 50);
32700 bus.write_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET + 6, 80);
32701
32702 bus.write_long(TEST_SP, te_handle);
32703 bus.write_word(TEST_SP + 4, (-20i16) as u16);
32704 bus.write_word(TEST_SP + 6, 0);
32705 let result = disp.dispatch_dialog(true, 0x012, &mut cpu, &mut bus);
32706 assert!(result.unwrap().is_ok());
32707 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 8);
32708
32709 assert_eq!(
32710 bus.read_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET),
32711 10
32712 );
32713 assert_eq!(
32714 bus.read_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET + 2),
32715 20
32716 );
32717 assert_eq!(
32718 bus.read_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET + 4),
32719 50
32720 );
32721 assert_eq!(
32722 bus.read_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET + 6),
32723 80
32724 );
32725 }
32726
32727 #[test]
32728 fn teselview_autoscroll_disabled_leaves_destrect_unchanged() {
32729 let (mut disp, mut cpu, mut bus) = setup();
32732 let te_handle = make_te_with_text(
32733 &mut disp,
32734 &mut bus,
32735 b"one two three four five six seven eight nine ten",
32736 );
32737 let te_ptr = bus.read_long(te_handle);
32738 bus.write_word(te_ptr + TrapDispatcher::TE_VIEW_RECT_OFFSET, 10);
32739 bus.write_word(te_ptr + TrapDispatcher::TE_VIEW_RECT_OFFSET + 2, 20);
32740 bus.write_word(te_ptr + TrapDispatcher::TE_VIEW_RECT_OFFSET + 4, 50);
32741 bus.write_word(te_ptr + TrapDispatcher::TE_VIEW_RECT_OFFSET + 6, 80);
32742 bus.write_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET, 0);
32743 bus.write_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET + 2, 20);
32744 bus.write_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET + 4, 40);
32745 bus.write_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET + 6, 80);
32746 bus.write_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET, 0);
32747 bus.write_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET, 0);
32748 disp.set_te_feature_bit(te_handle, TrapDispatcher::TE_FEATURE_AUTO_SCROLL, false);
32749
32750 bus.write_long(TEST_SP, te_handle);
32751 let result = disp.dispatch_dialog(true, 0x011, &mut cpu, &mut bus);
32752 assert!(result.unwrap().is_ok());
32753 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 4);
32754 assert_eq!(
32755 bus.read_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET),
32756 0
32757 );
32758 assert_eq!(
32759 bus.read_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET + 2),
32760 20
32761 );
32762 assert_eq!(
32763 bus.read_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET + 4),
32764 40
32765 );
32766 assert_eq!(
32767 bus.read_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET + 6),
32768 80
32769 );
32770 }
32771
32772 #[test]
32773 fn teselview_autoscroll_enabled_scrolls_destrect_toward_selection() {
32774 let (mut disp, mut cpu, mut bus) = setup();
32777 let te_handle = make_te_with_text(
32778 &mut disp,
32779 &mut bus,
32780 b"one two three four five six seven eight nine ten",
32781 );
32782 let te_ptr = bus.read_long(te_handle);
32783 bus.write_word(te_ptr + TrapDispatcher::TE_VIEW_RECT_OFFSET, 10);
32784 bus.write_word(te_ptr + TrapDispatcher::TE_VIEW_RECT_OFFSET + 2, 20);
32785 bus.write_word(te_ptr + TrapDispatcher::TE_VIEW_RECT_OFFSET + 4, 50);
32786 bus.write_word(te_ptr + TrapDispatcher::TE_VIEW_RECT_OFFSET + 6, 80);
32787 bus.write_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET, 0);
32788 bus.write_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET + 2, 20);
32789 bus.write_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET + 4, 40);
32790 bus.write_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET + 6, 80);
32791 bus.write_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET, 0);
32792 bus.write_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET, 0);
32793 disp.set_te_feature_bit(te_handle, TrapDispatcher::TE_FEATURE_AUTO_SCROLL, true);
32794
32795 bus.write_long(TEST_SP, te_handle);
32796 let result = disp.dispatch_dialog(true, 0x011, &mut cpu, &mut bus);
32797 assert!(result.unwrap().is_ok());
32798 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 4);
32799 assert_eq!(
32800 bus.read_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET),
32801 10
32802 );
32803 assert_eq!(
32804 bus.read_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET + 2),
32805 20
32806 );
32807 assert_eq!(
32808 bus.read_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET + 4),
32809 50
32810 );
32811 assert_eq!(
32812 bus.read_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET + 6),
32813 80
32814 );
32815 }
32816
32817 #[test]
32818 fn teselview_apple_canonical_below_view_shifts_destrect_top_up() {
32819 let (mut disp, mut cpu, mut bus) = setup();
32831 let te_handle = make_te_with_text(&mut disp, &mut bus, b"A\rB\rC\rD\rE\rF\rG\rH");
32832 let te_ptr = bus.read_long(te_handle);
32833 bus.write_word(te_ptr + TrapDispatcher::TE_VIEW_RECT_OFFSET, 0);
32834 bus.write_word(te_ptr + TrapDispatcher::TE_VIEW_RECT_OFFSET + 2, 0);
32835 bus.write_word(te_ptr + TrapDispatcher::TE_VIEW_RECT_OFFSET + 4, 30);
32836 bus.write_word(te_ptr + TrapDispatcher::TE_VIEW_RECT_OFFSET + 6, 100);
32837 bus.write_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET, 0);
32838 bus.write_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET + 2, 0);
32839 bus.write_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET + 4, 200);
32840 bus.write_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET + 6, 100);
32841 bus.write_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET, 14);
32842 bus.write_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET, 14);
32843 disp.set_te_feature_bit(te_handle, TrapDispatcher::TE_FEATURE_AUTO_SCROLL, true);
32844
32845 let pre_top = bus.read_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET) as i16;
32846
32847 bus.write_long(TEST_SP, te_handle);
32848 let result = disp.dispatch_dialog(true, 0x011, &mut cpu, &mut bus);
32849 assert!(result.unwrap().is_ok());
32850 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 4);
32851
32852 let post_top = bus.read_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET) as i16;
32853 assert!(
32854 post_top < pre_top,
32855 "TESelView must shift destRect.top UP (negative direction) when selection is below viewRect per IM:Text 1993 p. 2-92; got pre_top={} post_top={}",
32856 pre_top,
32857 post_top
32858 );
32859 }
32860
32861 #[test]
32862 fn teselview_procedure_protocol_consumes_only_tehandle_arg() {
32863 let (mut disp, mut cpu, mut bus) = setup();
32867 let te_handle = make_te_with_text(&mut disp, &mut bus, b"hi");
32868 disp.set_te_feature_bit(te_handle, TrapDispatcher::TE_FEATURE_AUTO_SCROLL, false);
32871
32872 bus.write_long(TEST_SP, te_handle);
32873 bus.write_word(TEST_SP + 4, 0xCAFE);
32874 bus.write_word(TEST_SP + 6, 0xBABE);
32875 let result = disp.dispatch_dialog(true, 0x011, &mut cpu, &mut bus);
32876 assert!(result.unwrap().is_ok());
32877 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 4);
32878 assert_eq!(bus.read_word(TEST_SP + 4), 0xCAFE);
32879 assert_eq!(bus.read_word(TEST_SP + 6), 0xBABE);
32880 }
32881
32882 #[test]
32883 fn tegetoffset_point_above_destrect_returns_zero() {
32884 let (mut disp, mut cpu, mut bus) = setup();
32892 let te_handle = make_te_with_text(&mut disp, &mut bus, b"ABC");
32893 let te_ptr = bus.read_long(te_handle);
32894 bus.write_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET, 10);
32895 bus.write_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET + 2, 20);
32896 bus.write_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET + 4, 40);
32897 bus.write_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET + 6, 140);
32898
32899 cpu.write_reg(Register::A7, TEST_SP);
32900 bus.write_long(TEST_SP, te_handle);
32901 bus.write_word(TEST_SP + 4, 5);
32902 bus.write_word(TEST_SP + 6, 30);
32903 bus.write_word(TEST_SP + 8, 0xBEEF);
32904 let result = disp.dispatch_dialog(true, 0x03C, &mut cpu, &mut bus);
32905 assert!(result.unwrap().is_ok());
32906 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 8);
32907 assert_eq!(bus.read_word(TEST_SP + 8) as i16, 0);
32908 }
32909
32910 #[test]
32911 fn tegetoffset_point_below_last_line_returns_telength() {
32912 let (mut disp, mut cpu, mut bus) = setup();
32918 let te_handle = make_te_with_text(&mut disp, &mut bus, b"ABC");
32919 let te_ptr = bus.read_long(te_handle);
32920 bus.write_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET, 10);
32921 bus.write_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET + 2, 20);
32922 bus.write_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET + 4, 40);
32923 bus.write_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET + 6, 140);
32924
32925 cpu.write_reg(Register::A7, TEST_SP);
32926 bus.write_long(TEST_SP, te_handle);
32927 bus.write_word(TEST_SP + 4, 200);
32928 bus.write_word(TEST_SP + 6, 30);
32929 bus.write_word(TEST_SP + 8, 0xBEEF);
32930 let result = disp.dispatch_dialog(true, 0x03C, &mut cpu, &mut bus);
32931 assert!(result.unwrap().is_ok());
32932 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 8);
32933 assert_eq!(bus.read_word(TEST_SP + 8) as i16, 3);
32934 }
32935
32936 #[test]
32937 fn tegetoffset_function_protocol_consumes_point_and_tehandle_args_writes_integer_result() {
32938 let (mut disp, mut cpu, mut bus) = setup();
32943 let te_handle = make_te_with_text(&mut disp, &mut bus, b"ABC");
32944 let te_ptr = bus.read_long(te_handle);
32945 bus.write_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET, 10);
32946 bus.write_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET + 2, 20);
32947 bus.write_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET + 4, 40);
32948 bus.write_word(te_ptr + TrapDispatcher::TE_DEST_RECT_OFFSET + 6, 140);
32949
32950 cpu.write_reg(Register::A7, TEST_SP);
32951 bus.write_long(TEST_SP, te_handle);
32952 bus.write_word(TEST_SP + 4, 5);
32953 bus.write_word(TEST_SP + 6, 30);
32954 bus.write_word(TEST_SP + 8, 0xDEAD);
32955 bus.write_word(TEST_SP + 10, 0xCAFE);
32956 let result = disp.dispatch_dialog(true, 0x03C, &mut cpu, &mut bus);
32957 assert!(result.unwrap().is_ok());
32958 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 8);
32959 assert_ne!(bus.read_word(TEST_SP + 8), 0xDEAD);
32960 assert_eq!(bus.read_word(TEST_SP + 10), 0xCAFE);
32961 }
32962
32963 #[test]
32964 fn tefindword_returns_word_bounds_for_interior_positions() {
32965 let (mut disp, mut cpu, mut bus) = setup();
32968 let te_handle = make_te_with_text(&mut disp, &mut bus, b"alpha beta");
32969 let te_ptr = bus.read_long(te_handle);
32970
32971 cpu.write_reg(Register::A7, TEST_SP);
32972 cpu.write_reg(Register::D0, 1); cpu.write_reg(Register::D2, 0x1111);
32974 cpu.write_reg(Register::A3, te_ptr);
32975 cpu.write_reg(Register::A4, te_handle);
32976
32977 let result = disp.dispatch_dialog(false, 0x0FE, &mut cpu, &mut bus);
32978 assert!(result.unwrap().is_ok());
32979 assert_eq!(cpu.read_reg(Register::A7), TEST_SP);
32980 assert_eq!(cpu.read_reg(Register::D0) as u16, 0);
32981 assert_eq!(cpu.read_reg(Register::D1) as u16, 5);
32982 }
32983
32984 #[test]
32985 fn tefindword_returns_second_word_bounds_without_touching_stack() {
32986 let (mut disp, mut cpu, mut bus) = setup();
32990 let te_handle = make_te_with_text(&mut disp, &mut bus, b"alpha beta");
32991 let te_ptr = bus.read_long(te_handle);
32992
32993 cpu.write_reg(Register::A7, TEST_SP);
32994 bus.write_word(TEST_SP, 0xCAFE);
32995 cpu.write_reg(Register::D0, 7); cpu.write_reg(Register::D2, 0x2222);
32997 cpu.write_reg(Register::A3, te_ptr);
32998 cpu.write_reg(Register::A4, te_handle);
32999
33000 let result = disp.dispatch_dialog(false, 0x0FE, &mut cpu, &mut bus);
33001 assert!(result.unwrap().is_ok());
33002 assert_eq!(cpu.read_reg(Register::A7), TEST_SP);
33003 assert_eq!(bus.read_word(TEST_SP), 0xCAFE);
33004 assert_eq!(cpu.read_reg(Register::D0) as u16, 6);
33005 assert_eq!(cpu.read_reg(Register::D1) as u16, 10);
33006 }
33007
33008 #[test]
33009 fn tesettext_copies_bytes_updates_length_and_pops_arguments() {
33010 let (mut disp, mut cpu, mut bus) = setup();
33013 let te_handle = make_te_with_text(&mut disp, &mut bus, b"OLD");
33014 let te_ptr = bus.read_long(te_handle);
33015 let source_ptr = bus.alloc(4);
33016 bus.write_bytes(source_ptr, b"NEW!");
33017
33018 bus.write_long(TEST_SP, te_handle);
33019 bus.write_long(TEST_SP + 4, 4);
33020 bus.write_long(TEST_SP + 8, source_ptr);
33021 let result = disp.dispatch_dialog(true, 0x1CF, &mut cpu, &mut bus);
33022 assert!(result.unwrap().is_ok());
33023 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 12);
33024
33025 assert_eq!(
33026 TrapDispatcher::te_text_bytes(&bus, te_handle),
33027 b"NEW!".to_vec()
33028 );
33029 assert_eq!(bus.read_word(te_ptr + TrapDispatcher::TE_LENGTH_OFFSET), 4);
33030 assert_eq!(
33031 bus.read_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET),
33032 4
33033 );
33034 assert_eq!(bus.read_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET), 4);
33035 let n_lines = u32::from(bus.read_word(te_ptr + TrapDispatcher::TE_N_LINES_OFFSET));
33036 assert!(n_lines > 0);
33037 assert_eq!(
33038 bus.read_word(te_ptr + TrapDispatcher::TE_LINE_STARTS_OFFSET + n_lines * 2),
33039 4
33040 );
33041 }
33042
33043 #[test]
33044 fn tesettext_nil_or_zero_length_input_clears_text() {
33045 let (mut disp, mut cpu, mut bus) = setup();
33048 let te_handle = make_te_with_text(&mut disp, &mut bus, b"HELLO");
33049 let te_ptr = bus.read_long(te_handle);
33050
33051 bus.write_long(TEST_SP, te_handle);
33052 bus.write_long(TEST_SP + 4, 5);
33053 bus.write_long(TEST_SP + 8, 0);
33054 let result = disp.dispatch_dialog(true, 0x1CF, &mut cpu, &mut bus);
33055 assert!(result.unwrap().is_ok());
33056 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 12);
33057 assert_eq!(
33058 TrapDispatcher::te_text_bytes(&bus, te_handle),
33059 Vec::<u8>::new()
33060 );
33061 assert_eq!(bus.read_word(te_ptr + TrapDispatcher::TE_LENGTH_OFFSET), 0);
33062 assert_eq!(
33063 bus.read_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET),
33064 0
33065 );
33066 assert_eq!(bus.read_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET), 0);
33067 }
33068
33069 #[test]
33070 fn tesettext_replaces_prior_contents_via_sequential_call() {
33071 let (mut disp, mut cpu, mut bus) = setup();
33076 let te_handle = make_te_with_text(&mut disp, &mut bus, b"OLD");
33077 let te_ptr = bus.read_long(te_handle);
33078
33079 let first_ptr = bus.alloc(6);
33080 bus.write_bytes(first_ptr, b"WORLD!");
33081 bus.write_long(TEST_SP, te_handle);
33082 bus.write_long(TEST_SP + 4, 6);
33083 bus.write_long(TEST_SP + 8, first_ptr);
33084 let r1 = disp.dispatch_dialog(true, 0x1CF, &mut cpu, &mut bus);
33085 assert!(r1.unwrap().is_ok());
33086 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 12);
33087 assert_eq!(
33088 TrapDispatcher::te_text_bytes(&bus, te_handle),
33089 b"WORLD!".to_vec()
33090 );
33091 assert_eq!(bus.read_word(te_ptr + TrapDispatcher::TE_LENGTH_OFFSET), 6);
33092
33093 cpu.write_reg(Register::A7, TEST_SP);
33094 let second_ptr = bus.alloc(2);
33095 bus.write_bytes(second_ptr, b"HI");
33096 bus.write_long(TEST_SP, te_handle);
33097 bus.write_long(TEST_SP + 4, 2);
33098 bus.write_long(TEST_SP + 8, second_ptr);
33099 let r2 = disp.dispatch_dialog(true, 0x1CF, &mut cpu, &mut bus);
33100 assert!(r2.unwrap().is_ok());
33101 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 12);
33102 assert_eq!(
33103 TrapDispatcher::te_text_bytes(&bus, te_handle),
33104 b"HI".to_vec()
33105 );
33106 assert_eq!(bus.read_word(te_ptr + TrapDispatcher::TE_LENGTH_OFFSET), 2);
33107 }
33108
33109 #[test]
33110 fn tesettext_balances_stackspace_with_pascal_protocol() {
33111 let (mut disp, mut cpu, mut bus) = setup();
33117 let te_handle = make_te_with_text(&mut disp, &mut bus, b"SEED");
33118 let source = bus.alloc(1);
33119 bus.write_bytes(source, b"X");
33120
33121 bus.write_long(TEST_SP, te_handle);
33122 bus.write_long(TEST_SP + 4, 1);
33123 bus.write_long(TEST_SP + 8, source);
33124 bus.write_word(TEST_SP + 12, 0xCAFE);
33125 let result = disp.dispatch_dialog(true, 0x1CF, &mut cpu, &mut bus);
33126 assert!(result.unwrap().is_ok());
33127 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 12);
33128 assert_eq!(bus.read_word(TEST_SP + 12), 0xCAFE);
33129 assert_eq!(
33130 TrapDispatcher::te_text_bytes(&bus, te_handle),
33131 b"X".to_vec()
33132 );
33133 }
33134
33135 #[test]
33136 fn tekey_inserts_character_at_caret_and_advances_selection() {
33137 let (mut disp, mut cpu, mut bus) = setup();
33140 let te_handle = make_te_with_text(&mut disp, &mut bus, b"HLO");
33141 let te_ptr = bus.read_long(te_handle);
33142 bus.write_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET, 1);
33143 bus.write_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET, 1);
33144
33145 bus.write_long(TEST_SP, te_handle);
33146 bus.write_word(TEST_SP + 4, u16::from(b'E'));
33147 let result = disp.dispatch_dialog(true, 0x1DC, &mut cpu, &mut bus);
33148 assert!(result.unwrap().is_ok());
33149 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 6);
33150 assert_eq!(
33151 TrapDispatcher::te_text_bytes(&bus, te_handle),
33152 b"HELO".to_vec()
33153 );
33154 assert_eq!(
33155 bus.read_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET),
33156 2
33157 );
33158 assert_eq!(bus.read_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET), 2);
33159 }
33160
33161 #[test]
33162 fn tekey_backspace_deletes_selection_or_previous_character() {
33163 let (mut disp, mut cpu, mut bus) = setup();
33166 let te_handle = make_te_with_text(&mut disp, &mut bus, b"HELLO");
33167 let te_ptr = bus.read_long(te_handle);
33168
33169 bus.write_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET, 1);
33170 bus.write_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET, 3);
33171 bus.write_long(TEST_SP, te_handle);
33172 bus.write_word(TEST_SP + 4, 0x0008);
33173 let result = disp.dispatch_dialog(true, 0x1DC, &mut cpu, &mut bus);
33174 assert!(result.unwrap().is_ok());
33175 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 6);
33176 assert_eq!(
33177 TrapDispatcher::te_text_bytes(&bus, te_handle),
33178 b"HLO".to_vec()
33179 );
33180 assert_eq!(
33181 bus.read_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET),
33182 1
33183 );
33184 assert_eq!(bus.read_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET), 1);
33185
33186 disp.te_set_text_contents(&mut bus, te_handle, b"HELLO");
33187 bus.write_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET, 2);
33188 bus.write_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET, 2);
33189 cpu.write_reg(Register::A7, TEST_SP);
33190 bus.write_long(TEST_SP, te_handle);
33191 bus.write_word(TEST_SP + 4, 0x0008);
33192 let result = disp.dispatch_dialog(true, 0x1DC, &mut cpu, &mut bus);
33193 assert!(result.unwrap().is_ok());
33194 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 6);
33195 assert_eq!(
33196 TrapDispatcher::te_text_bytes(&bus, te_handle),
33197 b"HLLO".to_vec()
33198 );
33199 assert_eq!(
33200 bus.read_word(te_ptr + TrapDispatcher::TE_SEL_START_OFFSET),
33201 1
33202 );
33203 assert_eq!(bus.read_word(te_ptr + TrapDispatcher::TE_SEL_END_OFFSET), 1);
33204 }
33205
33206 #[test]
33210 fn hide_dialog_item_moves_rect_offscreen_and_saves_original() {
33211 let (mut disp, mut cpu, mut bus) = setup();
33213 let dialog_ptr = 0x210000u32;
33214 disp.dialog_items.insert(
33215 dialog_ptr,
33216 vec![DialogItem {
33217 item_type: 4,
33218 rect: (10, 20, 30, 120),
33219 text: "OK".into(),
33220 resource_id: 0,
33221 proc_ptr: 0,
33222 sel_start: 0,
33223 sel_end: 0,
33224 }],
33225 );
33226
33227 bus.write_word(TEST_SP, 1);
33229 bus.write_long(TEST_SP + 2, dialog_ptr);
33230
33231 let result = disp.dispatch_dialog(true, 0x027, &mut cpu, &mut bus);
33232 assert!(result.unwrap().is_ok());
33233 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 6);
33234
33235 let item_rect = disp.dialog_items.get(&dialog_ptr).unwrap()[0].rect;
33237 assert!(
33238 item_rect.0 == 10 && item_rect.2 == 30,
33239 "vertical coordinates are unchanged by HideDialogItem, got {item_rect:?}"
33240 );
33241 assert!(
33242 item_rect.1 == 20 + 16384 && item_rect.3 == 120 + 16384,
33243 "hidden item rect must be offscreen, got {item_rect:?}"
33244 );
33245 assert_eq!(
33247 disp.hidden_dialog_item_rects.get(&(dialog_ptr, 1)).copied(),
33248 Some((10, 20, 30, 120))
33249 );
33250 }
33251
33252 #[test]
33253 fn show_dialog_item_restores_saved_rect() {
33254 let (mut disp, mut cpu, mut bus) = setup();
33256 let dialog_ptr = 0x210000u32;
33257 disp.dialog_items.insert(
33258 dialog_ptr,
33259 vec![DialogItem {
33260 item_type: 4,
33261 rect: (10, 20 + 16384, 30, 120 + 16384),
33262 text: "OK".into(),
33263 resource_id: 0,
33264 proc_ptr: 0,
33265 sel_start: 0,
33266 sel_end: 0,
33267 }],
33268 );
33269 disp.hidden_dialog_item_rects
33270 .insert((dialog_ptr, 1), (10, 20, 30, 120));
33271
33272 bus.write_word(TEST_SP, 1);
33273 bus.write_long(TEST_SP + 2, dialog_ptr);
33274
33275 let result = disp.dispatch_dialog(true, 0x028, &mut cpu, &mut bus);
33276 assert!(result.unwrap().is_ok());
33277 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 6);
33278
33279 assert_eq!(
33280 disp.dialog_items.get(&dialog_ptr).unwrap()[0].rect,
33281 (10, 20, 30, 120)
33282 );
33283 assert!(
33284 !disp.hidden_dialog_item_rects.contains_key(&(dialog_ptr, 1)),
33285 "saved rect must be removed after show"
33286 );
33287 }
33288
33289 #[test]
33290 fn hide_then_show_is_idempotent_roundtrip() {
33291 let (mut disp, mut cpu, mut bus) = setup();
33292 let dialog_ptr = 0x210000u32;
33293 let orig_rect = (50, 60, 80, 200);
33294 disp.dialog_items.insert(
33295 dialog_ptr,
33296 vec![DialogItem {
33297 item_type: 4,
33298 rect: orig_rect,
33299 text: "Cancel".into(),
33300 resource_id: 0,
33301 proc_ptr: 0,
33302 sel_start: 0,
33303 sel_end: 0,
33304 }],
33305 );
33306
33307 bus.write_word(TEST_SP, 1);
33308 bus.write_long(TEST_SP + 2, dialog_ptr);
33309 disp.dispatch_dialog(true, 0x027, &mut cpu, &mut bus)
33310 .unwrap()
33311 .unwrap();
33312
33313 cpu.write_reg(Register::A7, TEST_SP);
33315 bus.write_word(TEST_SP, 1);
33316 bus.write_long(TEST_SP + 2, dialog_ptr);
33317 disp.dispatch_dialog(true, 0x028, &mut cpu, &mut bus)
33318 .unwrap()
33319 .unwrap();
33320
33321 assert_eq!(
33322 disp.dialog_items.get(&dialog_ptr).unwrap()[0].rect,
33323 orig_rect,
33324 "round-trip hide→show must restore the exact original rect"
33325 );
33326 }
33327
33328 #[test]
33329 fn hide_dialog_item_already_hidden_left_coord_is_noop() {
33330 let (mut disp, mut cpu, mut bus) = setup();
33332 let dialog_ptr = 0x210000u32;
33333 let already_hidden = (10, 9000, 30, 9200);
33334 disp.dialog_items.insert(
33335 dialog_ptr,
33336 vec![DialogItem {
33337 item_type: 4,
33338 rect: already_hidden,
33339 text: "Hidden".into(),
33340 resource_id: 0,
33341 proc_ptr: 0,
33342 sel_start: 0,
33343 sel_end: 0,
33344 }],
33345 );
33346
33347 bus.write_word(TEST_SP, 1);
33348 bus.write_long(TEST_SP + 2, dialog_ptr);
33349
33350 let result = disp.dispatch_dialog(true, 0x027, &mut cpu, &mut bus);
33351 assert!(result.unwrap().is_ok());
33352 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 6);
33353 assert_eq!(
33354 disp.dialog_items.get(&dialog_ptr).unwrap()[0].rect,
33355 already_hidden
33356 );
33357 assert!(
33358 !disp.hidden_dialog_item_rects.contains_key(&(dialog_ptr, 1)),
33359 "already hidden items must not record a new saved rect"
33360 );
33361 }
33362
33363 #[test]
33364 fn show_dialog_item_without_saved_rect_subtracts_offset_when_hidden() {
33365 let (mut disp, mut cpu, mut bus) = setup();
33367 let dialog_ptr = 0x210000u32;
33368 disp.dialog_items.insert(
33369 dialog_ptr,
33370 vec![DialogItem {
33371 item_type: 4,
33372 rect: (10, 30 + 16384, 30, 130 + 16384),
33373 text: "ShowMe".into(),
33374 resource_id: 0,
33375 proc_ptr: 0,
33376 sel_start: 0,
33377 sel_end: 0,
33378 }],
33379 );
33380
33381 bus.write_word(TEST_SP, 1);
33382 bus.write_long(TEST_SP + 2, dialog_ptr);
33383
33384 let result = disp.dispatch_dialog(true, 0x028, &mut cpu, &mut bus);
33385 assert!(result.unwrap().is_ok());
33386 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 6);
33387 assert_eq!(
33388 disp.dialog_items.get(&dialog_ptr).unwrap()[0].rect,
33389 (10, 30, 30, 130)
33390 );
33391 }
33392
33393 #[test]
33394 fn show_dialog_item_visible_left_coord_is_noop() {
33395 let (mut disp, mut cpu, mut bus) = setup();
33397 let dialog_ptr = 0x210000u32;
33398 let visible = (10, 20, 30, 120);
33399 disp.dialog_items.insert(
33400 dialog_ptr,
33401 vec![DialogItem {
33402 item_type: 4,
33403 rect: visible,
33404 text: "Visible".into(),
33405 resource_id: 0,
33406 proc_ptr: 0,
33407 sel_start: 0,
33408 sel_end: 0,
33409 }],
33410 );
33411
33412 bus.write_word(TEST_SP, 1);
33413 bus.write_long(TEST_SP + 2, dialog_ptr);
33414 let result = disp.dispatch_dialog(true, 0x028, &mut cpu, &mut bus);
33415 assert!(result.unwrap().is_ok());
33416 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 6);
33417 assert_eq!(disp.dialog_items.get(&dialog_ptr).unwrap()[0].rect, visible);
33418 }
33419
33420 #[test]
33423 fn findditem_returns_zero_based_index_for_hit_and_pops_stack() {
33424 let (mut disp, mut cpu, mut bus) = setup();
33427 let dialog_ptr = 0x220000u32;
33428 disp.dialog_items.insert(
33429 dialog_ptr,
33430 vec![
33431 DialogItem {
33432 item_type: 4,
33433 rect: (10, 20, 30, 40),
33434 text: "A".into(),
33435 resource_id: 0,
33436 proc_ptr: 0,
33437 sel_start: 0,
33438 sel_end: 0,
33439 },
33440 DialogItem {
33441 item_type: 4,
33442 rect: (40, 50, 70, 90),
33443 text: "B".into(),
33444 resource_id: 0,
33445 proc_ptr: 0,
33446 sel_start: 0,
33447 sel_end: 0,
33448 },
33449 ],
33450 );
33451
33452 bus.write_word(TEST_SP, 45);
33453 bus.write_word(TEST_SP + 2, 55);
33454 bus.write_long(TEST_SP + 4, dialog_ptr);
33455 let result = disp.dispatch_dialog(true, 0x184, &mut cpu, &mut bus);
33456 assert!(result.unwrap().is_ok());
33457 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 8);
33458 assert_eq!(bus.read_word(TEST_SP + 8) as i16, 1);
33459 }
33460
33461 #[test]
33462 fn findditem_point_outside_all_items_returns_minus_one() {
33463 let (mut disp, mut cpu, mut bus) = setup();
33466 let dialog_ptr = 0x220100u32;
33467 disp.dialog_items.insert(
33468 dialog_ptr,
33469 vec![DialogItem {
33470 item_type: 4,
33471 rect: (10, 20, 30, 40),
33472 text: "A".into(),
33473 resource_id: 0,
33474 proc_ptr: 0,
33475 sel_start: 0,
33476 sel_end: 0,
33477 }],
33478 );
33479
33480 bus.write_word(TEST_SP, 99);
33481 bus.write_word(TEST_SP + 2, 99);
33482 bus.write_long(TEST_SP + 4, dialog_ptr);
33483 let result = disp.dispatch_dialog(true, 0x184, &mut cpu, &mut bus);
33484 assert!(result.unwrap().is_ok());
33485 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 8);
33486 assert_eq!(bus.read_word(TEST_SP + 8) as i16, -1);
33487 }
33488
33489 #[test]
33490 fn findditem_returns_first_overlapping_item_and_includes_disabled_items() {
33491 let (mut disp, mut cpu, mut bus) = setup();
33494 let dialog_ptr = 0x220200u32;
33495 disp.dialog_items.insert(
33496 dialog_ptr,
33497 vec![
33498 DialogItem {
33499 item_type: 0x80 | 4, rect: (10, 20, 40, 60),
33501 text: "DisabledTop".into(),
33502 resource_id: 0,
33503 proc_ptr: 0,
33504 sel_start: 0,
33505 sel_end: 0,
33506 },
33507 DialogItem {
33508 item_type: 4,
33509 rect: (20, 30, 50, 70),
33510 text: "EnabledBottom".into(),
33511 resource_id: 0,
33512 proc_ptr: 0,
33513 sel_start: 0,
33514 sel_end: 0,
33515 },
33516 ],
33517 );
33518
33519 bus.write_word(TEST_SP, 25);
33521 bus.write_word(TEST_SP + 2, 35);
33522 bus.write_long(TEST_SP + 4, dialog_ptr);
33523 let result = disp.dispatch_dialog(true, 0x184, &mut cpu, &mut bus);
33524 assert!(result.unwrap().is_ok());
33525 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 8);
33526 assert_eq!(bus.read_word(TEST_SP + 8) as i16, 0);
33527 }
33528
33529 #[test]
33534 fn init_cursor_resets_cursor_level_to_zero_and_sets_arrow_visible() {
33535 let (mut disp, mut cpu, mut bus) = setup();
33538
33539 disp.cursor_data = None;
33541 disp.cursor_level = -3;
33542 disp.cursor_visible = false;
33543
33544 let result = disp.dispatch_dialog(true, 0x050, &mut cpu, &mut bus);
33545 assert!(result.unwrap().is_ok());
33546 assert!(disp.cursor_data.is_some());
33547 assert_eq!(disp.cursor_level, 0);
33548 assert!(disp.cursor_visible);
33549 }
33550
33551 #[test]
33554 fn set_cursor_reads_cursor_data_and_pops_4_bytes() {
33555 let (mut disp, mut cpu, mut bus) = setup();
33556
33557 let crsr_ptr = 0x300000u32;
33558
33559 for i in 0..32u32 {
33561 bus.write_byte(crsr_ptr + i, 0xAA);
33562 }
33563 for i in 0..32u32 {
33565 bus.write_byte(crsr_ptr + 32 + i, 0xFF);
33566 }
33567 bus.write_word(crsr_ptr + 64, 5); bus.write_word(crsr_ptr + 66, 3); bus.write_long(TEST_SP, crsr_ptr);
33573
33574 let result = disp.dispatch_dialog(true, 0x051, &mut cpu, &mut bus);
33575 assert!(result.unwrap().is_ok());
33576 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 4);
33577
33578 let (data, mask, hot_v, hot_h) = disp.cursor_data().unwrap();
33579 assert!(data.iter().all(|&b| b == 0xAA));
33580 assert!(mask.iter().all(|&b| b == 0xFF));
33581 assert_eq!(hot_v, 5);
33582 assert_eq!(hot_h, 3);
33583 }
33584
33585 #[test]
33586 fn set_cursor_does_not_force_visibility_when_cursor_is_hidden() {
33587 let (mut disp, mut cpu, mut bus) = setup();
33590 let crsr_ptr = 0x300100u32;
33591 for i in 0..32u32 {
33592 bus.write_byte(crsr_ptr + i, 0x11);
33593 bus.write_byte(crsr_ptr + 32 + i, 0x22);
33594 }
33595 bus.write_word(crsr_ptr + 64, 9);
33596 bus.write_word(crsr_ptr + 66, 4);
33597 bus.write_long(TEST_SP, crsr_ptr);
33598
33599 disp.cursor_level = -1;
33600 disp.cursor_visible = false;
33601 let result = disp.dispatch_dialog(true, 0x051, &mut cpu, &mut bus);
33602 assert!(result.unwrap().is_ok());
33603 assert_eq!(disp.cursor_level, -1);
33604 assert!(!disp.cursor_visible);
33605 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 4);
33606 }
33607
33608 #[test]
33611 fn hide_cursor_decrements_level_and_hides_cursor() {
33612 let (mut disp, mut cpu, mut bus) = setup();
33615 disp.cursor_level = 0;
33616 disp.cursor_visible = true;
33617
33618 let result = disp.dispatch_dialog(true, 0x052, &mut cpu, &mut bus);
33619 assert!(result.unwrap().is_ok());
33620 assert_eq!(disp.cursor_level, -1);
33621 assert!(!disp.cursor_visible);
33622 }
33623
33624 #[test]
33627 fn show_cursor_balances_hidecursor_and_reveals_at_level_zero() {
33628 let (mut disp, mut cpu, mut bus) = setup();
33631 disp.cursor_level = -2;
33632 disp.cursor_visible = false;
33633
33634 let result = disp.dispatch_dialog(true, 0x053, &mut cpu, &mut bus);
33635 assert!(result.unwrap().is_ok());
33636 assert_eq!(disp.cursor_level, -1);
33637 assert!(!disp.cursor_visible);
33638
33639 let result = disp.dispatch_dialog(true, 0x053, &mut cpu, &mut bus);
33640 assert!(result.unwrap().is_ok());
33641 assert_eq!(disp.cursor_level, 0);
33642 assert!(disp.cursor_visible);
33643 }
33644
33645 #[test]
33646 fn show_cursor_at_level_zero_is_noop() {
33647 let (mut disp, mut cpu, mut bus) = setup();
33650 disp.cursor_level = 0;
33651 disp.cursor_visible = true;
33652
33653 let result = disp.dispatch_dialog(true, 0x053, &mut cpu, &mut bus);
33654 assert!(result.unwrap().is_ok());
33655 assert_eq!(disp.cursor_level, 0);
33656 assert!(disp.cursor_visible);
33657 }
33658
33659 #[test]
33662 fn obscure_cursor_noop_preserves_cursor_level_visibility_and_stack() {
33663 let (mut disp, mut cpu, mut bus) = setup();
33667 disp.cursor_level = -1;
33668 disp.cursor_visible = false;
33669 let sp_before = cpu.read_reg(Register::A7);
33670
33671 let result = disp.dispatch_dialog(true, 0x056, &mut cpu, &mut bus);
33672 assert!(result.unwrap().is_ok());
33673 assert_eq!(cpu.read_reg(Register::A7), sp_before);
33674 assert_eq!(disp.cursor_level, -1);
33675 assert!(!disp.cursor_visible);
33676 }
33677
33678 #[test]
33679 fn obscure_cursor_five_call_composition_preserves_stack_pointer_and_level() {
33680 let (mut disp, mut cpu, mut bus) = setup();
33688 disp.cursor_level = -2;
33689 disp.cursor_visible = false;
33690 let sp_before = cpu.read_reg(Register::A7);
33691
33692 for i in 0..5 {
33693 let result = disp.dispatch_dialog(true, 0x056, &mut cpu, &mut bus);
33694 assert!(result.unwrap().is_ok(), "iteration {i} dispatch failed");
33695 assert_eq!(
33696 cpu.read_reg(Register::A7),
33697 sp_before,
33698 "iteration {i}: ObscureCursor must leave SP unchanged (0-byte pop)",
33699 );
33700 assert_eq!(
33701 disp.cursor_level, -2,
33702 "iteration {i}: ObscureCursor must NOT change cursor level per IM:I I-168",
33703 );
33704 }
33705 }
33706
33707 #[test]
33710 fn get_cursor_returns_handle_for_system_cursor() {
33711 let (mut disp, mut cpu, mut bus) = setup();
33718 bus.write_word(TEST_SP, 2); let _ = disp.dispatch_dialog(true, 0x1B9, &mut cpu, &mut bus);
33721 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 2);
33722
33723 let handle = bus.read_long(TEST_SP + 2);
33724 assert_ne!(
33725 handle, 0,
33726 "GetCursor(crossCursor) must return non-NIL handle for a built-in system cursor"
33727 );
33728 let crsr = bus.read_long(handle);
33729 let hot_v = bus.read_word(crsr + 64) as i16;
33730 let hot_h = bus.read_word(crsr + 66) as i16;
33731 assert_eq!(
33732 (hot_v, hot_h),
33733 (7, 7),
33734 "crossCursor's hotspot is documented at (7,7) in IM:I I-475"
33735 );
33736 }
33737
33738 #[test]
33739 fn get_cursor_returns_nil_for_unknown_id_per_im_i_474() {
33740 let (mut disp, mut cpu, mut bus) = setup();
33748 bus.write_word(TEST_SP, 999); let _ = disp.dispatch_dialog(true, 0x1B9, &mut cpu, &mut bus);
33751 assert_eq!(
33752 cpu.read_reg(Register::A7),
33753 TEST_SP + 2,
33754 "GetCursor must pop 2 bytes (cursorID INTEGER)"
33755 );
33756 assert_eq!(
33757 bus.read_long(TEST_SP + 2),
33758 0,
33759 "GetCursor must return NIL when CURS resource is missing per IM:I I-474"
33760 );
33761 }
33762
33763 #[test]
33764 fn get_cursor_returns_stable_handle_across_repeated_calls_for_system_cursor() {
33765 let (mut disp, mut cpu, mut bus) = setup();
33771 bus.write_word(TEST_SP, 4); let _ = disp.dispatch_dialog(true, 0x1B9, &mut cpu, &mut bus);
33774 let h1 = bus.read_long(TEST_SP + 2);
33775 cpu.write_reg(Register::A7, TEST_SP);
33776 bus.write_word(TEST_SP, 4);
33777
33778 let _ = disp.dispatch_dialog(true, 0x1B9, &mut cpu, &mut bus);
33779 let h2 = bus.read_long(TEST_SP + 2);
33780 assert_eq!(
33781 h1, h2,
33782 "GetCursor must return the same handle for repeated calls on a system cursor ID"
33783 );
33784 }
33785
33786 #[test]
33787 fn get_cursor_returns_handle_to_loaded_curs_resource() {
33788 let (mut disp, mut cpu, mut bus) = setup();
33792 let curs_data: Vec<u8> = (0..68).map(|i| (i as u8).wrapping_mul(11)).collect();
33794 let res_ptr = disp.install_test_resource(&mut bus, *b"CURS", 200, &curs_data);
33795 bus.write_word(TEST_SP, 200);
33796
33797 let _ = disp.dispatch_dialog(true, 0x1B9, &mut cpu, &mut bus);
33798 let handle = bus.read_long(TEST_SP + 2);
33799 assert_ne!(
33800 handle, 0,
33801 "GetCursor must return non-NIL handle when CURS resource is loaded"
33802 );
33803 assert_eq!(
33804 bus.read_long(handle),
33805 res_ptr,
33806 "GetCursor's handle must dereference to the loaded CURS resource bytes"
33807 );
33808 }
33809
33810 #[test]
33813 fn get_pattern_returns_nil_for_missing_resource_per_im_i_473() {
33814 let (mut disp, mut cpu, mut bus) = setup();
33820 bus.write_word(TEST_SP, 1); let _ = disp.dispatch_dialog(true, 0x1B8, &mut cpu, &mut bus);
33823 assert_eq!(
33824 cpu.read_reg(Register::A7),
33825 TEST_SP + 2,
33826 "GetPattern must pop 2 bytes (patID INTEGER)"
33827 );
33828 assert_eq!(
33829 bus.read_long(TEST_SP + 2),
33830 0,
33831 "GetPattern must return NIL when PAT resource is missing per IM:I I-473"
33832 );
33833 }
33834
33835 #[test]
33836 fn get_pattern_returns_handle_to_loaded_pat_resource() {
33837 let (mut disp, mut cpu, mut bus) = setup();
33844 let pat_data: [u8; 8] = [0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA];
33845 let res_ptr = disp.install_test_resource(&mut bus, *b"PAT ", 16, &pat_data);
33846 bus.write_word(TEST_SP, 16);
33847
33848 let _ = disp.dispatch_dialog(true, 0x1B8, &mut cpu, &mut bus);
33849 let handle = bus.read_long(TEST_SP + 2);
33850 assert_ne!(
33851 handle, 0,
33852 "GetPattern must return non-NIL handle when PAT resource is loaded"
33853 );
33854 assert_eq!(
33855 bus.read_long(handle),
33856 res_ptr,
33857 "GetPattern's handle must dereference to the loaded PAT resource bytes \
33858 (not a fresh all-0xFF allocation)"
33859 );
33860 let master = bus.read_long(handle);
33863 for (i, want) in pat_data.iter().enumerate() {
33864 assert_eq!(
33865 bus.read_byte(master + i as u32),
33866 *want,
33867 "GetPattern PAT byte +{i} must match installed resource byte"
33868 );
33869 }
33870 }
33871
33872 #[test]
33873 fn get_pattern_returns_stable_handle_across_repeated_calls() {
33874 let (mut disp, mut cpu, mut bus) = setup();
33879 let pat_data: [u8; 8] = [0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0];
33880 let _ = disp.install_test_resource(&mut bus, *b"PAT ", 100, &pat_data);
33881
33882 bus.write_word(TEST_SP, 100);
33883 let _ = disp.dispatch_dialog(true, 0x1B8, &mut cpu, &mut bus);
33884 let h1 = bus.read_long(TEST_SP + 2);
33885 cpu.write_reg(Register::A7, TEST_SP);
33886 bus.write_word(TEST_SP, 100);
33887
33888 let _ = disp.dispatch_dialog(true, 0x1B8, &mut cpu, &mut bus);
33889 let h2 = bus.read_long(TEST_SP + 2);
33890 assert_eq!(
33891 h1, h2,
33892 "GetPattern must return the same handle for repeated calls on a loaded PAT resource"
33893 );
33894 }
33895
33896 #[test]
33899 fn get_icon_returns_nil_when_resource_missing() {
33900 let (mut disp, mut cpu, mut bus) = setup();
33906 bus.write_word(TEST_SP, 1); let _ = disp.dispatch_dialog(true, 0x1BB, &mut cpu, &mut bus);
33908 assert_eq!(
33909 cpu.read_reg(Register::A7),
33910 TEST_SP + 2,
33911 "GetIcon must pop 2 bytes (iconID INTEGER)"
33912 );
33913 assert_eq!(
33914 bus.read_long(TEST_SP + 2),
33915 0,
33916 "GetIcon must return NIL when ICON resource is missing per IM:I I-473"
33917 );
33918 }
33919
33920 #[test]
33921 fn get_icon_returns_handle_to_loaded_icon_resource() {
33922 let (mut disp, mut cpu, mut bus) = setup();
33928 let icon_data: Vec<u8> = (0..128).map(|i| (i as u8).wrapping_mul(7)).collect();
33932 let res_ptr = disp.install_test_resource(&mut bus, *b"ICON", 128, &icon_data);
33933 bus.write_word(TEST_SP, 128);
33934
33935 let _ = disp.dispatch_dialog(true, 0x1BB, &mut cpu, &mut bus);
33936 let handle = bus.read_long(TEST_SP + 2);
33937 assert_ne!(
33938 handle, 0,
33939 "GetIcon must return a non-NIL handle when ICON resource is loaded"
33940 );
33941 assert_eq!(
33942 bus.read_long(handle),
33943 res_ptr,
33944 "GetIcon's handle must dereference to the loaded ICON resource bytes \
33945 (not a fresh uninitialised allocation)"
33946 );
33947 let master = bus.read_long(handle);
33950 for (i, want) in icon_data.iter().enumerate() {
33951 assert_eq!(
33952 bus.read_byte(master + i as u32),
33953 *want,
33954 "GetIcon ICON byte +{i} must match installed resource byte"
33955 );
33956 }
33957 }
33958
33959 #[test]
33960 fn get_icon_returns_stable_handle_across_repeated_calls() {
33961 let (mut disp, mut cpu, mut bus) = setup();
33967 let icon_data: Vec<u8> = vec![0xCC; 128];
33968 disp.install_test_resource(&mut bus, *b"ICON", 200, &icon_data);
33969
33970 bus.write_word(TEST_SP, 200);
33971 let _ = disp.dispatch_dialog(true, 0x1BB, &mut cpu, &mut bus);
33972 let h1 = bus.read_long(TEST_SP + 2);
33973
33974 cpu.write_reg(Register::A7, TEST_SP);
33976 bus.write_word(TEST_SP, 200);
33977 let _ = disp.dispatch_dialog(true, 0x1BB, &mut cpu, &mut bus);
33978 let h2 = bus.read_long(TEST_SP + 2);
33979
33980 assert_ne!(h1, 0, "first GetIcon must return non-NIL");
33981 assert_eq!(
33982 h1, h2,
33983 "GetIcon must return the SAME handle on repeated calls per IM:Resource Mgr stability"
33984 );
33985 }
33986
33987 #[test]
33988 fn get_icon_pops_two_bytes_per_pascal_signature() {
33989 let (mut disp, mut cpu, mut bus) = setup();
33994 bus.write_word(TEST_SP, 999); let pre_a7 = cpu.read_reg(Register::A7);
33996 let _ = disp.dispatch_dialog(true, 0x1BB, &mut cpu, &mut bus);
33997 assert_eq!(
33998 cpu.read_reg(Register::A7),
33999 pre_a7 + 2,
34000 "GetIcon must advance A7 by 2 bytes (iconID INTEGER)"
34001 );
34002 }
34003
34004 #[test]
34007 fn get_picture_returns_nil_without_resource() {
34008 let (mut disp, mut cpu, mut bus) = setup();
34009
34010 bus.write_word(TEST_SP, 1);
34012
34013 let result = disp.dispatch_dialog(true, 0x1BC, &mut cpu, &mut bus);
34014 assert!(result.unwrap().is_ok());
34015 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 2);
34016
34017 let handle = bus.read_long(TEST_SP + 2);
34018 assert_eq!(handle, 0); }
34020
34021 #[test]
34022 fn get_picture_reloads_released_resource_from_open_resource_file() {
34023 fn make_resource_fork_bytes(resources: &[([u8; 4], i16, &[u8])]) -> Vec<u8> {
34024 let mut type_groups: Vec<([u8; 4], Vec<(i16, &[u8], u32)>)> = Vec::new();
34025 for (res_type, res_id, data) in resources {
34026 let group_idx = type_groups
34027 .iter()
34028 .position(|(existing_type, _)| existing_type == res_type)
34029 .unwrap_or_else(|| {
34030 type_groups.push((*res_type, Vec::new()));
34031 type_groups.len() - 1
34032 });
34033 type_groups[group_idx].1.push((*res_id, *data, 0));
34034 }
34035 type_groups.sort_by_key(|(res_type, _)| *res_type);
34036 for (_, entries) in &mut type_groups {
34037 entries.sort_by_key(|(res_id, _, _)| *res_id);
34038 }
34039
34040 let data_offset = 16u32;
34041 let mut data_section = Vec::new();
34042 for (_, entries) in &mut type_groups {
34043 for (_, data, data_pos) in entries {
34044 *data_pos = data_section.len() as u32;
34045 data_section.extend_from_slice(&(data.len() as u32).to_be_bytes());
34046 data_section.extend_from_slice(data);
34047 }
34048 }
34049
34050 let map_offset = data_offset + data_section.len() as u32;
34051 let type_list_offset = 30u16;
34052 let type_count = type_groups.len();
34053 let resource_count: usize = type_groups.iter().map(|(_, entries)| entries.len()).sum();
34054 let ref_lists_offset = 2 + type_count * 8;
34055 let name_list_offset =
34056 type_list_offset as usize + ref_lists_offset + resource_count * 12;
34057 let map_length = name_list_offset as u32;
34058
34059 let mut bytes = vec![0u8; (map_offset + map_length) as usize];
34060 let mut header = [0u8; 16];
34061 header[0..4].copy_from_slice(&data_offset.to_be_bytes());
34062 header[4..8].copy_from_slice(&map_offset.to_be_bytes());
34063 header[8..12].copy_from_slice(&(data_section.len() as u32).to_be_bytes());
34064 header[12..16].copy_from_slice(&map_length.to_be_bytes());
34065 bytes[0..16].copy_from_slice(&header);
34066 bytes[data_offset as usize..data_offset as usize + data_section.len()]
34067 .copy_from_slice(&data_section);
34068
34069 let map_start = map_offset as usize;
34070 bytes[map_start..map_start + 16].copy_from_slice(&header);
34071 bytes[map_start + 24..map_start + 26].copy_from_slice(&type_list_offset.to_be_bytes());
34072 bytes[map_start + 26..map_start + 28]
34073 .copy_from_slice(&(name_list_offset as u16).to_be_bytes());
34074 bytes[map_start + 28..map_start + 30]
34075 .copy_from_slice(&((type_count as u16) - 1).to_be_bytes());
34076
34077 let type_list_start = map_start + type_list_offset as usize;
34078 bytes[type_list_start..type_list_start + 2]
34079 .copy_from_slice(&((type_count as u16) - 1).to_be_bytes());
34080 let mut next_ref_list_offset = ref_lists_offset;
34081 for (i, (res_type, entries)) in type_groups.iter().enumerate() {
34082 let type_entry = type_list_start + 2 + i * 8;
34083 bytes[type_entry..type_entry + 4].copy_from_slice(res_type);
34084 bytes[type_entry + 4..type_entry + 6]
34085 .copy_from_slice(&((entries.len() as u16) - 1).to_be_bytes());
34086 bytes[type_entry + 6..type_entry + 8]
34087 .copy_from_slice(&(next_ref_list_offset as u16).to_be_bytes());
34088
34089 let ref_list_start = type_list_start + next_ref_list_offset;
34090 for (j, (res_id, _, data_pos)) in entries.iter().enumerate() {
34091 let ref_entry = ref_list_start + j * 12;
34092 bytes[ref_entry..ref_entry + 2]
34093 .copy_from_slice(&(*res_id as u16).to_be_bytes());
34094 bytes[ref_entry + 2..ref_entry + 4].copy_from_slice(&0xFFFFu16.to_be_bytes());
34095 bytes[ref_entry + 4] = 0;
34096 let data_offset_bytes = data_pos.to_be_bytes();
34097 bytes[ref_entry + 5..ref_entry + 8].copy_from_slice(&data_offset_bytes[1..4]);
34098 }
34099
34100 next_ref_list_offset += entries.len() * 12;
34101 }
34102
34103 bytes
34104 }
34105
34106 let (mut disp, mut cpu, mut bus) = setup();
34107 let pict_data = b"\x00\x11Wordtris reloadable picture data";
34108 let fork = make_resource_fork_bytes(&[(*b"PICT", 1127, &pict_data[..])]);
34109 disp.vfs_rsrc.insert("Pictures".to_string(), fork);
34110 disp.open_resource_file_from_vfs_key(&mut bus, "Pictures", false);
34111
34112 bus.write_word(TEST_SP, 1127);
34113 let first = disp.dispatch_dialog(true, 0x1BC, &mut cpu, &mut bus);
34114 assert!(first.unwrap().is_ok());
34115 let first_handle = bus.read_long(TEST_SP + 2);
34116 assert_ne!(first_handle, 0, "first GetPicture must find PICT 1127");
34117 let first_ptr = bus.read_long(first_handle);
34118 assert_eq!(bus.read_bytes(first_ptr, pict_data.len()), pict_data);
34119
34120 cpu.write_reg(Register::A7, TEST_SP);
34121 bus.write_long(TEST_SP, first_handle);
34122 let release = disp.dispatch_resource(true, 0x1A3, &mut cpu, &mut bus);
34123 assert!(release.unwrap().is_ok());
34124 assert_eq!(
34125 bus.read_long(first_handle),
34126 0,
34127 "ReleaseResource should clear the old master pointer"
34128 );
34129
34130 cpu.write_reg(Register::A7, TEST_SP);
34131 bus.write_word(TEST_SP, 1127);
34132 let second = disp.dispatch_dialog(true, 0x1BC, &mut cpu, &mut bus);
34133 assert!(second.unwrap().is_ok());
34134 let second_handle = bus.read_long(TEST_SP + 2);
34135 assert_ne!(
34136 second_handle, 0,
34137 "GetPicture must reload a released PICT from the open resource file"
34138 );
34139 assert_ne!(
34140 second_handle, first_handle,
34141 "a released resource should be returned through a fresh handle"
34142 );
34143 let second_ptr = bus.read_long(second_handle);
34144 assert_eq!(bus.read_bytes(second_ptr, pict_data.len()), pict_data);
34145 }
34146
34147 #[test]
34150 fn get_string_returns_loaded_str_resource_handle() {
34151 let (mut disp, mut cpu, mut bus) = setup();
34152 let str_ptr = bus.alloc(6);
34153 bus.write_byte(str_ptr, 5);
34154 bus.write_bytes(str_ptr + 1, b"Hello");
34155 disp.resources = Some(crate::trap::dispatch::LoadedResources {
34156 files: std::collections::HashMap::from([(
34157 0,
34158 crate::trap::dispatch::ResourceFileMap {
34159 loaded: std::collections::HashMap::from([((*b"STR ", 1), str_ptr)]),
34160 named: std::collections::HashMap::new(),
34161 names_by_id: std::collections::HashMap::new(),
34162 attrs: std::collections::HashMap::new(),
34163 map_attrs: 0,
34164 },
34165 )]),
34166 names: std::collections::HashMap::new(),
34167 search_order: vec![0],
34168 current_file: 0,
34169 });
34170
34171 bus.write_word(TEST_SP, 1);
34173
34174 let result = disp.dispatch_dialog(true, 0x1BA, &mut cpu, &mut bus);
34175 assert!(result.unwrap().is_ok());
34176 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 2);
34177
34178 let handle = bus.read_long(TEST_SP + 2);
34179 assert_ne!(handle, 0);
34180 assert_eq!(bus.read_long(handle), str_ptr);
34181 }
34182
34183 #[test]
34184 fn get_string_returns_nil_when_resource_is_missing() {
34185 let (mut disp, mut cpu, mut bus) = setup();
34186 bus.write_word(TEST_SP, 999);
34187
34188 let result = disp.dispatch_dialog(true, 0x1BA, &mut cpu, &mut bus);
34189 assert!(result.unwrap().is_ok());
34190 assert_eq!(cpu.read_reg(Register::A7), TEST_SP + 2);
34191 assert_eq!(bus.read_long(TEST_SP + 2), 0);
34192 }
34193
34194 #[test]
34197 fn unhandled_trap_returns_none() {
34198 let (mut disp, mut cpu, mut bus) = setup();
34199
34200 let result = disp.dispatch_dialog(true, 0xFFFF, &mut cpu, &mut bus);
34201 assert!(result.is_none());
34202 }
34203}