quadraui 0.2.0

Cross-platform Rust UI toolkit: write one AppLogic and run it unmodified on TUI, GTK4 (Linux), macOS, and Windows backends
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
//! Headless Direct2D test surface.
//!
//! Per `docs/TESTING.md`'s backend-testability requirement, every backend
//! must support paint-to-memory so tests don't need a real display —
//! `gtk::testing`/`gtk::multi_section_view::tests` use a headless
//! `cairo::ImageSurface`, `tui::split::tests` paints into a `ratatui::Buffer`.
//! [`HeadlessSurface`] is the Win-GUI equivalent: a real `ID2D1RenderTarget`
//! that paints into an in-process pixel buffer, no `HWND`/`WM_PAINT`/GPU
//! required.
//!
//! # Why a DC render target, not `CreateWicBitmapRenderTarget`
//!
//! A DC render target uses GDI (`Win32_Graphics_Gdi`, already pulled in by the
//! `win` feature) plus an in-memory device context bound to a `CreateDIBSection`
//! bitmap. `CreateDIBSection` hands back a raw pointer to the pixel buffer
//! directly ([`HeadlessSurface::pixel_at`]), so reading a pixel back is a
//! pointer offset — no copy step required.
//!
//! # Software, not hardware
//!
//! [`D2D1_RENDER_TARGET_TYPE_SOFTWARE`] forces Direct2D's WARP software
//! rasteriser — no GPU, no driver, no display adapter needed, only a CPU.
//! `CreateCompatibleDC(None)` still asks GDI for a DC, but that is a
//! virtual/basic display device every Windows host has (including headless CI
//! runners) — it does not require a physically attached monitor.
//!
//! # `feature = "win"` alone — not `target_os = "windows"`
//!
//! Every real Direct2D/GDI call in here is individually
//! `cfg(target_os = "windows")`-gated with a non-functional fallback
//! everywhere else — the same "compiles-everywhere, only *works* on
//! Windows" posture `backend.rs`/`run.rs`/`shell_runner.rs` already use
//! (see `super`'s module docs and `Cargo.toml`'s `win` feature comment).
//! Concretely: [`HeadlessSurface::new`] always returns `Err` off Windows
//! (there is no non-Windows Direct2D to construct one from), so
//! [`WinDriver::new`]'s `.expect(..)` panics if it is ever actually
//! *run* off Windows — but nothing does that: every call site that
//! constructs a `WinDriver` is itself gated to `target_os = "windows"`, since a
//! headless Direct2D surface is the whole point of the exercise. Off Windows
//! this module buys exactly one thing — type-checking.
//!
//! `pub` (like [`crate::tui::testing`] and [`crate::gtk::testing`])
//! rather than `pub(crate)`, both to match those siblings' convention and
//! so a plain `cargo build`/`clippy --features win` (which compiles this
//! module as part of the library regardless of whether any
//! `#[cfg(test)]` block currently calls it) never flags it as dead code.

#[cfg(target_os = "windows")]
use windows::core::Result as WinResult;
#[cfg(target_os = "windows")]
use windows::Win32::Foundation::RECT;
#[cfg(target_os = "windows")]
use windows::Win32::Graphics::Direct2D::Common::{
    D2D1_ALPHA_MODE_IGNORE, D2D1_COLOR_F, D2D1_PIXEL_FORMAT, D2D_RECT_F,
};
#[cfg(target_os = "windows")]
use windows::Win32::Graphics::Direct2D::{
    D2D1CreateFactory, ID2D1DCRenderTarget, ID2D1Factory, D2D1_FACTORY_TYPE_SINGLE_THREADED,
    D2D1_RENDER_TARGET_PROPERTIES, D2D1_RENDER_TARGET_TYPE_SOFTWARE,
};
#[cfg(target_os = "windows")]
use windows::Win32::Graphics::Dxgi::Common::DXGI_FORMAT_B8G8R8A8_UNORM;
#[cfg(target_os = "windows")]
use windows::Win32::Graphics::Gdi::{
    CreateCompatibleDC, CreateDIBSection, DeleteDC, DeleteObject, SelectObject, BITMAPINFO,
    BITMAPINFOHEADER, BI_RGB, DIB_RGB_COLORS, HBITMAP, HDC,
};

/// [`windows::core::Result`] on Windows. Off Windows the `windows` crate
/// isn't even a dependency (`Cargo.toml` restricts it to
/// `[target.'cfg(target_os = "windows")'.dependencies]`), so this is a
/// minimal unit-error stand-in there instead — see the module doc for
/// why every [`HeadlessSurface`] method that returns this only needs to
/// *type-check* off Windows, never to succeed.
#[cfg(not(target_os = "windows"))]
pub type WinResult<T> = Result<T, HeadlessSurfaceUnavailable>;

/// The error every fallible [`HeadlessSurface`]/[`WinDriver`] method
/// returns off Windows — see the module doc's "`feature = "win"` alone"
/// section. There is no non-Windows Direct2D to construct a real surface
/// from, so this is what `Err` carries instead of ever being `Ok`.
#[cfg(not(target_os = "windows"))]
#[derive(Debug)]
pub struct HeadlessSurfaceUnavailable;

#[cfg(not(target_os = "windows"))]
impl std::fmt::Display for HeadlessSurfaceUnavailable {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "HeadlessSurface: Direct2D is only available on target_os = \"windows\""
        )
    }
}

#[cfg(not(target_os = "windows"))]
impl std::error::Error for HeadlessSurfaceUnavailable {}

use crate::event::{Rect, Viewport};
use crate::runner::{AppLogic, Reaction};
use crate::shell::{ShellApp, ShellConfig};
use crate::testing::driver_core::DriverCore;
use crate::testing::{
    Anchor, ConformanceDriver, DriverInput, FrameInventory, LogicalViewport, PixelClickConformance,
};
// `InteractionState` is deliberately *not* imported here: the only use site
// is in `#[cfg(test)]` below, and a non-test `cargo clippy --features win`
// (which does compile this module, but not its test block) would flag the
// import as unused under `-D warnings`. The test refers to it as
// `crate::InteractionState`, matching `macos/testing.rs`.
use crate::{ButtonMask, Color, Key, Modifiers, MouseButton, NamedKey, Point, UiEvent};

use super::backend::WinBackend;
use super::run::{
    dispatch_event, render_frame, route_mouse_down, route_mouse_move, route_mouse_up, EventOutcome,
};

/// An offscreen `ID2D1DCRenderTarget` bound to a top-down 32bpp DIB
/// section. Construct with [`Self::new`], paint with [`Self::paint`] (or
/// the [`Self::fill_rect`] convenience for the common case), read a
/// painted pixel back with [`Self::pixel_at`].
///
/// Deliberately *not* wired into [`super::backend::WinBackend`] itself:
/// that backend's `Surface` is built around `ID2D1HwndRenderTarget`
/// (window-bound) and every `Backend::draw_*` rasteriser is still a
/// `todo!()` stub (see `backend.rs`'s module docs) — there is nothing yet
/// for a `WinBackend`-level driver to paint. `HeadlessSurface` is the
/// lower-level primitive future rasteriser tests build on, the same role
/// `cairo::ImageSurface` played for the GTK backend before `GtkDriver`
/// existed.
pub struct HeadlessSurface {
    /// Kept alive only because `ID2D1DCRenderTarget` was created from it.
    #[cfg(target_os = "windows")]
    #[allow(dead_code)]
    factory: ID2D1Factory,
    #[cfg(target_os = "windows")]
    target: ID2D1DCRenderTarget,
    #[cfg(target_os = "windows")]
    hdc: HDC,
    #[cfg(target_os = "windows")]
    bitmap: HBITMAP,
    /// Raw pointer into the `CreateDIBSection` pixel buffer — valid for
    /// as long as `bitmap` lives, i.e. for the lifetime of `self`.
    #[cfg(target_os = "windows")]
    bits: *mut u8,
    width: u32,
    height: u32,
}

impl HeadlessSurface {
    /// Create a `width` x `height` (device pixels, clamped to at least
    /// `1x1`) headless render target.
    ///
    /// Off Windows this always returns `Err` — there is no Direct2D to
    /// build a real one from (`windows` isn't even a dependency there,
    /// see the module doc's "`feature = "win"` alone" section) — so a
    /// caller that unconditionally `.expect()`s this (e.g.
    /// [`WinDriver::new`]) will panic if actually run off Windows. Every
    /// in-tree call site that does that is itself gated to
    /// `target_os = "windows"`, so this never fires in practice; it
    /// exists purely so the *signature* type-checks everywhere.
    pub fn new(width: u32, height: u32) -> WinResult<Self> {
        #[cfg(target_os = "windows")]
        {
            let width = width.max(1);
            let height = height.max(1);

            let factory: ID2D1Factory =
                // SAFETY: `D2D1CreateFactory` takes no pointers here beyond
                // `None` for the optional factory options; the returned
                // `ID2D1Factory` is a COM interface this struct owns for its
                // whole lifetime (released via `windows-rs`'s `Drop`).
                unsafe { D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, None)? };

            // `D2D1_ALPHA_MODE_IGNORE`: DC render targets only support
            // `IGNORE` or `PREMULTIPLIED` (MSDN), and nothing here composites
            // partial transparency, so `IGNORE` avoids any premultiply step
            // between `fill_rect`'s solid colour and the DIB's stored bytes.
            let render_props = D2D1_RENDER_TARGET_PROPERTIES {
                r#type: D2D1_RENDER_TARGET_TYPE_SOFTWARE,
                pixelFormat: D2D1_PIXEL_FORMAT {
                    format: DXGI_FORMAT_B8G8R8A8_UNORM,
                    alphaMode: D2D1_ALPHA_MODE_IGNORE,
                },
                ..Default::default()
            };
            // SAFETY: `factory` is the live interface just created above;
            // `render_props` is a plain stack struct borrowed for the
            // duration of this synchronous call. The returned
            // `ID2D1DCRenderTarget` is stored in `Self::target`, kept
            // alive alongside `factory` for the struct's whole lifetime.
            let target = unsafe { factory.CreateDCRenderTarget(&render_props)? };

            // Negative `biHeight` = top-down DIB, so `pixel_at`'s row math
            // matches on-screen row order (row 0 = top) with no vertical
            // flip — GDI only allows this for uncompressed (`BI_RGB`) DIBs,
            // which is what we're creating.
            let bmi = BITMAPINFO {
                bmiHeader: BITMAPINFOHEADER {
                    biSize: std::mem::size_of::<BITMAPINFOHEADER>() as u32,
                    biWidth: width as i32,
                    biHeight: -(height as i32),
                    biPlanes: 1,
                    biBitCount: 32,
                    biCompression: BI_RGB.0,
                    ..Default::default()
                },
                ..Default::default()
            };

            // SAFETY: `None` asks for a memory DC compatible with the
            // current screen — a virtual/basic display device every host
            // has (see the module doc's "Software, not hardware" section
            // on why this needs no physically attached monitor). `hdc` is
            // freed on every path below (both `Err` arms explicitly, and
            // `Self::drop` on the success path).
            let hdc = unsafe { CreateCompatibleDC(None) };
            let mut bits: *mut core::ffi::c_void = std::ptr::null_mut();
            // SAFETY: `hdc` was just created above; `bmi` describes a
            // top-down (negative `biHeight`) 32bpp DIB, per the
            // comment above it; `bits` is a plain stack pointer the
            // call writes through.
            let bitmap = match unsafe {
                CreateDIBSection(Some(hdc), &bmi, DIB_RGB_COLORS, &mut bits, None, 0)
            } {
                Ok(bitmap) => bitmap,
                Err(err) => {
                    // SAFETY: `hdc` is the handle created above and not
                    // used again after this — the only exit on this path.
                    unsafe {
                        let _ = DeleteDC(hdc);
                    }
                    return Err(err);
                }
            };
            // SAFETY: `hdc` and `bitmap` are both live handles from
            // above; selecting `bitmap` into `hdc` is what makes
            // `hdc`'s subsequent drawing (via `BindDC` below) target
            // this DIB's pixel buffer instead of the DC's default.
            unsafe { SelectObject(hdc, bitmap.into()) };

            let rect = RECT {
                left: 0,
                top: 0,
                right: width as i32,
                bottom: height as i32,
            };
            // SAFETY: `target` is the live render target created above;
            // `hdc` has `bitmap` selected into it (just above), so
            // `BindDC` binds Direct2D's output to that same pixel
            // buffer; `rect` is a plain stack struct borrowed for the
            // duration of this call.
            if let Err(err) = unsafe { target.BindDC(hdc, &rect) } {
                // SAFETY: `bitmap`/`hdc` are both still-live handles from
                // above; `BindDC` failed, so nothing downstream holds a
                // reference to either — this is the sole owner and the
                // only remaining chance to free them before returning
                // `Err`.
                unsafe {
                    let _ = DeleteObject(bitmap.into());
                    let _ = DeleteDC(hdc);
                }
                return Err(err);
            }

            Ok(Self {
                factory,
                target,
                hdc,
                bitmap,
                bits: bits as *mut u8,
                width,
                height,
            })
        }
        #[cfg(not(target_os = "windows"))]
        {
            let _ = (width, height);
            Err(HeadlessSurfaceUnavailable)
        }
    }

    /// `(width, height)` device pixels this surface was created with.
    pub fn size(&self) -> (u32, u32) {
        (self.width, self.height)
    }

    /// The live render target — same `ID2D1RenderTarget` surface every
    /// other backend method in this module (and, eventually, a future
    /// rasteriser test) paints against directly. Windows-only: the
    /// return type itself (`ID2D1DCRenderTarget`) has no non-Windows
    /// stand-in, unlike this module's other methods — see the module
    /// doc.
    #[cfg(target_os = "windows")]
    pub fn target(&self) -> &ID2D1DCRenderTarget {
        &self.target
    }

    /// Bracket `paint` between `BeginDraw`/`EndDraw`, same frame lifecycle
    /// as [`super::backend::WinBackend::begin_frame`]/`end_frame`. Returns
    /// the `EndDraw` result — `Err` on device loss or an invalid drawing
    /// call, matching every other fallible call in this module.
    /// Windows-only, same reasoning as [`Self::target`] above: the
    /// closure parameter's type has no non-Windows stand-in.
    #[cfg(target_os = "windows")]
    pub fn paint(&self, paint: impl FnOnce(&ID2D1DCRenderTarget)) -> WinResult<()> {
        // SAFETY: `self.target` is the live render target owned by
        // `self` for its whole lifetime; `BeginDraw`/`EndDraw` are
        // correctly paired — `EndDraw` always runs immediately after
        // `paint` returns, on every path, matching this function's own
        // doc comment on the bracketed frame lifecycle.
        unsafe { self.target.BeginDraw() };
        paint(&self.target);
        // SAFETY: pairs the `BeginDraw` immediately above; `None, None`
        // asks for no per-tag error detail, which this function's
        // return value doesn't need (callers only care about the
        // aggregate `Result`).
        unsafe { self.target.EndDraw(None, None) }
    }

    /// Fill `rect` (DIPs, target-relative) with a solid `color` — the
    /// smoke-test primitive this module's acceptance criterion asks for.
    pub fn fill_rect(&self, rect: Rect, color: Color) -> WinResult<()> {
        #[cfg(target_os = "windows")]
        {
            self.paint(|target| {
                let brush =
                    // SAFETY: `target` is the live render target
                    // `Self::paint` passes in, mid-`BeginDraw`/`EndDraw`;
                    // `color_to_d2d` builds a plain stack `D2D1_COLOR_F`
                    // borrowed only for this call; `None` for brush
                    // properties asks for the default (opaque) brush.
                    match unsafe { target.CreateSolidColorBrush(&color_to_d2d(color), None) } {
                        Ok(brush) => brush,
                        Err(_) => return,
                    };
                let rect_f = D2D_RECT_F {
                    left: rect.x,
                    top: rect.y,
                    right: rect.x + rect.width,
                    bottom: rect.y + rect.height,
                };
                // SAFETY: `target` is still the live render target from
                // above; `rect_f` is a plain stack struct and `brush` the
                // live interface just created, both borrowed only for
                // this call.
                unsafe { target.FillRectangle(&rect_f, &brush) };
            })
        }
        #[cfg(not(target_os = "windows"))]
        {
            let _ = (rect, color);
            Err(HeadlessSurfaceUnavailable)
        }
    }

    /// Read back the colour of the pixel at device-pixel coordinate
    /// `(x, y)`. `a` is always `255`: the render target was created with
    /// `D2D1_ALPHA_MODE_IGNORE` (required for DC render targets), so the
    /// DIB's alpha byte never carries meaningful coverage data.
    ///
    /// Off Windows this can't reach a real DIB at all (see the module
    /// doc), so it always reports opaque black rather than panicking —
    /// nothing off Windows calls this on a real assertion path today, but
    /// unlike `.expect()`ing a constructor, an infallible reader has no
    /// natural `Err` to return instead.
    ///
    /// # Panics
    ///
    /// On Windows, if `x >= width` or `y >= height` for the size this
    /// surface was created with.
    pub fn pixel_at(&self, x: u32, y: u32) -> Color {
        #[cfg(target_os = "windows")]
        {
            assert!(
                x < self.width && y < self.height,
                "pixel_at({x}, {y}) out of bounds for a {}x{} surface",
                self.width,
                self.height
            );
            // BGRA in memory (DXGI_FORMAT_B8G8R8A8_UNORM), 4 bytes/pixel,
            // top-down rows (see the negative `biHeight` above).
            let offset = (y as isize * self.width as isize + x as isize) * 4;
            // SAFETY: `self.bits` points at the `CreateDIBSection` pixel
            // buffer from `Self::new`, which is `4 * width * height`
            // bytes and lives as long as `self.bitmap` does — i.e. for
            // the lifetime of `self` (see `Self::bits`'s field doc).
            // `x < self.width && y < self.height` is asserted just
            // above, so `offset` and `offset + 3` (the last byte
            // `px.add(2)` reads) are both within that buffer.
            unsafe {
                let px = self.bits.offset(offset);
                let b = *px;
                let g = *px.add(1);
                let r = *px.add(2);
                Color::rgb(r, g, b)
            }
        }
        #[cfg(not(target_os = "windows"))]
        {
            let _ = (x, y);
            Color::rgb(0, 0, 0)
        }
    }
}

impl Drop for HeadlessSurface {
    fn drop(&mut self) {
        // `target`/`factory` release themselves via `windows-rs`'s
        // `Drop`/`Release` machinery; the raw GDI handles below don't and
        // must be torn down explicitly, mirroring the cleanup already
        // done on `Self::new`'s error paths. Off Windows `Self::new`
        // never returns `Ok`, so there is nothing to release — see the
        // module doc.
        #[cfg(target_os = "windows")]
        // SAFETY: `self.bitmap`/`self.hdc` are the live GDI handles
        // created in `Self::new` and never freed anywhere else on the
        // success path (the comment above explains why `target`/
        // `factory` don't need the same treatment); `Drop::drop` runs at
        // most once, so this is the sole, final release of both.
        unsafe {
            let _ = DeleteObject(self.bitmap.into());
            let _ = DeleteDC(self.hdc);
        }
    }
}

#[cfg(target_os = "windows")]
fn color_to_d2d(color: Color) -> D2D1_COLOR_F {
    D2D1_COLOR_F {
        r: color.r as f32 / 255.0,
        g: color.g as f32 / 255.0,
        b: color.b as f32 / 255.0,
        a: color.a as f32 / 255.0,
    }
}

/// Build a [`WinDriver`] that wraps `app` in the full
/// [`crate::shell_adapter::ShellAdapter`] stack, mirroring exactly what
/// [`crate::win::shell_runner::run_with_shell`] does at runtime — but
/// painting into a [`HeadlessSurface`] instead of opening a live Win32
/// window. The Win-GUI twin of [`crate::gtk::testing::driver_with_shell`]
/// / [`crate::macos::testing::driver_with_shell`] /
/// [`crate::tui::testing::driver_with_shell`]; all four
/// share the same [`ShellApp`] + [`ShellConfig`] input, differing only in
/// the native units their respective drivers take (Win-GUI: DIPs, same
/// as GTK/macOS points/pixels).
///
/// Use this constructor in tests that need to verify the full
/// `ShellApp → ShellAdapter → dispatch_event` integration path on the
/// Win-GUI backend — e.g. confirming that shell chrome (activity bar,
/// status bar) actually paints through the composed adapter, the way
/// macOS's precedent test
/// (`tests/macos_example_driver.rs::appshell_demo_renders_shell_chrome_via_driver_with_shell`,
/// #465) does for `MacDriver`.
///
/// # Example
///
/// ```no_run
/// # use quadraui::win::testing::driver_with_shell;
/// # use quadraui::{ShellApp, ShellConfig, Backend, ShellContext, Reaction, UiEvent};
/// # struct MyApp;
/// # impl ShellApp for MyApp {
/// #     fn render_content(&self, _: &mut dyn Backend, _: &quadraui::compose::app_shell::AppShellLayout) {}
/// #     fn handle(&mut self, _: UiEvent, _: &mut dyn Backend, _: &ShellContext) -> Reaction { Reaction::Continue }
/// # }
/// let config = ShellConfig::new("Demo", vec![]);
/// let mut driver = driver_with_shell(MyApp, config, 800, 480);
/// let _ = driver.pixel(0, 0);
/// ```
pub fn driver_with_shell<A: ShellApp + 'static>(
    app: A,
    config: ShellConfig,
    width: u32,
    height: u32,
) -> WinDriver<impl AppLogic> {
    let adapter = crate::shell_adapter::build_shell_adapter(app, config);
    WinDriver::new(adapter, width, height)
}

/// Drives an [`AppLogic`] impl headlessly against the Win-GUI backend for
/// tests. Construct with [`Self::new`] (runs `setup` + paints the first
/// frame), poke it with [`Self::press`] / [`Self::type_char`] /
/// [`Self::click`], and read painted pixels back with [`Self::pixel`].
///
/// ## Display-free
///
/// Renders into a headless [`HeadlessSurface`] (an `ID2D1DCRenderTarget`
/// bound to an in-memory DIB section — see that type's module docs) via
/// [`WinBackend::attach_headless`] — no live `HWND`, no `WndProc`, no
/// display. `Self::render`/`Self::dispatch` call the exact same
/// [`super::run::render_frame`] / [`super::run::dispatch_event`] the live
/// `win::run` message loop uses, so this exercises production
/// pre-processing (ActivityBar keyboard-focus redirect, global
/// accelerator matching) identically to a real keypress — mirrors
/// `MacDriver`'s "no drift from production" contract.
///
/// ## Painted-text recording
///
/// Like [`crate::gtk::testing::GtkDriver`] / [`crate::macos::testing::MacDriver`],
/// this driver records every [`super::text::DWrite::draw_text`]/
/// `draw_text_styled` call into [`super::backend::WinBackend::text_runs`]
/// — [`Self::new`] turns recording on — so [`Self::find`]/
/// [`Self::find_bounds`]/[`Self::screen_contains`]/[`Self::painted_texts`]
/// and the [`crate::testing::ConformanceDriver`] surface
/// (`click_text`/`AssertScreenHas`/…) resolve real painted text instead
/// of an honest `None`/`false` stub. Coverage is exactly as wide as the
/// rasterisers that have landed against `super::text::draw_text`'s choke
/// point — every `todo!()`-stub rasteriser still contributes nothing,
/// same as it contributes no pixels.
pub struct WinDriver<A: AppLogic> {
    core: DriverCore<WinBackend, A>,
    surface: HeadlessSurface,
    width: u32,
    height: u32,
}

impl<A: AppLogic> WinDriver<A> {
    /// Build a driver for `app` on a `width`×`height` pixel surface, run
    /// the app's `setup` hook, and paint the first frame.
    ///
    /// # Panics
    ///
    /// If creating the offscreen Direct2D DC render target
    /// ([`HeadlessSurface::new`]) or attaching it
    /// ([`WinBackend::attach_headless`]) fails — both are effectively
    /// infallible on a real Windows host (WARP software rasterisation
    /// needs no GPU/driver/display, see `HeadlessSurface`'s module docs),
    /// so a failure here means something is fundamentally wrong with the
    /// test environment rather than a condition tests should recover
    /// from — same posture `HeadlessSurface`'s own doctest/unit tests
    /// take with `.expect(..)`.
    ///
    /// Off Windows, [`HeadlessSurface::new`] always errs (see its doc),
    /// so this **always** panics if actually called there — see this
    /// module's doc for why every real call site is itself gated to
    /// `target_os = "windows"`, making that unreachable in practice.
    pub fn new(app: A, width: u32, height: u32) -> Self {
        let surface = HeadlessSurface::new(width, height)
            .expect("WinDriver::new: create offscreen Direct2D DC render target");
        let mut backend = WinBackend::new();
        // Record every painted text run into `WinBackend::text_runs` so
        // `find`/`find_bounds`/`screen_contains`/`inventory` can locate
        // text from any DirectWrite-painted primitive — off in
        // production, mirrors `GtkBackend::set_painted_text_recording` /
        // `MacBackend::set_painted_text_recording`.
        backend.set_painted_text_recording(true);
        // `attach_headless` (like `HeadlessSurface::target`) takes/returns
        // Windows-only types and so is itself whole-function
        // `target_os = "windows"`-gated in `backend.rs` — off Windows
        // there is nothing to attach to (`surface` above is already a
        // `HeadlessSurfaceUnavailable` panic by this point in practice).
        #[cfg(target_os = "windows")]
        backend
            .attach_headless(surface.target().clone(), width, height)
            .expect("WinDriver::new: attach headless surface to WinBackend");
        let mut app = app;
        app.setup(&mut backend);
        let mut driver = Self {
            core: DriverCore::new(app, backend),
            surface,
            width,
            height,
        };
        driver.render();
        driver
    }

    /// Repaint one frame through the shared production render path.
    ///
    /// Calls [`WinBackend::ensure_surface`] first, mirroring `win::run`'s
    /// live `WM_PAINT`/`WM_SIZE` handlers: without this, a
    /// surface `end_frame` dropped after a failed `EndDraw` (device
    /// lost / `D2DERR_RECREATE_TARGET`) stayed `None` forever in a
    /// headless driver, since nothing else in this type's call path ever
    /// recreated it — the exact gap that made the render-target recovery
    /// path unreachable from a test. `ensure_surface` is itself
    /// Windows-only (same reasoning as `attach_headless` in [`Self::new`]),
    /// so it's skipped off Windows — [`render_frame`] below still
    /// type-checks and runs there, painting nothing, same as it would on
    /// a `WinBackend` with no surface attached at all.
    pub fn render(&mut self) {
        #[cfg(target_os = "windows")]
        let _ = self.core.backend_mut().ensure_surface();
        let viewport = Viewport::new(self.width as f32, self.height as f32, 1.0);
        let (backend, app) = self.core.parts_mut();
        render_frame(backend, app, viewport);
    }

    /// Feed one synthetic event through the shared production
    /// [`dispatch_event`] path. Repaints on redraw and latches `exited`.
    pub fn dispatch(&mut self, event: UiEvent) -> Reaction {
        if self.core.exited() {
            return Reaction::Exit;
        }
        let outcome = {
            let (backend, app) = self.core.parts_mut();
            dispatch_event(event, backend, app)
        };
        self.apply_outcome(outcome)
    }

    /// Advance idle scheduling by calling [`AppLogic::tick`] directly,
    /// mirroring [`crate::tui::testing::TuiDriver::tick`] /
    /// [`crate::macos::testing::MacDriver::tick`] — until
    /// now `WinDriver` had no way to drive `tick` at all short of a real
    /// `WM_TIMER`, which never fires in a headless test. Repaints on
    /// redraw and latches `exited`, via the same [`Self::apply_outcome`]
    /// plumbing [`Self::dispatch`] uses.
    ///
    /// Note this only exercises the `AppLogic::tick` half of the
    /// scheduling loop, not whether a real Win32 `SetTimer`/`WM_TIMER`
    /// round-trip would have called it on a live message loop in the
    /// first place — see [`WinBackend::frame_requests`]/
    /// [`WinBackend::pending_frame_delay`] for asserting on *that* half
    /// without a live `HWND`.
    pub fn tick(&mut self) -> Reaction {
        if self.core.exited() {
            return Reaction::Exit;
        }
        let reaction = {
            let (backend, app) = self.core.parts_mut();
            app.tick(backend)
        };
        self.apply_outcome(reaction.into())
    }

    /// Shared outcome→[`Reaction`] bookkeeping [`Self::dispatch`] and
    /// [`Self::mouse_down`]/[`Self::mouse_move`]/[`Self::mouse_up`]
    /// all need: repaint on redraw, latch `exited` on exit. Thin wrapper
    /// over [`DriverCore::apply_outcome`] — `render` still
    /// has to be a closure here (not `Self::render`) because `DriverCore`
    /// doesn't own this driver's `surface`/`width`/`height`.
    fn apply_outcome(&mut self, outcome: EventOutcome) -> Reaction {
        let viewport = Viewport::new(self.width as f32, self.height as f32, 1.0);
        self.core.apply_outcome(outcome, |backend, app| {
            #[cfg(target_os = "windows")]
            let _ = backend.ensure_surface();
            render_frame(backend, app, viewport);
        })
    }

    /// Press a key (no modifiers).
    pub fn press(&mut self, key: Key) -> Reaction {
        DriverInput::press(self, key)
    }

    /// Type a single character key (no modifiers).
    pub fn type_char(&mut self, c: char) -> Reaction {
        DriverInput::type_char(self, c)
    }

    /// Press a named (non-printable) key, e.g. [`NamedKey::Enter`].
    pub fn press_named(&mut self, key: NamedKey) -> Reaction {
        DriverInput::press_named(self, key)
    }

    /// Press a character key with Ctrl held.
    pub fn ctrl_char(&mut self, c: char) -> Reaction {
        DriverInput::ctrl_char(self, c)
    }

    /// Left-click at surface coordinates `(x, y)` (DIPs): down then up.
    /// Unlike `GtkDriver`/`MacDriver`/`TuiDriver`'s `click` (a bare
    /// press-down, [`DriverInput::click`]'s default), Win-GUI's has
    /// always released too — preserved here as an explicit
    /// [`DriverInput`] override rather than silently homogenised
    pub fn click(&mut self, x: f32, y: f32) -> Reaction {
        DriverInput::click(self, x, y)
    }

    /// Left-button drag from `(x0, y0)` to `(x1, y1)`: down → move → up.
    pub fn drag(&mut self, x0: f32, y0: f32, x1: f32, y1: f32) -> Reaction {
        DriverInput::drag(self, x0, y0, x1, y1)
    }

    /// Press the left mouse button down at `(x, y)`. Routes through
    /// [`route_mouse_down`] — the same `dispatch_click` text-region/
    /// scrollbar hit-testing the live `wndproc` runs — rather than handing
    /// a raw `MouseDown` straight to [`Self::dispatch`], so a click into a
    /// registered `TextRegion` starts a selection drag here exactly as it
    /// does live.
    pub fn mouse_down(&mut self, x: f32, y: f32) -> Reaction {
        if self.core.exited() {
            return Reaction::Exit;
        }
        let outcome = {
            let (backend, app) = self.core.parts_mut();
            route_mouse_down(
                backend,
                app,
                Point::new(x, y),
                MouseButton::Left,
                Modifiers::default(),
            )
        };
        self.apply_outcome(outcome)
    }

    /// Move the cursor to `(x, y)` with the left button held. Routes
    /// through [`route_mouse_move`] so an in-progress
    /// `TextSelection`/scrollbar drag emits `TextSelectionChanged`/scroll
    /// events, mirroring the live `wndproc`.
    pub fn mouse_move(&mut self, x: f32, y: f32) -> Reaction {
        if self.core.exited() {
            return Reaction::Exit;
        }
        let outcome = {
            let (backend, app) = self.core.parts_mut();
            route_mouse_move(
                backend,
                app,
                Point::new(x, y),
                ButtonMask {
                    left: true,
                    ..ButtonMask::default()
                },
            )
        };
        self.apply_outcome(outcome)
    }

    /// Release the left mouse button at `(x, y)`. Routes through
    /// [`route_mouse_up`] so an in-progress scrollbar/text-selection
    /// drag ends cleanly, mirroring the live `wndproc`.
    pub fn mouse_up(&mut self, x: f32, y: f32) -> Reaction {
        if self.core.exited() {
            return Reaction::Exit;
        }
        let outcome = {
            let (backend, app) = self.core.parts_mut();
            route_mouse_up(backend, app, Point::new(x, y), MouseButton::Left)
        };
        self.apply_outcome(outcome)
    }

    /// Whether the app has returned [`Reaction::Exit`].
    pub fn exited(&self) -> bool {
        self.core.exited()
    }

    /// Access the app state for test assertions.
    pub fn app(&self) -> &A {
        self.core.app()
    }

    /// Mutable access to the app state for tests that need to poke state
    /// directly rather than through a scripted [`UiEvent`].
    pub fn app_mut(&mut self) -> &mut A {
        self.core.app_mut()
    }

    /// Access the backend for test assertions.
    pub fn backend(&self) -> &WinBackend {
        self.core.backend()
    }

    /// Mutable access to the backend — needed for `&mut self` trait
    /// methods like [`Backend::last_error`] that a test wants to poll
    /// directly rather than through a scripted event.
    pub fn backend_mut(&mut self) -> &mut WinBackend {
        self.core.backend_mut()
    }

    /// Force the backend's surface back to the "not attached" state a
    /// genuine `EndDraw` failure leaves it in, without needing to manufacture
    /// a real Direct2D error to get there.
    ///
    /// `WinBackend::surface`/`attach_surface`/`attach_headless` are all
    /// `pub(crate)`, so before this, nothing outside quadraui itself could reach
    /// the "had a surface, then lost it" state at all. This method plus
    /// [`Self::backend_mut`] lets tests drive the transient-loss case: call this,
    /// then assert fallback behaviour against [`Self::backend_mut`] directly,
    /// bypassing [`Self::render`] (which would otherwise immediately paper over
    /// the dropped state via its own `ensure_surface` call before any assertion
    /// gets to see it). The next [`Self::render`] (or a direct `ensure_surface`
    /// call on [`Self::backend_mut`]) recreates it again, exactly like the real
    /// recovery path this simulates.
    pub fn drop_surface(&mut self) {
        #[cfg(target_os = "windows")]
        self.core.backend_mut().drop_surface_for_testing();
    }

    /// Access the underlying offscreen surface.
    pub fn surface(&self) -> &HeadlessSurface {
        &self.surface
    }

    /// Read a painted pixel back from the rendered surface at device-pixel
    /// coordinate `(x, y)` — see [`HeadlessSurface::pixel_at`].
    pub fn pixel(&self, x: u32, y: u32) -> Color {
        self.surface.pixel_at(x, y)
    }

    /// All text painted during the last [`Self::render`], as recorded at
    /// the [`super::text::DWrite::draw_text`]/`draw_text_styled` choke
    /// point — shared body: [`DriverCore::painted_texts`]
    pub fn painted_texts(&self) -> Vec<&str> {
        self.core.painted_texts()
    }

    /// True if any painted text contains `needle` — the Win-GUI analogue
    /// of [`crate::gtk::testing::GtkDriver::screen_contains`] /
    /// [`crate::macos::testing::MacDriver::screen_contains`] — shared
    /// body: [`DriverCore::screen_contains`].
    pub fn screen_contains(&self, needle: &str) -> bool {
        self.core.screen_contains(needle)
    }

    /// Bounds (DIPs) of the first painted text run containing `needle` —
    /// shared body: [`DriverCore::find_bounds`].
    pub fn find_bounds(&self, needle: &str) -> Option<Rect> {
        self.core.find_bounds(needle)
    }

    /// Center coordinates (DIPs) of the first painted text run containing
    /// `needle` — pass straight to [`Self::click`]. `None` if nothing
    /// painted this frame matched. Shared body: [`DriverCore::find`]
    pub fn find(&self, needle: &str) -> Option<(f32, f32)> {
        self.core.find(needle)
    }
}

/// The four raw primitives [`DriverInput`]'s default `press`/`type_char`/
/// `press_named`/`ctrl_char` methods build on — see that trait's doc for
/// why `dispatch`/`mouse_down`/`mouse_move`/`mouse_up` stay required
/// (genuinely per-backend) rather than shared. `click` is
/// overridden (not the default bare press-down) to preserve `WinDriver`'s
/// existing down-then-up behaviour — see [`WinDriver::click`]'s doc.
impl<A: AppLogic> DriverInput for WinDriver<A> {
    fn dispatch(&mut self, event: UiEvent) -> Reaction {
        self.dispatch(event)
    }

    fn mouse_down(&mut self, x: f32, y: f32) -> Reaction {
        self.mouse_down(x, y)
    }

    fn mouse_move(&mut self, x: f32, y: f32) -> Reaction {
        self.mouse_move(x, y)
    }

    fn mouse_up(&mut self, x: f32, y: f32) -> Reaction {
        self.mouse_up(x, y)
    }

    fn click(&mut self, x: f32, y: f32) -> Reaction {
        self.mouse_down(x, y);
        self.mouse_up(x, y)
    }
}

/// Backs [`ConformanceDriver::click_text_at`]/`drag_text`/`scroll_at`'s
/// shared pixel-unit bodies, resolved against the real
/// painted-text-run recording described on [`WinDriver`]'s module doc.
/// A needle no rasteriser has painted this frame (either
/// because it genuinely isn't on screen, or because its rasteriser is
/// still a `todo!()` stub) reports `None`/`false`, same as every other
/// backend's driver does for a genuinely-missing needle — any
/// conformance step that reaches
/// [`PixelClickConformance::click_text_at`]/`drag_text`/`scroll_at`
/// directly (rather than through the runner's `require_painted` guard,
/// which turns a `None` into a graceful `Outcome::Fail` first) panics
/// with a `"WinDriver: … not painted"` message.
impl<A: AppLogic> PixelClickConformance for WinDriver<A> {
    const NAME: &'static str = "WinDriver";

    fn find_bounds(&self, needle: &str) -> Option<Rect> {
        self.find_bounds(needle)
    }

    fn find(&self, needle: &str) -> Option<(f32, f32)> {
        self.find(needle)
    }

    fn conformance_line_height(&self) -> f32 {
        crate::Backend::line_height(self.core.backend())
    }
}

impl<A: AppLogic> ConformanceDriver for WinDriver<A> {
    type App = A;

    fn new_fixture(app: Self::App, viewport: LogicalViewport) -> Self {
        // Win-GUI's native unit is the DIP (device-independent pixel).
        // Scale the logical cols/rows by the same nominal
        // char_width/line_height `GtkDriver::new_fixture` /
        // `MacDriver::new_fixture` use — the driver's first frame (and
        // therefore the app's real font metrics) doesn't exist yet to
        // measure from.
        const NOMINAL_CHAR_WIDTH: u32 = 8;
        const NOMINAL_LINE_HEIGHT: u32 = 16;
        WinDriver::new(
            app,
            viewport.cols * NOMINAL_CHAR_WIDTH,
            viewport.rows * NOMINAL_LINE_HEIGHT,
        )
    }

    fn backend_caps(&self) -> crate::BackendCaps {
        // Straight off the real `WinBackend` this driver wraps — never a
        // re-statement.
        crate::Backend::backend_caps(self.core.backend())
    }

    fn press_named(&mut self, key: NamedKey) {
        WinDriver::press_named(self, key);
    }

    fn type_char(&mut self, c: char) {
        WinDriver::type_char(self, c);
    }

    fn ctrl_char(&mut self, c: char) {
        WinDriver::ctrl_char(self, c);
    }

    fn click_text_at(&mut self, needle: &str, at: Anchor) {
        PixelClickConformance::click_text_at(self, needle, at)
    }

    fn drag_text(&mut self, from: &str, to: &str) {
        PixelClickConformance::drag_text(self, from, to)
    }

    fn scroll_at(&mut self, needle: &str, lines: i32) {
        PixelClickConformance::scroll_at(self, needle, lines)
    }

    fn inventory(&self) -> FrameInventory {
        FrameInventory {
            text_runs: self.core.backend().text_runs().to_vec(),
            // Zones registered this frame via `Backend::register_zone` —
            // `AppShell::render`'s shell-chrome zones (activity-bar items,
            // sidebar header/content, status bar, title bar, ...) plus
            // `WinBackend::draw_menu_bar`/`draw_status_bar_interactive`/
            // `draw_command_center`'s own band-content zones. Primitives
            // that don't yet call `register_zone` contribute no zone,
            // mirroring `GtkDriver::inventory`/`MacDriver::inventory`.
            zones: self.core.backend().zones.clone(),
        }
    }

    fn screen_has(&self, needle: &str) -> bool {
        self.screen_contains(needle)
    }

    fn exited(&self) -> bool {
        WinDriver::exited(self)
    }
}

#[cfg(test)]
mod tests {
    // Every test in this module (and `StatusBarApp` below) exercises a
    // real `HeadlessSurface`/`WinBackend` paint, which is a guaranteed
    // panic off Windows (`HeadlessSurface::new` always errs there — see
    // the module doc's "`feature = "win"` alone" section). Each one is
    // individually `#[cfg(target_os = "windows")]`-gated rather than
    // gating this whole `mod tests`, mirroring `backend.rs`'s
    // `mod tests` precedent — a future pure-Rust-logic test belongs in
    // this same module, ungated. That leaves this import with no user at
    // all off Windows (every name it brings in is only referenced from
    // `target_os = "windows"`-gated items below), hence the `allow`.
    #[cfg_attr(not(target_os = "windows"), allow(unused_imports))]
    use super::*;
    // Only the `target_os = "windows"`-gated `TickCountingApp`/
    // `tick_calls_applogic_tick_and_applies_its_reaction` below reference
    // `Duration` — gate the import the same way so it isn't flagged unused
    // off Windows.
    #[cfg(target_os = "windows")]
    use std::time::Duration;

    #[cfg(target_os = "windows")]
    #[test]
    fn fills_a_solid_rect_and_reads_the_pixel_back() {
        let surface = HeadlessSurface::new(64, 64).expect("create headless surface");
        surface
            .fill_rect(Rect::new(0.0, 0.0, 64.0, 64.0), Color::rgb(200, 40, 40))
            .expect("fill rect");

        let center = surface.pixel_at(32, 32);
        assert_eq!((center.r, center.g, center.b), (200, 40, 40));
    }

    #[cfg(target_os = "windows")]
    #[test]
    fn a_rect_that_does_not_cover_the_whole_surface_leaves_the_rest_cleared() {
        let surface = HeadlessSurface::new(32, 32).expect("create headless surface");
        surface
            // SAFETY: `target` is the live render target `Self::paint`
            // passes in, mid-`BeginDraw`/`EndDraw`; the `D2D1_COLOR_F` is
            // a plain stack literal borrowed only for this call.
            .paint(|target| unsafe {
                target.Clear(Some(&D2D1_COLOR_F {
                    r: 0.0,
                    g: 0.0,
                    b: 0.0,
                    a: 1.0,
                }));
            })
            .expect("clear");
        surface
            .fill_rect(Rect::new(0.0, 0.0, 8.0, 8.0), Color::rgb(0, 255, 0))
            .expect("fill rect");

        let inside = surface.pixel_at(2, 2);
        let outside = surface.pixel_at(20, 20);
        assert_eq!((inside.r, inside.g, inside.b), (0, 255, 0));
        assert_eq!((outside.r, outside.g, outside.b), (0, 0, 0));
    }

    /// A minimal `AppLogic` that paints one `StatusBar` segment — the
    /// acceptance scenario that `WinDriver::find` should locate a status-bar
    /// label after a real `WinBackend::draw_status_bar` paint.
    /// `target_os`-gated: its only use site is too.
    #[cfg(target_os = "windows")]
    struct StatusBarApp;

    #[cfg(target_os = "windows")]
    impl AppLogic for StatusBarApp {
        type AreaId = ();

        fn render(&self, backend: &mut dyn crate::Backend, _area: ()) {
            backend.draw_status_bar_interactive(
                Rect::new(0.0, 0.0, 200.0, 20.0),
                &crate::StatusBar {
                    id: crate::WidgetId::new("status"),
                    left_segments: vec![crate::StatusBarSegment {
                        text: "NORMAL".into(),
                        fg: Color::rgb(255, 255, 255),
                        bg: Color::rgb(10, 20, 30),
                        bold: false,
                        action_id: None,
                    }],
                    right_segments: vec![],
                },
                &crate::InteractionState::new(),
            );
        }

        fn handle(&mut self, _event: UiEvent, _backend: &mut dyn crate::Backend) -> Reaction {
            Reaction::Continue
        }
    }

    /// Acceptance criterion: verify that `WinDriver::find` locates a status-bar
    /// segment after painting headlessly (this test runs headlessly; the real
    /// Windows run is covered separately).
    #[cfg(target_os = "windows")]
    #[test]
    fn find_locates_a_status_bar_segment_after_paint() {
        let driver = WinDriver::new(StatusBarApp, 200, 20);

        assert!(
            driver.find("NORMAL").is_some(),
            "WinDriver::find should locate the painted status-bar segment: {:?}",
            driver.painted_texts()
        );
        assert!(driver.screen_contains("NORMAL"));

        let inventory = ConformanceDriver::inventory(&driver);
        assert!(
            !inventory.text_runs().is_empty(),
            "inventory().text_runs() should be non-empty after a status-bar paint"
        );
        assert!(inventory.screen_has("NORMAL"));
    }

    /// Verify that a real `EndDraw` failure triggers device-lost recovery and
    /// the next frame still paints.
    ///
    /// ## Forcing a genuine `EndDraw` failure
    ///
    /// `DrawBitmap` returns `void` in the Direct2D API — any failure it
    /// hits is recorded internally and only reported later, by `EndDraw`.
    /// Drawing an [`ID2D1Bitmap`] created against a *different*
    /// `ID2D1DCRenderTarget`'s resource domain (a second, independent
    /// [`HeadlessSurface`]) is a well-defined Direct2D error
    /// (`D2DERR_WRONG_RESOURCE_DOMAIN`) — this reaches `WinBackend::end_frame`'s
    /// real `EndDraw(None, None)` call through the same deferred-error path a
    /// genuine `D2DERR_RECREATE_TARGET` device-loss would, without corrupting
    /// any OS resource the recovery path then has to fake its way around.
    /// `HeadlessSurface::paint`'s own doc already calls out this pattern.
    /// This test is the "invalid drawing call" half of it, driven through the
    /// real `Backend::end_frame`/`last_error` wiring instead of
    /// `HeadlessSurface::paint` directly.
    #[cfg(target_os = "windows")]
    #[test]
    fn end_frame_recovers_the_next_frame_after_a_forced_end_draw_failure() {
        use windows::Win32::Graphics::Direct2D::Common::D2D_SIZE_U;
        use windows::Win32::Graphics::Direct2D::{
            ID2D1Bitmap, D2D1_BITMAP_INTERPOLATION_MODE_LINEAR, D2D1_BITMAP_PROPERTIES,
        };

        const W: u32 = 32;
        const H: u32 = 32;
        let divider_color = Color::rgb(0x22, 0x66, 0xaa);

        let surface = HeadlessSurface::new(W, H).expect("create headless surface");
        let mut backend = WinBackend::new();
        backend
            .attach_headless(surface.target().clone(), W, H)
            .expect("attach headless surface");

        let paint_divider = |backend: &mut WinBackend| {
            use crate::Backend;
            backend.begin_frame(Viewport::new(W as f32, H as f32, 1.0));
            backend.set_theme(crate::theme::Theme {
                separator: divider_color,
                ..crate::theme::Theme::default()
            });
            backend.draw_terminal_divider(Rect::new(0.0, 0.0, W as f32, H as f32));
            backend.end_frame();
        };

        // Frame 1: baseline — confirm painting works before anything is
        // forced to fail.
        paint_divider(&mut backend);
        assert_eq!(
            (
                surface.pixel_at(0, H / 2).r,
                surface.pixel_at(0, H / 2).g,
                surface.pixel_at(0, H / 2).b,
            ),
            (divider_color.r, divider_color.g, divider_color.b),
            "sanity: frame 1 must paint the divider before we force anything to fail"
        );

        // A second, independent headless surface purely to manufacture a
        // bitmap in a different Direct2D resource domain — see the
        // module doc above for why this reliably forces a deferred
        // `EndDraw` failure.
        let foreign_surface = HeadlessSurface::new(4, 4).expect("create foreign headless surface");
        let bitmap_props = D2D1_BITMAP_PROPERTIES {
            pixelFormat: D2D1_PIXEL_FORMAT {
                format: DXGI_FORMAT_B8G8R8A8_UNORM,
                alphaMode: D2D1_ALPHA_MODE_IGNORE,
            },
            dpiX: 96.0,
            dpiY: 96.0,
        };
        // SAFETY: `foreign_surface.target()` is a live render target
        // (deliberately a *different* one from `surface` under test — see
        // this test's doc comment on why); `None`/`0` for the source data
        // asks Direct2D to allocate an uninitialized 1x1 bitmap rather
        // than copy from a caller buffer, which needs no pointer at all;
        // `bitmap_props` is a plain stack struct borrowed for this call.
        let foreign_bitmap: ID2D1Bitmap = unsafe {
            foreign_surface
                .target()
                .CreateBitmap(
                    D2D_SIZE_U {
                        width: 1,
                        height: 1,
                    },
                    None,
                    0,
                    &bitmap_props,
                )
                .expect("create foreign-domain bitmap")
        };

        // Frame 2: draw the foreign-domain bitmap into the surface under
        // test — `end_frame`'s `EndDraw` must now fail for real.
        {
            use crate::Backend;
            backend.begin_frame(Viewport::new(W as f32, H as f32, 1.0));
            // SAFETY: `surface.target()` is the live render target under
            // test, mid-`BeginDraw`/`EndDraw` via `backend.begin_frame`/
            // `end_frame`; `foreign_bitmap` is the live (but
            // wrong-resource-domain) bitmap from above — drawing it is
            // deliberately expected to record a deferred error, per this
            // test's doc comment, not to be a sound paint. `None` for the
            // optional dest/src rects draws the whole 1x1 bitmap.
            unsafe {
                surface.target().DrawBitmap(
                    &foreign_bitmap,
                    None,
                    1.0,
                    D2D1_BITMAP_INTERPOLATION_MODE_LINEAR,
                    None,
                );
            }
            backend.end_frame();

            assert_eq!(
                backend.last_error(),
                Some(crate::backend::BackendError::SurfaceLost),
                "end_frame must report SurfaceLost once EndDraw genuinely fails"
            );
            assert_eq!(
                backend.last_error(),
                None,
                "last_error() must clear on read — a second call must not repeat the same error"
            );
        }

        // Frame 3 — the acceptance criterion: painting must still work
        // after the forced failure. `ensure_surface` before
        // `paint_divider` mirrors exactly what `WinDriver::render` (and
        // `win::run`'s live `WM_PAINT`/`WM_SIZE` handlers) do before
        // every real frame — this test calls it explicitly
        // since it drives `WinBackend` directly rather than through
        // `WinDriver`.
        {
            let _ = backend.ensure_surface();
            paint_divider(&mut backend);
        }
        assert_eq!(
            (
                surface.pixel_at(0, H / 2).r,
                surface.pixel_at(0, H / 2).g,
                surface.pixel_at(0, H / 2).b,
            ),
            (divider_color.r, divider_color.g, divider_color.b),
            "frame 3 (after recovery) must still paint the divider — the render target must have \
             been rebuilt by ensure_surface, not left permanently dropped"
        );
    }

    /// Verify that `render_frame`'s panic guard around `app.render` must
    /// (a) survive a panicking render, and (b) leave the `BeginDraw`/`EndDraw`
    /// bracket balanced, so the next frame still paints.
    ///
    /// This drives the real `super::run::render_frame` that the live
    /// `wndproc`'s `WM_PAINT` handler calls — the same production function, not
    /// a reimplementation — but not `wndproc`/`WNDPROC` itself: that C ABI
    /// entry point only exists inside `mod win32` (`target_os = "windows"`),
    /// which no headless test can drive.
    #[cfg(target_os = "windows")]
    #[test]
    fn render_frame_survives_a_panicking_app_render_and_the_next_frame_still_paints() {
        const W: u32 = 32;
        const H: u32 = 32;
        let divider_color = Color::rgb(0x22, 0x66, 0xaa);

        /// Panics on its first `render` call, then paints a solid
        /// divider on every call after that — lets one test drive both
        /// "the panicking frame" and "the recovered frame" through the
        /// same `AppLogic` impl.
        struct PanicsOnceThenPaints {
            panicked_yet: std::cell::Cell<bool>,
            divider_color: Color,
        }

        impl AppLogic for PanicsOnceThenPaints {
            type AreaId = ();

            fn render(&self, backend: &mut dyn crate::Backend, _area: ()) {
                if !self.panicked_yet.replace(true) {
                    panic!("PanicsOnceThenPaints: deliberate panic for #922 coverage");
                }
                backend.set_theme(crate::theme::Theme {
                    separator: self.divider_color,
                    ..crate::theme::Theme::default()
                });
                backend.draw_terminal_divider(Rect::new(0.0, 0.0, W as f32, H as f32));
            }

            fn handle(&mut self, _event: UiEvent, _backend: &mut dyn crate::Backend) -> Reaction {
                Reaction::Continue
            }
        }

        let surface = HeadlessSurface::new(W, H).expect("create headless surface");
        let mut backend = WinBackend::new();
        backend
            .attach_headless(surface.target().clone(), W, H)
            .expect("attach headless surface");
        let app = PanicsOnceThenPaints {
            panicked_yet: std::cell::Cell::new(false),
            divider_color,
        };
        let viewport = Viewport::new(W as f32, H as f32, 1.0);

        // Frame 1: `app.render` panics.
        render_frame(&mut backend, &app, viewport);

        // Frame 2 — verify painting still works after the caught panic,
        // proving `end_frame` ran (so `BeginDraw` from frame 1 wasn't left
        // unmatched) even though frame 1's `app.render` never returned normally.
        let _ = backend.ensure_surface();
        render_frame(&mut backend, &app, viewport);
        assert_eq!(
            (
                surface.pixel_at(0, H / 2).r,
                surface.pixel_at(0, H / 2).g,
                surface.pixel_at(0, H / 2).b,
            ),
            (divider_color.r, divider_color.g, divider_color.b),
            "frame 2 (after a caught panic in frame 1) must still paint — the \
             BeginDraw/EndDraw bracket must have stayed balanced despite the panic"
        );
    }

    /// Verify that [`WinDriver::drop_surface`] reaches the same `painted: false`
    /// fallback contract a real `EndDraw` failure leaves `WinBackend` in — on a
    /// driver that *had* a real surface attached, not just a bare
    /// `WinBackend::new()` that never got one in the first place.
    #[cfg(target_os = "windows")]
    #[test]
    fn drop_surface_reaches_the_same_unpainted_fallback_a_real_end_draw_failure_does() {
        use crate::Backend;

        struct NoopApp;
        impl AppLogic for NoopApp {
            type AreaId = ();
            fn render(&self, _backend: &mut dyn crate::Backend, _area: ()) {}
            fn handle(&mut self, _event: UiEvent, _backend: &mut dyn crate::Backend) -> Reaction {
                Reaction::Continue
            }
        }

        let minimap = crate::primitives::minimap::Minimap {
            id: crate::types::WidgetId::new("minimap"),
            lines: (0..10)
                .map(|i| crate::primitives::minimap::MinimapLine {
                    text: format!("line {i}"),
                    line_idx: i,
                })
                .collect(),
            syntax_spans: Vec::new(),
            visible_row_start: 0,
            visible_row_count: 5,
            total_buffer_lines: 10,
        };
        let rect = Rect::new(0.0, 0.0, 20.0, 64.0);

        let mut driver = WinDriver::new(NoopApp, 64, 64);
        let viewport = Viewport::new(64.0, 64.0, 1.0);

        // Every real `draw_*` call happens inside a `begin_frame`/`end_frame`
        // bracket (`render_frame`'s contract) — probing `draw_minimap`
        // directly still has to honour that, or the underlying Direct2D
        // render target (reused by `ensure_surface`'s recovery below, not
        // recreated from scratch) is left in an undefined state outside
        // its documented `BeginDraw`/`EndDraw` usage.
        driver.backend_mut().begin_frame(viewport);
        let before = driver.backend_mut().draw_minimap(rect, &minimap);
        driver.backend_mut().end_frame();
        assert!(
            before.painted,
            "sanity: a freshly-built WinDriver has a real surface attached"
        );

        driver.drop_surface();
        driver.backend_mut().begin_frame(viewport);
        let after = driver.backend_mut().draw_minimap(rect, &minimap);
        driver.backend_mut().end_frame();
        assert!(
            !after.painted,
            "drop_surface must reach the same `painted: false` fallback a \
             real EndDraw failure leaves behind"
        );
        assert_eq!(
            after.layout, before.layout,
            "the fallback layout must still agree with the real paint's \
             layout — same contract \
             draw_minimap_with_no_surface_reports_unpainted_and_agrees_with_minimap_layout \
             proves in-crate"
        );

        // Recovery: the next `WinDriver::render` call's `ensure_surface`
        // recreates the surface, exactly like the real recovery path
        driver.render();
        driver.backend_mut().begin_frame(viewport);
        let recovered = driver.backend_mut().draw_minimap(rect, &minimap);
        driver.backend_mut().end_frame();
        assert!(
            recovered.painted,
            "WinDriver::render must recover the dropped surface via \
             ensure_surface, same as a real EndDraw-failure recovery"
        );
    }

    /// An `AppLogic` whose `tick` counts its own calls and, on the
    /// first one only, asks to be woken again — the minimal shape
    /// [`WinDriver::tick`] needs to exercise both halves
    /// of the contract: that it actually calls `AppLogic::tick`, and
    /// that it applies the returned `Reaction` the same way
    /// `WinDriver::dispatch` applies a dispatched event's outcome.
    /// `target_os`-gated: constructing a `WinDriver` at all needs a real
    /// `HeadlessSurface` (see this module's doc).
    #[cfg(target_os = "windows")]
    struct TickCountingApp {
        ticks: std::cell::Cell<u32>,
    }

    #[cfg(target_os = "windows")]
    impl AppLogic for TickCountingApp {
        type AreaId = ();

        fn render(&self, _backend: &mut dyn crate::Backend, _area: ()) {}

        fn handle(&mut self, _event: UiEvent, _backend: &mut dyn crate::Backend) -> Reaction {
            Reaction::Continue
        }

        fn tick(&mut self, _backend: &mut dyn crate::Backend) -> Reaction {
            let n = self.ticks.get() + 1;
            self.ticks.set(n);
            if n == 1 {
                Reaction::RedrawAfter(Duration::from_millis(100))
            } else {
                Reaction::Continue
            }
        }
    }

    /// Verify that `WinDriver::tick` calls `AppLogic::tick` and applies its
    /// reaction. Before this, there was no way to drive `AppLogic::tick` through
    /// `WinDriver` short of a real `WM_TIMER`.
    #[cfg(target_os = "windows")]
    #[test]
    fn tick_calls_applogic_tick_and_applies_its_reaction() {
        let mut driver = WinDriver::new(
            TickCountingApp {
                ticks: std::cell::Cell::new(0),
            },
            64,
            64,
        );

        let reaction = driver.tick();
        assert_eq!(
            driver.app().ticks.get(),
            1,
            "tick() must call AppLogic::tick"
        );
        assert_eq!(reaction, Reaction::RedrawAfter(Duration::from_millis(100)));

        let reaction = driver.tick();
        assert_eq!(driver.app().ticks.get(), 2);
        assert_eq!(reaction, Reaction::Continue);
    }
}