@@ -599,7 +599,17 @@ fn render_month_grid( |
| 599 | 599 | render_title(month, &layout, buf, styles); |
| 600 | 600 | render_weekdays(&layout, buf, styles); |
| 601 | 601 | |
| 602 | | - for cell in month.cells().filter(|cell| !cell.is_selected) { |
| 602 | + for cell in month |
| 603 | + .cells() |
| 604 | + .filter(|cell| !cell.is_selected && !cell.is_today) |
| 605 | + { |
| 606 | + render_cell(cell, &layout, agenda_source, buf, styles); |
| 607 | + } |
| 608 | + |
| 609 | + for cell in month |
| 610 | + .cells() |
| 611 | + .filter(|cell| cell.is_today && !cell.is_selected) |
| 612 | + { |
| 603 | 613 | render_cell(cell, &layout, agenda_source, buf, styles); |
| 604 | 614 | } |
| 605 | 615 | |
@@ -629,7 +639,19 @@ fn render_week_grid( |
| 629 | 639 | render_week_title(selected_week, &layout, buf, styles); |
| 630 | 640 | render_weekdays_for_week(&layout, buf, styles); |
| 631 | 641 | |
| 632 | | - for cell in selected_week.cells.iter().filter(|cell| !cell.is_selected) { |
| 642 | + for cell in selected_week |
| 643 | + .cells |
| 644 | + .iter() |
| 645 | + .filter(|cell| !cell.is_selected && !cell.is_today) |
| 646 | + { |
| 647 | + render_week_cell(cell, &layout, agenda_source, buf, styles); |
| 648 | + } |
| 649 | + |
| 650 | + for cell in selected_week |
| 651 | + .cells |
| 652 | + .iter() |
| 653 | + .filter(|cell| cell.is_today && !cell.is_selected) |
| 654 | + { |
| 633 | 655 | render_week_cell(cell, &layout, agenda_source, buf, styles); |
| 634 | 656 | } |
| 635 | 657 | |
@@ -1431,6 +1453,16 @@ mod tests { |
| 1431 | 1453 | calendar::CalendarDate, |
| 1432 | 1454 | }; |
| 1433 | 1455 | |
| 1456 | + #[derive(Debug, Clone, Copy)] |
| 1457 | + struct ExpectedBorder<'a> { |
| 1458 | + horizontal: &'a str, |
| 1459 | + vertical: &'a str, |
| 1460 | + corner: &'a str, |
| 1461 | + fg: Color, |
| 1462 | + bg: Color, |
| 1463 | + modifier: Modifier, |
| 1464 | + } |
| 1465 | + |
| 1434 | 1466 | fn date(year: i32, month: Month, day: u8) -> CalendarDate { |
| 1435 | 1467 | CalendarDate::from_ymd(year, month, day).expect("valid test date") |
| 1436 | 1468 | } |
@@ -1456,6 +1488,31 @@ mod tests { |
| 1456 | 1488 | .expect("day exists in month") |
| 1457 | 1489 | } |
| 1458 | 1490 | |
| 1491 | + fn assert_cell_perimeter(buffer: &Buffer, rect: Rect, expected: ExpectedBorder<'_>) { |
| 1492 | + let left = rect.left(); |
| 1493 | + let right = rect.right().saturating_sub(1); |
| 1494 | + let top = rect.top(); |
| 1495 | + let bottom = rect.bottom().saturating_sub(1); |
| 1496 | + let border_points = [ |
| 1497 | + ((left, top), expected.corner), |
| 1498 | + ((right, top), expected.corner), |
| 1499 | + ((left, bottom), expected.corner), |
| 1500 | + ((right, bottom), expected.corner), |
| 1501 | + ((left + 1, top), expected.horizontal), |
| 1502 | + ((left + 1, bottom), expected.horizontal), |
| 1503 | + ((left, top + 1), expected.vertical), |
| 1504 | + ((right, top + 1), expected.vertical), |
| 1505 | + ]; |
| 1506 | + |
| 1507 | + for (position, symbol) in border_points { |
| 1508 | + let cell = buffer.cell(position).expect("border cell exists"); |
| 1509 | + assert_eq!(cell.symbol(), symbol); |
| 1510 | + assert_eq!(cell.fg, expected.fg); |
| 1511 | + assert_eq!(cell.bg, expected.bg); |
| 1512 | + assert!(cell.modifier.contains(expected.modifier)); |
| 1513 | + } |
| 1514 | + } |
| 1515 | + |
| 1459 | 1516 | fn centered(width: usize, text: &str) -> String { |
| 1460 | 1517 | let padding = width.saturating_sub(text.len()); |
| 1461 | 1518 | let left = padding / 2; |
@@ -1540,6 +1597,20 @@ mod tests { |
| 1540 | 1597 | assert!(today_cell.modifier.contains(Modifier::BOLD)); |
| 1541 | 1598 | assert!(today_cell.modifier.contains(Modifier::DIM)); |
| 1542 | 1599 | |
| 1600 | + let today_rect = layout.cell_rect(today.week_index, today.weekday_index); |
| 1601 | + assert_cell_perimeter( |
| 1602 | + &buffer, |
| 1603 | + today_rect, |
| 1604 | + ExpectedBorder { |
| 1605 | + horizontal: "=", |
| 1606 | + vertical: "!", |
| 1607 | + corner: "+", |
| 1608 | + fg: Color::Yellow, |
| 1609 | + bg: Color::Reset, |
| 1610 | + modifier: Modifier::DIM, |
| 1611 | + }, |
| 1612 | + ); |
| 1613 | + |
| 1543 | 1614 | let filler_cell = buffer.cell((2, 3)).expect("filler label cell exists"); |
| 1544 | 1615 | assert_eq!(filler_cell.fg, Color::DarkGray); |
| 1545 | 1616 | assert!(filler_cell.modifier.contains(Modifier::DIM)); |
@@ -1553,22 +1624,49 @@ mod tests { |
| 1553 | 1624 | let selected = month.selected_cell().expect("selected cell exists"); |
| 1554 | 1625 | let rect = layout.cell_rect(selected.week_index, selected.weekday_index); |
| 1555 | 1626 | |
| 1556 | | - let top_left = buffer |
| 1557 | | - .cell((rect.x, rect.y)) |
| 1558 | | - .expect("selected border exists"); |
| 1559 | | - let top_edge = buffer |
| 1560 | | - .cell((rect.x + 1, rect.y)) |
| 1561 | | - .expect("selected border exists"); |
| 1562 | | - let left_edge = buffer |
| 1563 | | - .cell((rect.x, rect.y + 1)) |
| 1564 | | - .expect("selected border exists"); |
| 1565 | | - |
| 1566 | | - assert_eq!(top_left.symbol(), "#"); |
| 1567 | | - assert_eq!(top_edge.symbol(), "#"); |
| 1568 | | - assert_eq!(left_edge.symbol(), "#"); |
| 1569 | | - assert_eq!(top_left.fg, Color::Cyan); |
| 1570 | | - assert_eq!(top_left.bg, Color::Blue); |
| 1571 | | - assert!(top_left.modifier.contains(Modifier::BOLD)); |
| 1627 | + assert_cell_perimeter( |
| 1628 | + &buffer, |
| 1629 | + rect, |
| 1630 | + ExpectedBorder { |
| 1631 | + horizontal: "#", |
| 1632 | + vertical: "#", |
| 1633 | + corner: "#", |
| 1634 | + fg: Color::Cyan, |
| 1635 | + bg: Color::Blue, |
| 1636 | + modifier: Modifier::BOLD, |
| 1637 | + }, |
| 1638 | + ); |
| 1639 | + } |
| 1640 | + |
| 1641 | + #[test] |
| 1642 | + fn today_week_cell_has_full_focus_perimeter() { |
| 1643 | + let month = |
| 1644 | + CalendarMonth::from_dates(date(2026, Month::April, 20), date(2026, Month::April, 23)); |
| 1645 | + let area = Rect::new(0, 0, 84, 8); |
| 1646 | + let mut buffer = Buffer::empty(area); |
| 1647 | + WeekGrid::new(&month).render(area, &mut buffer); |
| 1648 | + |
| 1649 | + let layout = WeekGridLayout::new(area).expect("supported week layout"); |
| 1650 | + let week = selected_week(&month).expect("selected week exists"); |
| 1651 | + let today = week |
| 1652 | + .cells |
| 1653 | + .iter() |
| 1654 | + .find(|cell| cell.is_today) |
| 1655 | + .expect("today cell appears in selected week"); |
| 1656 | + let rect = layout.cell_rect(today.weekday_index); |
| 1657 | + |
| 1658 | + assert_cell_perimeter( |
| 1659 | + &buffer, |
| 1660 | + rect, |
| 1661 | + ExpectedBorder { |
| 1662 | + horizontal: "=", |
| 1663 | + vertical: "!", |
| 1664 | + corner: "+", |
| 1665 | + fg: Color::Yellow, |
| 1666 | + bg: Color::Reset, |
| 1667 | + modifier: Modifier::DIM, |
| 1668 | + }, |
| 1669 | + ); |
| 1572 | 1670 | } |
| 1573 | 1671 | |
| 1574 | 1672 | #[test] |