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
//! Direct2D rasteriser for [`crate::Split`].
//!
//! Painting is delegated to the shared
//! [`crate::primitives::split::native_surface_paint::paint`] — see that fn's
//! module doc for a documented divergence between this module and GTK's:
//! Windows's `ID2D1SolidColorBrush` (via `super::text::fill_rect`)
//! always honoured a translucent `theme.separator`, while GTK did not.
//! This module carries [`win_split_layout`]; the shared
//! [`super::surface::D2dSurface`] adapter has replaced the previous
//! `draw_split` compatibility shim.
//!
//! Only compiled on `target_os = "windows"` — see `super::mod`'s
//! `#[cfg(target_os = "windows")] mod split;` and `backend.rs`'s module
//! docs for why the rest of this repo's `--features win` compile gate
//! stays meaningful without a Windows host.
//!
//! # Theme
//!
//! `WinBackend::draw_split` passes `&self.current_theme` to the shared
//! [`crate::primitives::split::native_surface_paint::paint`] — the same
//! live theme `Backend::set_theme` writes, the same shape
//! `win::status_bar`'s module doc documents.
#[cfg(test)]
use windows::Win32::Graphics::Direct2D::ID2D1RenderTarget;
use crate::event::Rect;
use crate::primitives::layout_metrics::pixel_split_layout;
use crate::primitives::split::{Split, SplitLayout};
#[cfg(test)]
use crate::theme::Theme;
/// Compute a [`Split`]'s layout without painting — the twin of
/// `crate::Backend::draw_split` (the removed `draw_split` free function
/// shim this backed — see module doc). Both call [`Split::layout`] with
/// the identical divider thickness, so a no-paint hit-test call always
/// agrees with what the last paint drew. Shares that thickness with
/// `gtk_split_layout` / `mac_split_layout` via
/// [`crate::primitives::layout_metrics::pixel::DIVIDER`].
pub fn win_split_layout(rect: Rect, split: &Split) -> SplitLayout {
pixel_split_layout(split, rect)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::primitives::split::{SplitDirection, SplitHit};
use crate::types::WidgetId;
use crate::win::testing::HeadlessSurface;
const W: u32 = 200;
const H: u32 = 100;
fn split() -> Split {
Split {
id: WidgetId::new("s"),
direction: SplitDirection::Horizontal,
ratio: 0.5,
first_min: 0.0,
second_min: 0.0,
}
}
/// Paint `layout` via the shared
/// [`crate::primitives::split::native_surface_paint::paint`] through
/// a [`super::super::surface::D2dSurface`] over `target` — the same
/// adapter the now-removed `draw_split` shim used,
/// exercised here directly.
fn paint(target: &ID2D1RenderTarget, layout: &SplitLayout) {
let mut raw = super::super::surface::D2dSurface {
target,
dwrite: None,
};
crate::primitives::split::native_surface_paint::paint(layout, &mut raw, &Theme::default());
}
/// Paint↔click round trip: the divider's painted bg and the
/// layout's own `hit_test` over that same bounds must agree, and
/// clicks either side of it resolve to the matching pane.
#[test]
fn paint_and_hit_test_round_trip() {
let surface = HeadlessSurface::new(W, H).expect("create surface");
let split = split();
let rect = Rect::new(0.0, 0.0, W as f32, H as f32);
let layout = win_split_layout(rect, &split);
surface
.paint(|target| {
paint(target, &layout);
})
.expect("paint split");
let theme = Theme::default();
let div = layout.divider_bounds;
let div_px = surface.pixel_at(
(div.x + div.width / 2.0) as u32,
(div.y + div.height / 2.0) as u32,
);
assert_eq!(
(div_px.r, div_px.g, div_px.b),
(theme.separator.r, theme.separator.g, theme.separator.b)
);
let divider_hit = layout.hit_test(div.x + div.width / 2.0, H as f32 / 2.0);
assert_eq!(divider_hit, SplitHit::Divider(WidgetId::new("s")));
let first_hit = layout.hit_test(1.0, H as f32 / 2.0);
assert_eq!(first_hit, SplitHit::FirstPane(WidgetId::new("s")));
let second_hit = layout.hit_test(W as f32 - 1.0, H as f32 / 2.0);
assert_eq!(second_hit, SplitHit::SecondPane(WidgetId::new("s")));
}
/// `win_split_layout` (no-paint) produces byte-identical layout
/// to what `draw_split` painted — same split, same rect.
#[test]
fn no_paint_layout_matches_paint_layout() {
let split = split();
let rect = Rect::new(0.0, 0.0, W as f32, H as f32);
let surface = HeadlessSurface::new(W, H).expect("create surface");
let painted = win_split_layout(rect, &split);
surface
.paint(|target| {
paint(target, &painted);
})
.expect("paint");
let no_paint = win_split_layout(rect, &split);
assert_eq!(painted, no_paint);
}
#[test]
fn vertical_direction_stacks_panes() {
let mut split = split();
split.direction = SplitDirection::Vertical;
let rect = Rect::new(0.0, 0.0, W as f32, H as f32);
let layout = win_split_layout(rect, &split);
assert!(layout.first_bounds.y < layout.divider_bounds.y);
assert!(layout.divider_bounds.y < layout.second_bounds.y);
}
}