use core_graphics::sys::CGContextRef;
use core_text::font::CTFont;
use crate::primitives::command_line::CommandLine;
use crate::theme::Theme;
#[allow(clippy::too_many_arguments)]
pub unsafe fn draw_command_line(
ctx: CGContextRef,
font: &CTFont,
cmd: &CommandLine,
theme: &Theme,
x: f64,
y: f64,
width: f64,
line_height: f64,
char_width: f32,
) {
draw_command_line_selection(
ctx,
font,
cmd,
theme,
x,
y,
width,
line_height,
char_width,
None,
);
}
#[allow(clippy::too_many_arguments)]
pub unsafe fn draw_command_line_selection(
ctx: CGContextRef,
font: &CTFont,
cmd: &CommandLine,
theme: &Theme,
x: f64,
y: f64,
width: f64,
line_height: f64,
char_width: f32,
selection: Option<(usize, usize)>,
) -> crate::primitives::command_line::CommandLineLayout {
let mut surface = super::surface::CgSurface {
ctx,
font: Some(font),
};
let rect = crate::event::Rect::new(x as f32, y as f32, width as f32, line_height as f32);
crate::primitives::command_line::native_surface_paint::paint(
cmd,
&mut surface,
theme,
rect,
char_width,
selection,
)
}
#[cfg(test)]
mod tests {
use super::super::headless::BitmapSurface;
use super::super::text::make_font;
use super::super::MacBackend;
use super::*;
use crate::event::{Rect as QRect, Viewport};
use crate::types::WidgetId;
use crate::Backend;
const W: u32 = 320;
const H: u32 = 60;
fn sample(text: &str, cursor: Option<usize>, right_align: bool) -> CommandLine {
CommandLine {
id: WidgetId::new("cmdline"),
text: text.into(),
cursor_offset: cursor,
right_align,
}
}
fn paint_via_backend(cmd: &CommandLine, rect: QRect) -> (BitmapSurface, f64) {
let surface = BitmapSurface::new(W, H);
surface.fill(1.0, 1.0, 1.0, 1.0);
let mut backend = MacBackend::new();
backend.set_current_font(make_font("Menlo", 14.0).expect("Menlo installed"));
let line_height = backend.line_height() as f64;
backend.begin_frame(Viewport::new(W as f32, H as f32, 1.0));
backend.enter_frame_scope(surface.context_ptr(), |b| {
b.draw_command_line(rect, cmd);
});
backend.end_frame();
(surface, line_height)
}
#[test]
fn fills_the_bar_background_at_origin() {
let cmd = sample(":wq", None, false);
let (surface, lh) = paint_via_backend(&cmd, QRect::new(0.0, 0.0, W as f32, 20.0));
let theme = Theme::default();
let probe_y = (lh / 2.0) as u32;
let (r, g, b, _) = surface.pixel(W - 4, probe_y);
assert_eq!(
(r, g, b),
(
theme.command_line_bg.r,
theme.command_line_bg.g,
theme.command_line_bg.b
),
"command line background should cover the full bar width",
);
}
#[test]
fn fills_the_bar_background_at_nonzero_origin() {
let origin_x = 24.0_f32;
let origin_y = 30.0_f32;
let cmd = sample(":wq", None, false);
let (surface, _lh) = paint_via_backend(
&cmd,
QRect::new(origin_x, origin_y, W as f32 - origin_x, 20.0),
);
let theme = Theme::default();
let (r, g, b, _) = surface.pixel(W - 4, origin_y as u32 + 4);
assert_eq!(
(r, g, b),
(
theme.command_line_bg.r,
theme.command_line_bg.g,
theme.command_line_bg.b
),
"bar should paint at the requested origin",
);
assert_eq!(
{
let (r, g, b, _) = surface.pixel(4, origin_y as u32 + 4);
(r, g, b)
},
(255, 255, 255),
"nothing should paint left of the bar origin",
);
assert_eq!(
{
let (r, g, b, _) = surface.pixel(W - 4, origin_y as u32 - 4);
(r, g, b)
},
(255, 255, 255),
"nothing should paint above the bar origin",
);
}
#[test]
fn cursor_paints_cursor_colour_after_the_prefix() {
let cmd = sample(":wq", Some(1), false);
let rect = QRect::new(0.0, 0.0, W as f32, 20.0);
let (surface, lh) = paint_via_backend(&cmd, rect);
let theme = Theme::default();
let mut backend = MacBackend::new();
backend.set_current_font(make_font("Menlo", 14.0).expect("Menlo installed"));
let layout = backend.command_line_layout(rect, &cmd);
let cursor_rect = layout.char_bounds(1);
let px = (cursor_rect.x + 1.0) as u32;
let py = (lh / 2.0) as u32;
let (r, g, b, _) = surface.pixel(px, py);
assert_eq!(
(r, g, b),
(theme.cursor.r, theme.cursor.g, theme.cursor.b),
"insert cursor should paint at char_bounds(1)'s column",
);
}
#[test]
fn selection_paints_highlight_behind_text() {
let cmd = sample(" x", None, false);
let rect = QRect::new(0.0, 0.0, W as f32, 20.0);
let theme = Theme {
command_line_bg: crate::types::Color::rgb(255, 255, 255),
selection: crate::types::Color::rgb(0, 0, 255),
selection_alpha: 1.0,
..Theme::default()
};
let surface = BitmapSurface::new(W, H);
surface.fill(1.0, 1.0, 1.0, 1.0);
let mut backend = MacBackend::new();
backend.set_current_font(make_font("Menlo", 14.0).expect("Menlo installed"));
backend.set_theme(theme);
let line_height = backend.line_height() as f64;
backend.begin_frame(Viewport::new(W as f32, H as f32, 1.0));
backend.enter_frame_scope(surface.context_ptr(), |b| {
b.draw_command_line_selection(rect, &cmd, Some((0, 1)));
});
backend.end_frame();
let layout = backend.command_line_layout(rect, &cmd);
let sel_rect = layout
.selection_bounds((0, 1))
.expect("non-empty selection should produce a rect");
let px = (sel_rect.x + sel_rect.width / 2.0) as u32;
let py = (line_height / 2.0) as u32;
let (r, g, b, _) = surface.pixel(px, py);
assert_eq!(
(r, g, b),
(0, 0, 255),
"selected column should paint the highlight colour",
);
let unsel_px = (sel_rect.x + sel_rect.width + sel_rect.width / 2.0) as u32;
let (r, g, b, _) = surface.pixel(unsel_px, py);
assert_eq!(
(r, g, b),
(255, 255, 255),
"unselected column should not paint the highlight",
);
}
#[test]
fn multibyte_cursor_offset_does_not_panic() {
let text = ":éditer";
assert!(!text.is_char_boundary(2));
let cmd = sample(text, Some(2), false);
let _ = paint_via_backend(&cmd, QRect::new(0.0, 0.0, W as f32, 20.0));
}
#[test]
fn right_aligned_text_is_pushed_to_the_right_edge() {
let cmd = sample("3/17", None, true);
let (surface, lh) = paint_via_backend(&cmd, QRect::new(0.0, 0.0, W as f32, 20.0));
let theme = Theme::default();
let bg = (
theme.command_line_bg.r,
theme.command_line_bg.g,
theme.command_line_bg.b,
);
let row = (lh / 2.0) as u32;
let left_quarter_all_bg = (0..W / 4).all(|x| {
let (r, g, b, _) = surface.pixel(x, row);
(r, g, b) == bg
});
assert!(
left_quarter_all_bg,
"right-aligned text must not paint into the left quarter",
);
let right_quarter_has_glyph = (W - W / 4..W).any(|x| {
(0..(lh as u32).min(H)).any(|dy| {
let (r, g, b, _) = surface.pixel(x, dy);
(r, g, b) != bg
})
});
assert!(
right_quarter_has_glyph,
"right-aligned text should paint glyphs in the right quarter",
);
}
#[test]
fn zero_width_rect_is_a_no_op() {
let cmd = sample(":wq", Some(1), false);
let (surface, _lh) = paint_via_backend(&cmd, QRect::new(0.0, 0.0, 0.0, 20.0));
assert_eq!(
{
let (r, g, b, _) = surface.pixel(1, 1);
(r, g, b)
},
(255, 255, 255),
"a zero-width command line should paint nothing at all",
);
}
#[test]
fn command_line_layout_hit_test_matches_painted_glyph_at_nonzero_origin() {
let origin_x = 24.0_f32;
let origin_y = 30.0_f32;
let rect = QRect::new(origin_x, origin_y, W as f32 - origin_x, 20.0);
let cmd = sample(":wq", None, false);
let surface = BitmapSurface::new(W, H);
surface.fill(1.0, 1.0, 1.0, 1.0);
let mut backend = MacBackend::new();
backend.set_current_font(make_font("Menlo", 14.0).expect("Menlo installed"));
backend.begin_frame(Viewport::new(W as f32, H as f32, 1.0));
backend.enter_frame_scope(surface.context_ptr(), |b| {
b.draw_command_line(rect, &cmd);
});
backend.end_frame();
let layout = backend.command_line_layout(rect, &cmd);
let char_width = backend.char_width();
assert!(char_width > 1.0, "Menlo char_width should be several px");
let y0 = origin_y as u32;
let y1 = (origin_y + rect.height).min(H as f32) as u32;
let find_painted = |x0: u32, x1: u32| -> Option<u32> {
for x in x0..x1.min(W) {
for y in y0..y1 {
let (r, g, b, _) = surface.pixel(x, y);
if (r, g, b) != (255, 255, 255) {
return Some(x);
}
}
}
None
};
let col0_x0 = origin_x as u32 + 1;
let col0_x1 = (origin_x + char_width).floor() as u32;
let px0 = find_painted(col0_x0, col0_x1)
.unwrap_or_else(|| panic!("column 0 (':') painted no pixel in {col0_x0}..{col0_x1}"));
assert_eq!(layout.hit_test(px0 as f32), 0);
let col1_x0 = (origin_x + char_width).ceil() as u32 + 1;
let col1_x1 = (origin_x + 2.0 * char_width).floor() as u32;
let px1 = find_painted(col1_x0, col1_x1)
.unwrap_or_else(|| panic!("column 1 ('w') painted no pixel in {col1_x0}..{col1_x1}"));
assert_eq!(layout.hit_test(px1 as f32), 1);
assert_eq!(layout.hit_test(0.0), 0);
assert_eq!(layout.hit_test(origin_x), 0);
}
}