Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -1525,8 +1525,14 @@
row and column lines an interval with its own type and colour) and
`Gridlines` (only lines set away from `Line::default_style` are stored:
solid Automatic for Gantt Rows, Bottom Tier Column, Sheet Rows, Sheet
Columns, Title Vertical and Title Horizontal, none for the rest, which is
what the hosts drew before). `Formatting::gridlines` replaces the old
Columns, Title Vertical and Title Horizontal, what the hosts drew before,
and Current Date, solid as Project 2024 draws it (#1688; its green is not
measured, so Automatic; the hosts draw it only where the plan's
`CurrentDate` option is set, and a new docxy plan sets none); none for the
rest, so a Current Date set to none is stored). The default moved without
a format marker: a solid Automatic Current Date an older build stored is
now the default and is dropped on the next save, and a plugin's `null` on
Current Date now means solid; it sends `{"type":"none"}` to turn it off). `Formatting::gridlines` replaces the old
opaque string; `formatting.xml` carries `<gridline>` children (an older
root `gridlines` attribute is ignored). A plugin command's `view.gridlines`
is an object of line name to `{type, color, interval, interval_type,
Expand Down
2 changes: 1 addition & 1 deletion docs/ui-test-harness.md
Original file line number Diff line number Diff line change
Expand Up @@ -445,7 +445,7 @@ State keys, as the app reports them after every driving verb:
| `timeline_h` | Project (#1535): the Timeline pane's height in px: 84 by default, 60 to half the window once the grip under it is dragged. Window view state, never saved |
| `nonworking` | Project: Timeline's inclusive, merged shaded day runs as `[[first, last], ...]` in Project date form (`Wed 3/4/26`). Empty when each day is narrower than 2 px or the project calendar has no weekly working time. Reported even while the Timeline is hidden |
| `filler_rows` | Project: ruled empty rows visible below the last task, from the last drawn frame's `project-body` height (0 before the first layout). Unlike the other keys it trails a driving verb by a frame, so settle with a `shot` before asserting it |
| `gridlines`, `gridlines_set` | Project (#1575): the plan's gridlines set away from their default, in Project's list order, each `{line, type, color, interval, interval_type, interval_color}` (`gridlines.0.line is Gantt Rows`), and how many there are (0 on a plan drawn as before the model) |
| `gridlines`, `gridlines_set` | Project (#1575): the plan's gridlines set away from their default, in Project's list order, each `{line, type, color, interval, interval_type, interval_color}` (`gridlines.0.line is Gantt Rows`), and how many there are (0 on a plan with every line at its default) |
| `cell_styles` | Project (#1664): the plan's cell text styles, each `{uid, column, ...style}` with the style's set keys (`bold`, `strikethrough`, `pattern`, ...) (`cell_styles.0.pattern is solid`) |
| `row_categories` | Project (#1665): each shown task row's text style categories as `"<uid>:<cat>\|<cat>..."`, general first (`row_categories.0 is 1:All\|Critical Tasks`); blank rows left out; `Late Tasks` and `Placeholder Tasks` only when a loaded manifest opts them in; `[]` without a UI plugin |
| `ribbon_tab` | current kind-aware ribbon tab name (`Task`, `Resource`, `View`, `Home`, etc.; the contextual table tabs are `Table Design` and `Table Layout`, though the strip draws the second as plain "Layout", as Word does) |
Expand Down
49 changes: 43 additions & 6 deletions projcore/src/formatting/gridlines.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
//! line Project's "Line to change" list names has a normal type and colour
//! and, for the row and column lines, an interval that draws every Nth line
//! differently. The default of every line is what the hosts drew before the
//! model existed, so a plan without gridlines looks as it did; a plan stores
//! model existed, so a plan without gridlines looks as it did, except Current
//! Date, which is solid Automatic as Project 2024 draws it (#1688); a plan stores
//! only the lines that differ from their default. A UI plugin command's
//! `view.gridlines` reply edits them ([`Gridlines::apply`]); the hosts' table
//! and chart draw from them.
Expand Down Expand Up @@ -163,16 +164,21 @@ impl Line {

/// What the hosts drew before the model (#1575): the table's row and
/// column rules, the chart's row rules and bottom-tier (day) lines and the
/// timescale's cell borders, all solid in the host's own colour; nothing
/// else.
/// timescale's cell borders, all solid in the host's own colour.
///
/// Current Date is not one of those: it is solid Automatic, as Project
/// 2024 draws it (#1688; Project's green is not measured). The hosts draw
/// it only where the plan's `CurrentDate` option is set, and a new docxy
/// plan sets none. Status Date, Project Start and Finish are none.
pub fn default_style(self) -> Gridline {
match self {
Line::GanttRows
| Line::BottomTierColumn
| Line::SheetRows
| Line::SheetColumns
| Line::TitleVertical
| Line::TitleHorizontal => Gridline::solid(),
| Line::TitleHorizontal
| Line::CurrentDate => Gridline::solid(),
_ => Gridline::default(),
}
}
Expand Down Expand Up @@ -450,7 +456,7 @@ mod tests {
}

#[test]
fn the_defaults_are_what_the_hosts_drew() {
fn the_defaults_are_the_hosts_rules_and_projects_current_date() {
let g = Gridlines::default();
assert!(g.is_empty());
for l in Line::ALL {
Expand All @@ -462,6 +468,7 @@ mod tests {
| Line::SheetColumns
| Line::TitleVertical
| Line::TitleHorizontal
| Line::CurrentDate
);
assert_eq!(g.get(l).line_type == LineType::Solid, solid, "{}", l.name());
assert_eq!(g.get(l).color, None, "{} is automatic", l.name());
Expand Down Expand Up @@ -499,7 +506,7 @@ mod tests {
g.set(
Line::CurrentDate,
Gridline {
line_type: LineType::Solid,
line_type: LineType::Dotted,
interval: 3,
interval_type: LineType::Dotted,
..Gridline::default()
Expand All @@ -526,6 +533,36 @@ mod tests {
assert!(!g.get(Line::StatusDate).draws());
}

#[test]
fn the_current_date_line_draws_by_default_and_none_is_stored() {
// Current Date draws by default, as in Project 2024 (#1688).
assert_eq!(Line::CurrentDate.default_style(), Gridline::solid());
assert_eq!(Line::StatusDate.default_style(), Gridline::default());
assert_eq!(Line::ProjectStart.default_style(), Gridline::default());
assert_eq!(Line::ProjectFinish.default_style(), Gridline::default());
let g = Gridlines::default();
assert!(g.get(Line::CurrentDate).draws());
assert_eq!(g.set_lines().count(), 0);
let json = g.to_json().to_string();
assert!(json.contains(r#""Current Date":{"type":"solid""#), "{json}");
// Turning it off is a real setting, kept through formatting.xml.
let mut g = Gridlines::default();
g.apply(
&Json::parse(r#"{"Current Date":{"type":"none"}}"#).unwrap(),
"view.gridlines",
)
.unwrap();
assert_eq!(g.set_lines().count(), 1);
assert!(!g.get(Line::CurrentDate).draws());
let mut f = crate::formatting::Formatting::default();
f.gridlines = g;
let back = crate::formatting::Formatting::from_xml(&f.to_xml()).unwrap();
assert_eq!(
back.gridlines.get(Line::CurrentDate).line_type,
LineType::None
);
}

#[test]
fn a_view_reply_edits_lines_by_name() {
let mut g = Gridlines::default();
Expand Down
3 changes: 2 additions & 1 deletion suite/docxy/src/project/gantt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -651,7 +651,8 @@ fn backdrop(
pub(crate) fn body_grid(view: &ProjectView, pal: Pal) -> impl IntoElement {
let cal = view.ed.project().project_shading_calendar();
// The plan's gridlines (#1575): each line's type, colour and interval;
// the defaults are the rules drawn before the model.
// the defaults are the rules drawn before the model, and Current Date
// solid Automatic as Project 2024 draws it (#1688).
let grid = view.ed.project().formatting.gridlines.clone();
let dates = date_lines(
&view.ed,
Expand Down
24 changes: 24 additions & 0 deletions suite/docxy/src/project/gantt/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -786,6 +786,30 @@ fn a_status_date_line_needs_a_status_date() {
assert!(at(Line::StatusDate) < at(Line::CurrentDate), "{xs:?}");
}

#[test]
fn a_plan_with_a_current_date_draws_its_line_by_default() {
use projcore::formatting::gridlines::{Gridline, Gridlines, Line};
// Project 2024 draws a Current Date line by default (#1688).
let mut p = untitled_project();
let start = p.start_date.unwrap();
p.tasks = vec![task(1, 1, 1)];
let day = projcore::DateTime::from_minutes(start.minutes() + 2 * 1440).to_mspdi();
p.set_option("CurrentDate", Some(&day));
let mut v = ProjectView::new(p, false);
v.layout(2000.);
let scale = v.chart_scale();
let lines = |grid: &Gridlines| -> Vec<Line> {
date_lines(&v.ed, grid, scale, 0., scale.width())
.into_iter()
.map(|(line, _, _)| line)
.collect()
};
let mut grid = Gridlines::default();
assert_eq!(lines(&grid), vec![Line::CurrentDate]);
grid.set(Line::CurrentDate, Gridline::default());
assert!(lines(&grid).is_empty());
}

#[test]
fn filler_rows_count_the_ruled_space_below_the_last_task() {
assert_eq!(filler_rows_of(280., 0., 0, ROW_H), 10);
Expand Down
25 changes: 25 additions & 0 deletions yppxy/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3065,6 +3065,31 @@ mod tests {
use super::*;
use projcore::editor::parse_duration;

#[test]
fn a_plan_with_a_current_date_draws_its_line_by_default() {
// Project 2024 draws a Current Date line by default (#1688).
let start = projcore::datetime::DateTime::from_ymd_hm(2026, 3, 2, 8, 0);
let mut proj = Project {
start_date: Some(start),
..Project::default()
};
let day = projcore::datetime::DateTime::from_ymd_hm(2026, 3, 4, 8, 0);
proj.set_option("CurrentDate", Some(&day.to_mspdi()));
let origin = start.day_number();
let ed = Editor::new(proj.clone());
let marks = gridline_marks(&ed, origin);
let current: Vec<_> = marks.iter().filter(|m| m.day == 2).collect();
assert_eq!(current.len(), 1, "{marks:?}");
assert!(!current[0].broken);
// Turned off, it is not drawn.
proj.formatting.gridlines.set(
projcore::formatting::gridlines::Line::CurrentDate,
projcore::formatting::gridlines::Gridline::default(),
);
let ed = Editor::new(proj);
assert!(gridline_marks(&ed, origin).iter().all(|m| m.day != 2));
}

fn unnamed_summary_fixture(title: &str) -> Project {
Project {
title: title.into(),
Expand Down
Loading