use windows::Win32::Graphics::Direct2D::ID2D1RenderTarget;
use super::text::{fill_rect, stroke_rect, DWrite};
use crate::event::Rect;
use crate::primitives::command_center::{CommandCenter, CommandCenterLayout, CommandCenterMeasure};
use crate::theme::Theme;
const SEARCH_H_PAD_PX: f32 = 16.0;
const SEARCH_MIN_WIDTH_PX: f32 = 280.0;
pub fn win_command_center_layout(
char_width: f32,
rect: Rect,
cc: &CommandCenter,
) -> CommandCenterLayout {
cc.layout(
rect,
CommandCenterMeasure::from_char_width(&cc.search_label, char_width, rect.height),
)
}
fn win_command_center_layout_measured(
dwrite: &DWrite,
rect: Rect,
cc: &CommandCenter,
) -> CommandCenterLayout {
let search_box_width = if cc.search_label.is_empty() {
0.0
} else {
let (text_w, _) = dwrite.measure_text(&cc.search_label).unwrap_or((0.0, 0.0));
(text_w + SEARCH_H_PAD_PX).max(SEARCH_MIN_WIDTH_PX)
};
cc.layout(
rect,
CommandCenterMeasure {
arrow_width: CommandCenterMeasure::ARROW_WIDTH_PX,
gap: CommandCenterMeasure::GAP_PX,
search_box_width,
height: rect.height,
},
)
}
#[allow(clippy::too_many_arguments)]
pub fn draw_command_center(
target: &ID2D1RenderTarget,
dwrite: &DWrite,
_char_width: f32,
line_height: f32,
rect: Rect,
cc: &CommandCenter,
theme: &Theme,
) -> CommandCenterLayout {
let layout = win_command_center_layout_measured(dwrite, rect, cc);
if rect.width <= 0.0 || rect.height <= 0.0 {
return layout;
}
let _ = fill_rect(target, rect, theme.tab_bar_bg);
let enabled_fg = theme.tab_inactive_fg;
let disabled_fg = theme.muted_fg;
let text_y = rect.y + (rect.height - line_height) / 2.0;
if let Some(bb) = layout.back_bounds {
let fg = if cc.back_enabled {
enabled_fg
} else {
disabled_fg
};
let (tw, th) = dwrite.measure_text("◀").unwrap_or((0.0, 0.0));
let tx = bb.x + (bb.width - tw) / 2.0;
let _ = dwrite.draw_text(target, "◀", Rect::new(tx, text_y, tw, th), fg);
}
if let Some(fb) = layout.forward_bounds {
let fg = if cc.forward_enabled {
enabled_fg
} else {
disabled_fg
};
let (tw, th) = dwrite.measure_text("▶").unwrap_or((0.0, 0.0));
let tx = fb.x + (fb.width - tw) / 2.0;
let _ = dwrite.draw_text(target, "▶", Rect::new(tx, text_y, tw, th), fg);
}
if let Some(sb) = layout.search_bounds {
let border = Rect::new(sb.x, sb.y + 2.0, sb.width, (sb.height - 4.0).max(0.0));
let _ = stroke_rect(target, border, theme.separator, 1.0);
let (tw, th) = dwrite.measure_text(&cc.search_label).unwrap_or((0.0, 0.0));
let _ = dwrite.draw_text(
target,
&cc.search_label,
Rect::new(sb.x + 8.0, text_y, tw, th),
theme.tab_inactive_fg,
);
}
layout
}
#[cfg(test)]
mod tests {
use super::*;
use crate::primitives::command_center::CommandCenterHit;
use crate::types::WidgetId;
use crate::win::testing::HeadlessSurface;
const W: f32 = 480.0;
const H: f32 = 32.0;
fn sample_cc() -> CommandCenter {
CommandCenter {
id: WidgetId::new("cc"),
back_enabled: true,
forward_enabled: false,
search_label: "project".into(),
}
}
fn paint_via_backend_at(cc: &CommandCenter, x: f32, y: f32) -> CommandCenterLayout {
let surface = HeadlessSurface::new(W as u32, H as u32).expect("create surface");
let (dwrite, _, char_width) = DWrite::new("Segoe UI", 10.0, None).expect("create DWrite");
let rect = Rect::new(x, y, W - x, H - y);
surface
.paint(|target| {
draw_command_center(
target,
&dwrite,
char_width,
16.0,
rect,
cc,
&Theme::default(),
);
})
.map(|_| win_command_center_layout(char_width, rect, cc))
.expect("paint command center")
}
#[test]
fn round_trip_click_hits_back_and_search_box() {
let cc = sample_cc();
let layout = paint_via_backend_at(&cc, 0.0, 0.0);
let back = layout.back_bounds.expect("back bounds present");
assert_eq!(
layout.hit_test(back.x + 1.0, back.y + 1.0),
CommandCenterHit::Back
);
let search = layout.search_bounds.expect("search bounds present");
assert_eq!(
layout.hit_test(search.x + 1.0, search.y + 1.0),
CommandCenterHit::SearchBox
);
}
#[test]
fn round_trip_click_hits_back_at_nonzero_origin() {
let cc = sample_cc();
let layout = paint_via_backend_at(&cc, 7.0, 13.0);
let back = layout.back_bounds.expect("back bounds present");
assert!(
back.x >= 7.0,
"back.x={} must not fall left of the strip's own origin",
back.x
);
assert_eq!(
layout.hit_test(back.x + 1.0, back.y + 1.0),
CommandCenterHit::Back
);
}
#[test]
fn empty_search_label_omits_search_bounds() {
let cc = CommandCenter {
search_label: "".into(),
..sample_cc()
};
let layout = paint_via_backend_at(&cc, 0.0, 0.0);
assert!(layout.search_bounds.is_none());
assert!(layout.back_bounds.is_some());
}
#[test]
fn no_paint_layout_matches_paint_layout() {
let cc = sample_cc();
let rect = Rect::new(0.0, 0.0, W, H);
let (dwrite, _, char_width) = DWrite::new("Segoe UI", 10.0, None).expect("create DWrite");
let surface = HeadlessSurface::new(W as u32, H as u32).expect("create surface");
let painted = surface
.paint(|target| {
draw_command_center(
target,
&dwrite,
char_width,
16.0,
rect,
&cc,
&Theme::default(),
);
})
.map(|_| win_command_center_layout_measured(&dwrite, rect, &cc))
.expect("paint");
let no_paint = win_command_center_layout_measured(&dwrite, rect, &cc);
assert_eq!(painted, no_paint);
}
#[test]
fn draw_command_center_sizes_search_box_from_real_measured_width_not_char_width_estimate() {
let cc = CommandCenter {
search_label: "i".repeat(60),
..sample_cc()
};
let rect = Rect::new(0.0, 0.0, 900.0, H);
let (dwrite, _, char_width) = DWrite::new("Segoe UI", 10.0, None).expect("create DWrite");
let surface = HeadlessSurface::new(900, H as u32).expect("create surface");
let painted = surface
.paint(|target| {
draw_command_center(
target,
&dwrite,
char_width,
16.0,
rect,
&cc,
&Theme::default(),
);
})
.map(|_| win_command_center_layout_measured(&dwrite, rect, &cc))
.expect("paint");
let estimate = win_command_center_layout(char_width, rect, &cc);
let painted_width = painted
.search_bounds
.expect("non-empty search_label produces search_bounds")
.width;
let estimate_width = estimate
.search_bounds
.expect("non-empty search_label produces search_bounds")
.width;
assert_ne!(
painted_width, estimate_width,
"a run of narrow glyphs should measure differently than the flat \
char_width estimate — if this starts passing by equality, the \
fixture no longer exercises the real-vs-estimate gap #1260 fixed"
);
}
#[test]
fn win_command_center_layout_delegates_to_shared_char_width_formula() {
let cc = CommandCenter {
id: WidgetId::new("cc"),
back_enabled: true,
forward_enabled: true,
search_label: "project-name".into(),
};
let char_width = 9.0_f32;
let rect = Rect::new(3.0, 5.0, 400.0, 24.0);
let layout = win_command_center_layout(char_width, rect, &cc);
let expected =
CommandCenterMeasure::from_char_width(&cc.search_label, char_width, rect.height);
let search = layout
.search_bounds
.expect("non-empty search_label produces search_bounds");
assert_eq!(search.width, expected.search_box_width);
}
}