Skip to content

Commit 6895b9f

Browse files
committed
small updates
1 parent 58cf476 commit 6895b9f

2 files changed

Lines changed: 173 additions & 5 deletions

File tree

rio-backend/src/crosswords/mod.rs

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4416,16 +4416,27 @@ impl<U: EventListener> Crosswords<U> {
44164416
// Absolute row = history_size + screen-relative row
44174417
let dest_row = self.history_size() as i64 + cursor_row as i64;
44184418

4419-
// Compute cell-based size
4419+
// kitty spec the `X=`/`Y=` sub-cell offset must be smaller
4420+
// than the cell size; kitty clamps out-of-range values to the
4421+
// cell box
4422+
let cell_x_offset = (placement.cell_x_offset as usize).min(cell_width - 1);
4423+
let cell_y_offset = (placement.cell_y_offset as usize).min(cell_height - 1);
4424+
4425+
// Compute cell-based size.
4426+
//
4427+
// the trick is the sub-cell offset shifts the image
4428+
// within its first cell, so it can spill into one extra
4429+
// row/column. Include it so cursor movement and row occupation
4430+
// cover the full image.
44204431
let columns = if placement.columns > 0 {
44214432
placement.columns
44224433
} else {
4423-
display_w.div_ceil(cell_width) as u32
4434+
(display_w + cell_x_offset).div_ceil(cell_width) as u32
44244435
};
44254436
let rows = if placement.rows > 0 {
44264437
placement.rows
44274438
} else {
4428-
display_h.div_ceil(cell_height) as u32
4439+
(display_h + cell_y_offset).div_ceil(cell_height) as u32
44294440
};
44304441

44314442
// Create overlay placement.
@@ -4454,8 +4465,8 @@ impl<U: EventListener> Crosswords<U> {
44544465
rows,
44554466
pixel_width: display_w as u32,
44564467
pixel_height: display_h as u32,
4457-
cell_x_offset: placement.cell_x_offset,
4458-
cell_y_offset: placement.cell_y_offset,
4468+
cell_x_offset: cell_x_offset as u32,
4469+
cell_y_offset: cell_y_offset as u32,
44594470
z_index: placement.z_index,
44604471
transmit_time,
44614472
};

rio-backend/src/graphics/kitty/mod.rs

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -617,6 +617,163 @@ fn test_image_row_occupation_exact_fit() {
617617
);
618618
}
619619

620+
#[test]
621+
fn test_subcell_offset_forwarded_and_clamped() {
622+
let event_listener = TestEventListener;
623+
let window_id = unsafe { WindowId::dummy() };
624+
625+
let mut term: Crosswords<TestEventListener> = Crosswords::new(
626+
crate::crosswords::CrosswordsSize::new(80, 24),
627+
crate::ansi::CursorShape::Block,
628+
event_listener,
629+
window_id,
630+
0,
631+
10_000,
632+
);
633+
634+
term.graphics.cell_width = 10.0;
635+
term.graphics.cell_height = 20.0;
636+
637+
let pixels = vec![255u8; 40 * 40 * 4];
638+
let graphic = GraphicData {
639+
id: GraphicId::new(1),
640+
width: 40,
641+
height: 40,
642+
color_type: ColorType::Rgba,
643+
pixels,
644+
is_opaque: true,
645+
resize: None,
646+
display_width: None,
647+
display_height: None,
648+
transmit_time: std::time::Instant::now(),
649+
};
650+
term.store_graphic(graphic);
651+
652+
// In-range `X=`/`Y=` flows through to the stored placement.
653+
let placement = kitty_graphics_protocol::PlacementRequest {
654+
image_id: 1,
655+
placement_id: 7,
656+
x: 0,
657+
y: 0,
658+
width: 0,
659+
height: 0,
660+
columns: 0,
661+
rows: 0,
662+
z_index: 0,
663+
virtual_placement: false,
664+
unicode_placeholder: 0,
665+
cursor_movement: 1,
666+
cell_x_offset: 7,
667+
cell_y_offset: 9,
668+
};
669+
term.place_graphic(placement);
670+
671+
let stored = term
672+
.graphics
673+
.kitty_placements
674+
.get(&(1, 7))
675+
.expect("placement stored");
676+
assert_eq!(stored.cell_x_offset, 7);
677+
assert_eq!(stored.cell_y_offset, 9);
678+
679+
// Per kitty spec the offset must be smaller than the cell size;
680+
// out-of-range values are clamped to the cell box.
681+
let placement = kitty_graphics_protocol::PlacementRequest {
682+
image_id: 1,
683+
placement_id: 8,
684+
x: 0,
685+
y: 0,
686+
width: 0,
687+
height: 0,
688+
columns: 0,
689+
rows: 0,
690+
z_index: 0,
691+
virtual_placement: false,
692+
unicode_placeholder: 0,
693+
cursor_movement: 1,
694+
cell_x_offset: 999,
695+
cell_y_offset: 999,
696+
};
697+
term.place_graphic(placement);
698+
699+
let stored = term
700+
.graphics
701+
.kitty_placements
702+
.get(&(1, 8))
703+
.expect("placement stored");
704+
assert_eq!(stored.cell_x_offset, 9, "clamped to cell_width - 1");
705+
assert_eq!(stored.cell_y_offset, 19, "clamped to cell_height - 1");
706+
}
707+
708+
#[test]
709+
fn test_subcell_offset_extends_row_occupation() {
710+
let event_listener = TestEventListener;
711+
let window_id = unsafe { WindowId::dummy() };
712+
713+
let mut term: Crosswords<TestEventListener> = Crosswords::new(
714+
crate::crosswords::CrosswordsSize::new(80, 24),
715+
crate::ansi::CursorShape::Block,
716+
event_listener,
717+
window_id,
718+
0,
719+
10_000,
720+
);
721+
722+
term.graphics.cell_width = 10.0;
723+
term.graphics.cell_height = 20.0;
724+
725+
// 40px tall image on 20px cells: exactly 2 rows without an offset.
726+
let pixels = vec![255u8; 40 * 40 * 4];
727+
let graphic = GraphicData {
728+
id: GraphicId::new(1),
729+
width: 40,
730+
height: 40,
731+
color_type: ColorType::Rgba,
732+
pixels,
733+
is_opaque: true,
734+
resize: None,
735+
display_width: None,
736+
display_height: None,
737+
transmit_time: std::time::Instant::now(),
738+
};
739+
term.store_graphic(graphic);
740+
741+
// `Y=15` shifts the image down within its first cell, so it spills
742+
// into a third row: ceil((40 + 15) / 20) = 3. Cursor movement and
743+
// occupation must cover that extra row.
744+
let placement = kitty_graphics_protocol::PlacementRequest {
745+
image_id: 1,
746+
placement_id: 7,
747+
x: 0,
748+
y: 0,
749+
width: 0,
750+
height: 0,
751+
columns: 0,
752+
rows: 0,
753+
z_index: 0,
754+
virtual_placement: false,
755+
unicode_placeholder: 0,
756+
cursor_movement: 0,
757+
cell_x_offset: 0,
758+
cell_y_offset: 15,
759+
};
760+
term.place_graphic(placement);
761+
762+
let stored = term
763+
.graphics
764+
.kitty_placements
765+
.get(&(1, 7))
766+
.expect("placement stored");
767+
assert_eq!(stored.rows, 3, "Y offset spills the image into a 3rd row");
768+
assert_eq!(stored.columns, 4, "no X offset: 40px / 10px = 4 columns");
769+
770+
// C=0: cursor lands on the last row of the image (row index rows - 1).
771+
assert_eq!(
772+
term.grid.cursor.pos.row.0, 2,
773+
"cursor advances to the extra row created by the Y offset"
774+
);
775+
}
776+
620777
#[test]
621778
fn test_image_row_occupation_single_row() {
622779
let event_listener = TestEventListener;

0 commit comments

Comments
 (0)