diff --git a/corpus/styles/candidate/.snowbound/themes.json b/corpus/styles/candidate/.snowbound/themes.json
index 549945dd36f23fa7a726c139f4d6865ceaedd612..23fc2f6d5ab4d043bdab59d4ce4a64e2ccf6a2b1 100644
--- a/corpus/styles/candidate/.snowbound/themes.json
+++ b/corpus/styles/candidate/.snowbound/themes.json
@@ -3,30 +3,30 @@
"assignments": [
{
"scope": {
- "section": "125e8f01c6ee404b96cc675d00ceb659"
+ "section": "f0eb3316ed995d4f9a403ead450892a1"
},
"theme": "onenote",
"assigned": 134000000000000000
},
{
"scope": {
- "section": "af4f8ea23c5f7e41838ac7cbea473590"
+ "section": "13d8c0f86a617e4cb35733d2e514408f"
},
"theme": "manuscript",
"assigned": 134000000000000000
},
{
"scope": {
- "section": "18af2996b565454c971191d887028fc8"
+ "section": "aae44a30901cff4aaca7fadf0c8bf659"
},
"theme": "editorial",
"assigned": 134000000000000000
},
{
"scope": {
- "section": "f0ca724b3a9d3d4b82bc6f28bd77966c"
+ "section": "709fb4685c18f945a24f9aa162fc78e4"
},
- "theme": "modern",
+ "theme": "modern-2",
"assigned": 134000000000000000
}
]
diff --git a/corpus/styles/candidate/Editorial.one b/corpus/styles/candidate/Editorial.one
index 1f5b13635c9519f0b1d6470a2a80781333fa1743..24ed6e93c7f9f84e53328a7b75e492de54dd8aba 100644
Binary files a/corpus/styles/candidate/Editorial.one and b/corpus/styles/candidate/Editorial.one differ
diff --git a/corpus/styles/candidate/Manuscript.one b/corpus/styles/candidate/Manuscript.one
index d595551c7d0126f3a0f5e118d6bf55b45c038576..2debba02524ead50186d43e9b237e716e3766b6a 100644
Binary files a/corpus/styles/candidate/Manuscript.one and b/corpus/styles/candidate/Manuscript.one differ
diff --git a/corpus/styles/candidate/Modern.one b/corpus/styles/candidate/Modern.one
index 7228d5e88fbbda251c692ea98fe89da570505f84..4a38d76d1405c3de7884eeaee32346e0659d567f 100644
Binary files a/corpus/styles/candidate/Modern.one and b/corpus/styles/candidate/Modern.one differ
diff --git a/corpus/styles/candidate/OneNote.one b/corpus/styles/candidate/OneNote.one
index 6915847a3b5e8c59004dcd085c9d9705da76d783..91a55e1519e618291e533fea933417773a9e4751 100644
Binary files a/corpus/styles/candidate/OneNote.one and b/corpus/styles/candidate/OneNote.one differ
diff --git a/corpus/styles/candidate/Open Notebook.onetoc2 b/corpus/styles/candidate/Open Notebook.onetoc2
index d4ceb28b8e6899338a0ccf41cc757423a03dd3bd..a0d64387026996a2b08255213ee699d343223d6b 100644
Binary files a/corpus/styles/candidate/Open Notebook.onetoc2 and b/corpus/styles/candidate/Open Notebook.onetoc2 differ
diff --git a/corpus/styles/cold/commands.jsonl b/corpus/styles/cold/commands.jsonl
index 0843332e2b3ce3020bb737357ca84f70ba069ead..746eea2f97bf65d2e89558bee279d263bc14fc74 100644
--- a/corpus/styles/cold/commands.jsonl
+++ b/corpus/styles/cold/commands.jsonl
@@ -1,4 +1,4 @@
{"command": "powershell -NoProfile -Command \"Expand-Archive -LiteralPath C:\\one-tests\\transfer.zip -DestinationPath C:\\one-tests\\runs\\capture\\notebook\"", "exit": 0, "stdout": "", "stderr": "", "error": null}
-{"command": "powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File C:\\one-tests\\author.ps1 -Root C:\\one-tests\\runs\\capture -CloneHost ONE-M6-222E5D78", "exit": 0, "stdout": "", "stderr": "", "error": null}
-{"command": "powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File C:\\one-tests\\read-current.ps1 -Root C:\\one-tests\\runs\\capture -CloneHost ONE-M6-222E5D78 -ExpectedPages 4 -UseCurrentCache -KeepOpen", "exit": 0, "stdout": "Read 4 sections and 4 pages.\r\n", "stderr": "", "error": null}
+{"command": "powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File C:\\one-tests\\author.ps1 -Root C:\\one-tests\\runs\\capture -CloneHost ONE-M6-F5FCF7CA", "exit": 0, "stdout": "", "stderr": "", "error": null}
+{"command": "powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File C:\\one-tests\\read-current.ps1 -Root C:\\one-tests\\runs\\capture -CloneHost ONE-M6-F5FCF7CA -ExpectedPages 4 -UseCurrentCache -KeepOpen", "exit": 0, "stdout": "Read 4 sections and 4 pages.\r\n", "stderr": "", "error": null}
{"command": "powershell -NoProfile -Command \"Compress-Archive -Force -Path C:\\one-tests\\runs\\capture\\* -DestinationPath C:\\one-tests\\captured.zip\"", "exit": 0, "stdout": "", "stderr": "", "error": null}
diff --git a/corpus/styles/cold/machine.json b/corpus/styles/cold/machine.json
index 5d1ccba7f887bfaf23686dfb5c2dae77c791d0be..d9ad4f5236be591af98c703770701c5faaa9c69f 100644
--- a/corpus/styles/cold/machine.json
+++ b/corpus/styles/cold/machine.json
@@ -1 +1 @@
-{"name": "m6-222e5d78", "hostname": "ONE-M6-222E5D78"}
+{"name": "m6-f5fcf7ca", "hostname": "ONE-M6-F5FCF7CA"}
diff --git a/corpus/styles/cold/notebook/Editorial.one b/corpus/styles/cold/notebook/Editorial.one
index 42462a2e46e8b31d90bfd6e8fc0254a167a8add7..42f857451ba8abf0e81045c2d3b628320494a51f 100644
Binary files a/corpus/styles/cold/notebook/Editorial.one and b/corpus/styles/cold/notebook/Editorial.one differ
diff --git a/corpus/styles/cold/notebook/Manuscript.one b/corpus/styles/cold/notebook/Manuscript.one
index 8f3ce5fb57b2b2ff62ed6076782a98fac5baf849..6d1f3cd16fe92799c184c6678aa689fc3a8f62d7 100644
Binary files a/corpus/styles/cold/notebook/Manuscript.one and b/corpus/styles/cold/notebook/Manuscript.one differ
diff --git a/corpus/styles/cold/notebook/Modern.one b/corpus/styles/cold/notebook/Modern.one
index 403d594c04d6df0f50a8d65dc8c5b0178a5c40de..e3fe52ba72f439b9e62363cfe795e4e01414aff9 100644
Binary files a/corpus/styles/cold/notebook/Modern.one and b/corpus/styles/cold/notebook/Modern.one differ
diff --git a/corpus/styles/cold/notebook/OneNote.one b/corpus/styles/cold/notebook/OneNote.one
index eabc7f8002f2b284c95f9b55622de02338c54e86..f0d94cc4502a0c7ce214f7d3c5ff2016dc6244e1 100644
Binary files a/corpus/styles/cold/notebook/OneNote.one and b/corpus/styles/cold/notebook/OneNote.one differ
diff --git a/corpus/styles/cold/notebook/Open Notebook.onetoc2 b/corpus/styles/cold/notebook/Open Notebook.onetoc2
index 6a5357126ab396e70a19ac2ae665da6f24f35860..173f9614ad2806f275337099312ee2669cf591ad 100644
Binary files a/corpus/styles/cold/notebook/Open Notebook.onetoc2 and b/corpus/styles/cold/notebook/Open Notebook.onetoc2 differ
diff --git a/corpus/styles/cold/read/environment.json b/corpus/styles/cold/read/environment.json
index e93a5211ce6a1aba021b77c79ee65472d25d7ba3..bcacd47aaab9e1d7dc2ea05d8c2181a0d19883cf 100644
--- a/corpus/styles/cold/read/environment.json
+++ b/corpus/styles/cold/read/environment.json
@@ -1,7 +1,7 @@
{
"powershell": "5.1.14409.1005",
"schema": "xs2010",
- "hostname": "ONE-M6-222E5D78",
+ "hostname": "ONE-M6-F5FCF7CA",
"cold": false,
"onenote": "14.0.4763.1000"
}
diff --git a/corpus/styles/cold/read/hierarchy.xml b/corpus/styles/cold/read/hierarchy.xml
index 21cad963c0a70f32b9f24f30a03e43419f1311f5..2e84ba8ccaf5c8bc608567fac40c07bd120cc051 100644
--- a/corpus/styles/cold/read/hierarchy.xml
+++ b/corpus/styles/cold/read/hierarchy.xml
@@ -1,2 +1,2 @@
-
+
diff --git a/corpus/styles/cold/read/page-000.navigation.json b/corpus/styles/cold/read/page-000.navigation.json
index 556fccc0ba9aafbacaa8b574185841ec2226b9ac..eb3009ecdd53b4c3f537147cba67b7f35fb93b77 100644
--- a/corpus/styles/cold/read/page-000.navigation.json
+++ b/corpus/styles/cold/read/page-000.navigation.json
@@ -6,7 +6,7 @@
"h": 600,
"error": null,
"win": {
- "title": "Heading 1 in Manuscript - Microsoft OneNote",
+ "title": "Heading 1 in OneNote - Microsoft OneNote",
"class": "Framework::CFrame",
"dialog": false
}
diff --git a/corpus/styles/cold/read/page-000.png b/corpus/styles/cold/read/page-000.png
index cf74c1e244c403efb231e2bd111d7986ab78ec70..f0054128fc722539634e17b44dce82dca4a2b9e9 100644
Binary files a/corpus/styles/cold/read/page-000.png and b/corpus/styles/cold/read/page-000.png differ
diff --git a/corpus/styles/cold/read/page-000.xml b/corpus/styles/cold/read/page-000.xml
index 0a3a9b4653d5d2510d1851ed2749ae3eecf056d9..c23fa4941ab433dbb201a60b7632820b5dafeca8 100644
--- a/corpus/styles/cold/read/page-000.xml
+++ b/corpus/styles/cold/read/page-000.xml
@@ -1,7 +1,8 @@
-Heading 2 in Manuscript]]>Heading 3 in Manuscript]]>Heading 4 in Manuscript]]>Heading 6 in Manuscript]]>Quote in Manuscript]]>
+Heading 1 in OneNote]]>Heading 2 in OneNote]]>Heading 3 in OneNote]]>Heading 4 in OneNote]]>Heading 6 in OneNote]]>Quote in OneNote]]>
diff --git a/corpus/styles/cold/read/page-001.navigation.json b/corpus/styles/cold/read/page-001.navigation.json
index eb3009ecdd53b4c3f537147cba67b7f35fb93b77..fbef07913da8ee5d5dac2283fcacb7e449ba65d5 100644
--- a/corpus/styles/cold/read/page-001.navigation.json
+++ b/corpus/styles/cold/read/page-001.navigation.json
@@ -6,7 +6,7 @@
"h": 600,
"error": null,
"win": {
- "title": "Heading 1 in OneNote - Microsoft OneNote",
+ "title": "Heading 1 in Modern - Microsoft OneNote",
"class": "Framework::CFrame",
"dialog": false
}
diff --git a/corpus/styles/cold/read/page-001.png b/corpus/styles/cold/read/page-001.png
index a5eb0b44677bb5f2dc2c2b6d278330c5775261da..52782aac431eb65ef6fa40043f7bd444cc753297 100644
Binary files a/corpus/styles/cold/read/page-001.png and b/corpus/styles/cold/read/page-001.png differ
diff --git a/corpus/styles/cold/read/page-001.xml b/corpus/styles/cold/read/page-001.xml
index ebc52a01470deea7c86e2d90066b47c2c3ebbfec..59154708f7de31d84f9bfc0d91e18e68ab1e4c4d 100644
--- a/corpus/styles/cold/read/page-001.xml
+++ b/corpus/styles/cold/read/page-001.xml
@@ -1,8 +1,9 @@
-Heading 1 in OneNote]]>Heading 2 in OneNote]]>Heading 3 in OneNote]]>Heading 4 in OneNote]]>Heading 6 in OneNote]]>Quote in OneNote]]>
+Heading 1 in Modern]]>Heading 2 in Modern]]>Heading 3 in Modern]]>Heading 4 in Modern]]>Heading 5 in Modern]]>Heading 6 in Modern]]>Quote in Modern]]>
diff --git a/corpus/styles/cold/read/page-002.png b/corpus/styles/cold/read/page-002.png
index 235a6a47216251b5bd1ef8b008a07c93d72345c0..0f5ff46430e34aa7971d9584457be80632a4bdf8 100644
Binary files a/corpus/styles/cold/read/page-002.png and b/corpus/styles/cold/read/page-002.png differ
diff --git a/corpus/styles/cold/read/page-002.xml b/corpus/styles/cold/read/page-002.xml
index 58207e109ab7de0708bea486bea44f6486295835..7ed0b85e7bff9253474475955ecf43d03ea4501e 100644
--- a/corpus/styles/cold/read/page-002.xml
+++ b/corpus/styles/cold/read/page-002.xml
@@ -1,8 +1,8 @@
-Heading 1 in Editorial]]>Heading 2 in Editorial]]>Heading 3 in Editorial]]>Heading 4 in Editorial]]>Heading 6 in Editorial]]>Quote in Editorial]]>
+Heading 1 in Editorial]]>Heading 2 in Editorial]]>Heading 3 in Editorial]]>Heading 4 in Editorial]]>Heading 6 in Editorial]]>Quote in Editorial]]>
diff --git a/corpus/styles/cold/read/page-003.navigation.json b/corpus/styles/cold/read/page-003.navigation.json
index fbef07913da8ee5d5dac2283fcacb7e449ba65d5..556fccc0ba9aafbacaa8b574185841ec2226b9ac 100644
--- a/corpus/styles/cold/read/page-003.navigation.json
+++ b/corpus/styles/cold/read/page-003.navigation.json
@@ -6,7 +6,7 @@
"h": 600,
"error": null,
"win": {
- "title": "Heading 1 in Modern - Microsoft OneNote",
+ "title": "Heading 1 in Manuscript - Microsoft OneNote",
"class": "Framework::CFrame",
"dialog": false
}
diff --git a/corpus/styles/cold/read/page-003.png b/corpus/styles/cold/read/page-003.png
index 6cab06bc59975eb692f514c892f4cc47c1807d3c..ed25a5304fec32984cfccceffdeecfb3d0bb35a6 100644
Binary files a/corpus/styles/cold/read/page-003.png and b/corpus/styles/cold/read/page-003.png differ
diff --git a/corpus/styles/cold/read/page-003.xml b/corpus/styles/cold/read/page-003.xml
index 60f71dd65b7be922721730dfa3f9c45bc3e4a52a..af3bcc55a623d5a9f6c81dbf9abf271be2f75db6 100644
--- a/corpus/styles/cold/read/page-003.xml
+++ b/corpus/styles/cold/read/page-003.xml
@@ -1,10 +1,7 @@
-Heading 1 in Modern]]>Heading 2 in Modern]]>Heading 3 in Modern]]>Heading 4 in Modern]]>Heading 5 in Modern]]>Heading 6 in Modern]]>Page Title in Modern]]>Quote in Modern]]>
+Heading 2 in Manuscript]]>Heading 3 in Manuscript]]>Heading 4 in Manuscript]]>Heading 6 in Manuscript]]>Quote in Manuscript]]>
diff --git a/corpus/styles/cold/run.json b/corpus/styles/cold/run.json
index 8c605f3ac1747131b7482d6df45fe748c6729c44..ef5eb72df7edda326be0f35ce40f2e7ce099e1bc 100644
--- a/corpus/styles/cold/run.json
+++ b/corpus/styles/cold/run.json
@@ -1,7 +1,7 @@
{
- "notebook": "/private/tmp/snowbound-styles/candidate",
+ "notebook": "/private/var/folders/90/4f4rngds01j55ggdj_vglk_c0000gn/T/styles-export",
"expected_pages": 4,
- "author": "/Users/clo/dev/one/tools/native/styles.ps1",
+ "author": "/Users/clo/dev/one-styles-review/tools/native/styles.ps1",
"author_timeout_seconds": 600,
"inspect": false,
"collect_notebook": true,
diff --git a/corpus/styles/cold/source.json b/corpus/styles/cold/source.json
index 3788e618b7f47dc909b3ea59855c8f75ef059db2..4dbea11f7f6a2a93629a4acd850414502f94cecf 100644
--- a/corpus/styles/cold/source.json
+++ b/corpus/styles/cold/source.json
@@ -1,38 +1,38 @@
[
{
"path": ".snowbound/themes.json",
- "bytes": 669,
- "sha256": "0ede06c023cbdfa06fe307225baeb461f5a70e6ce368ae69dec9d405ef5822ac",
- "mtime_ns": 1790821816933321351
+ "bytes": 671,
+ "sha256": "d6b191076a9f833cb3e79a38121ed9d1fe29a405a79138969fb88b0b43066594",
+ "mtime_ns": 1790988700942364730
},
{
"path": "Editorial.one",
"bytes": 12096,
- "sha256": "0f65b622cdbeee22b146a6259074aeb4fd2b398d2bf982c7d712db31d190b6d1",
- "mtime_ns": 1790821816932162894
+ "sha256": "79f3d171ad78db53de276119c81f2fe4b6e298a6554494f407cd74436d43ecec",
+ "mtime_ns": 1790988700940784899
},
{
"path": "Manuscript.one",
"bytes": 12120,
- "sha256": "109d595430cf434fabcab11bfe233a34b5182a4b48f8cc5146885ee22936f636",
- "mtime_ns": 1790821816931895020
+ "sha256": "f4d25c78ed8537f486afa7e4e1853f9816895dc5d794c598248de649fac851ef",
+ "mtime_ns": 1790988700940457857
},
{
"path": "Modern.one",
"bytes": 12000,
- "sha256": "9ea5680d90a9b44bd036d4d50f5bcaae5b8f8ac505af4623e0ac3d0488b09ce2",
- "mtime_ns": 1790821816932649894
+ "sha256": "a91513231304e0a27e6896cf374cff98ddc4528a479622f0cbfb7577da151986",
+ "mtime_ns": 1790988700941597439
},
{
"path": "OneNote.one",
"bytes": 12072,
- "sha256": "5b019d8246db5a2728de5502d30b2a1bfe7a0a5d376c1a5d15ce98ac551ed210",
- "mtime_ns": 1790821816932870935
+ "sha256": "1f84914801b00eb77a201c148146afc5e2544c207528710d70937833750ab300",
+ "mtime_ns": 1790988700941853856
},
{
"path": "Open Notebook.onetoc2",
"bytes": 2036,
- "sha256": "9f613cf0b5db95b8809fe7d325cae9c37e8b9ae57fa3fff3b7d669649f1b416c",
- "mtime_ns": 1790821816932392186
+ "sha256": "a89533b7656fd9f5db22dadec54550a520b1b77980aad8398a7f3126f82b9d00",
+ "mtime_ns": 1790988700941091815
}
]
diff --git a/crates/mobile/src/library.rs b/crates/mobile/src/library.rs
index 993caaf77378bbc7540ffd8499c1eb0202b9fe7d..d3f48c587cba03bd6462f4214d453532112cf41c 100644
--- a/crates/mobile/src/library.rs
+++ b/crates/mobile/src/library.rs
@@ -1567,8 +1567,17 @@ pub unsafe extern "C" fn sb_notebook_create(
time: *const c_char,
error: *mut *mut c_char,
) -> bool {
+ use notebook::sidecar::themes::{Assignment, DEFAULT, Scope, Themes};
let created = dated(&string(author), &string(date), &string(time)).and_then(|page| {
- Notebook::create(string(path), string(cache), Notebook::NEW_COLOR, &page)?;
+ let notebook = Notebook::create(string(path), string(cache), Notebook::NEW_COLOR, &page)?;
+ notebook.save_themes(Themes {
+ assignments: vec![Assignment {
+ scope: Scope::Notebook,
+ theme: Some(DEFAULT.into()),
+ assigned: filetime(),
+ }],
+ ..Default::default()
+ })?;
Ok(())
});
created.map_err(|cause| failed(cause, error)).is_ok()
diff --git a/crates/notebook/src/sidecar/themes.rs b/crates/notebook/src/sidecar/themes.rs
index 610c782049d5cfe59613f0033782733e24a3b564..5d93db7125914171107fd00a915b6c78a8005e5d 100644
--- a/crates/notebook/src/sidecar/themes.rs
+++ b/crates/notebook/src/sidecar/themes.rs
@@ -112,8 +112,12 @@ impl Themes {
all
}
+ /// The theme `id` names, retired built-ins too, which pages may still wear.
pub fn theme(&self, id: &str) -> Option {
- self.all().into_iter().find(|theme| theme.id == id)
+ shipped()
+ .into_iter()
+ .chain(self.themes.iter().filter(|theme| !theme.deleted).cloned())
+ .find(|theme| theme.id == id)
}
/// The theme `scope` names itself, if one it can find.
@@ -224,8 +228,9 @@ pub(crate) fn write(storage: &dyn Storage, change: Themes) -> Result {
Err(io::Error::from(io::ErrorKind::ResourceBusy).into())
}
-fn is_built_in(id: &str) -> bool {
- built_in().iter().any(|theme| theme.id == id)
+/// Whether `id` names a theme Snowbound ships, or once offered.
+pub fn is_built_in(id: &str) -> bool {
+ shipped().iter().any(|theme| theme.id == id)
}
/// The accent of a section coloured `section` (a COLORREF; none for OneNote's None), as a
@@ -350,10 +355,22 @@ const ITALIC: [bool; 2] = [false, true];
const BOTH: [bool; 2] = [true, true];
const NONE: [f32; 2] = [0.0; 2];
-/// The themes Snowbound ships. Once shipped a built-in never changes: two versions
-/// disagreeing would restyle a page back and forth as each opens it. A changed look ships
-/// under a new id.
+/// The theme new notebooks take unless the user chooses another.
+pub const DEFAULT: &str = "modern-2";
+
+/// Built-ins a page wears still but no longer offered: a changed look ships under a new id.
+const RETIRED: [&str; 1] = ["modern"];
+
+/// The themes Snowbound offers, OneNote 2010's first.
pub fn built_in() -> Vec {
+ let mut offered = shipped();
+ offered.retain(|theme| !RETIRED.contains(&theme.id.as_str()));
+ offered
+}
+
+/// Every theme Snowbound ever shipped. Once shipped a built-in's look never changes: two
+/// versions disagreeing would restyle a page back and forth as each opens it.
+fn shipped() -> Vec {
let theme = |id: &str, name: &str, styles: [ThemeStyle; 11]| Theme {
id: id.into(),
name: name.into(),
@@ -366,6 +383,11 @@ pub fn built_in() -> Vec {
deleted: false,
};
let blue = Some("#366092");
+ let tinted = |style: ThemeStyle| ThemeStyle {
+ color: Some(color_hex(accent(None))),
+ accent: true,
+ ..style
+ };
vec![
// OneNote 2010's own (lab, 2026-09-30).
theme(
@@ -419,9 +441,27 @@ pub fn built_in() -> Vec {
style("Calibri", 11.5, PLAIN, Some("#262626"), [0.0, 3.0]),
],
),
+ // OneNote's page title in Arial; its accent the section's.
theme(
- "modern",
+ "modern-2",
"Modern",
+ [
+ tinted(style("Arial", 16.0, BOLD, None, [10.0, 2.0])),
+ style("Arial", 13.0, BOLD, Some("#1B2631"), [8.0, 2.0]),
+ style("Arial", 11.0, BOLD, Some("#1B2631"), [6.0, 0.0]),
+ style("Arial", 10.5, BOTH, Some("#1B2631"), [6.0, 0.0]),
+ tinted(style("Arial", 10.5, BOLD, None, [6.0, 0.0])),
+ tinted(style("Arial", 10.5, ITALIC, None, [6.0, 0.0])),
+ style("Arial", 17.0, PLAIN, None, NONE),
+ style("Arial", 8.0, PLAIN, Some("#7B868C"), NONE),
+ style("Arial", 10.5, ITALIC, Some("#5F6B73"), [0.0, 3.0]),
+ style("Courier New", 10.0, PLAIN, Some("#1B2631"), NONE),
+ style("Arial", 10.5, PLAIN, Some("#2E2E2E"), [0.0, 3.0]),
+ ],
+ ),
+ theme(
+ "modern",
+ "Modern (original)",
[
style("Arial", 16.0, BOLD, Some("#0E6E6E"), [10.0, 2.0]),
style("Arial", 13.0, BOLD, Some("#1B2631"), [8.0, 2.0]),
@@ -544,9 +584,32 @@ mod tests {
assert_eq!(json["accent"], true);
}
+ /// Modern's title is OneNote's in Arial. The Modern first shipped, its title bold, is
+ /// no longer offered, but pages given it keep it.
+ #[test]
+ fn modern_keeps_onenote_s_page_title_and_the_first_modern_stays() {
+ let [onenote, modern] = ["onenote", "modern-2"]
+ .map(|id| built_in().into_iter().find(|theme| theme.id == id).unwrap());
+ let title = |theme: &Theme| definition("PageTitle", &theme.styles["PageTitle"], None);
+ let mut expected = title(&onenote);
+ expected.format.font = Some("Arial".into());
+ assert_eq!(title(&modern), expected);
+ assert!(built_in().iter().all(|theme| theme.id != "modern"));
+ let themes = Themes {
+ assignments: vec![Assignment {
+ scope: Scope::Notebook,
+ theme: Some("modern".into()),
+ assigned: 1,
+ }],
+ ..Themes::default()
+ };
+ assert!(themes.effective(None, None).unwrap().styles["PageTitle"].bold);
+ assert!(themes.all().iter().all(|theme| theme.id != "modern"));
+ }
+
#[test]
fn every_built_in_theme_names_every_gallery_style() {
- for theme in built_in() {
+ for theme in shipped() {
let sheet = theme.sheet(None);
for (name, _) in STYLES {
assert!(sheet.contains_key(name), "{} {name}", theme.id);
diff --git a/crates/snowbound/src/main.rs b/crates/snowbound/src/main.rs
index 34ea45cde11a961956ad3bb00ece27b7be883b7e..1a7143234bb15f86f428031cea8c93d37a3818df 100644
--- a/crates/snowbound/src/main.rs
+++ b/crates/snowbound/src/main.rs
@@ -868,6 +868,8 @@ struct State {
password: Option,
/// Options' Passwords.
passwords: settings::Passwords,
+ /// The theme new notebooks take, by id.
+ notebook_theme: Option,
/// What the pointer is dragging: a page's tab, a section tab or a sidebar row.
drag: Option,
/// Pages whose template strip was dismissed this run.
@@ -1248,6 +1250,7 @@ impl State {
locked,
password: None,
passwords: stored.passwords,
+ notebook_theme: stored.notebook_theme.0,
live_options: stored.live,
drag: None,
templates: templates::View::Strip,
diff --git a/crates/snowbound/src/manage.rs b/crates/snowbound/src/manage.rs
index 4c5d8964e20de98fd66cc741dde01a31ef8d8374..790e4595f29cdd3cb0c5944d99f0276cc622702d 100644
--- a/crates/snowbound/src/manage.rs
+++ b/crates/snowbound/src/manage.rs
@@ -432,11 +432,22 @@ impl State {
) -> Result<(), Box> {
let page = self.dated_page(None)?;
let (cache, notify) = (self.cache.clone(), notify(self.proxy.clone()));
+ let theme = self.notebook_theme.clone();
self.load(move || {
let location = notebook::fs::absolute(&root)?
.to_string_lossy()
.into_owned();
let notebook = Notebook::create(&location, &cache, Notebook::NEW_COLOR, &page)?;
+ if theme.is_some() {
+ notebook.save_themes(notebook::sidecar::themes::Themes {
+ assignments: vec![notebook::sidecar::themes::Assignment {
+ scope: notebook::sidecar::themes::Scope::Notebook,
+ theme,
+ assigned: crate::filetime(),
+ }],
+ ..Default::default()
+ })?;
+ }
let library = Arc::new(Library::created(&location, notebook, &cache));
let path = library
.first_section()
diff --git a/crates/snowbound/src/options.rs b/crates/snowbound/src/options.rs
index 9b68e084f7ea983dd90fe5f49f3c48d821564f78..21b721ec48b64cd25f429bf474a5de621a32dce0 100644
--- a/crates/snowbound/src/options.rs
+++ b/crates/snowbound/src/options.rs
@@ -109,6 +109,14 @@ const SECTIONS: &[Section] = &[
},
],
},
+ Group {
+ heading: "Default style theme",
+ rows: &[Row {
+ label: "New notebooks:",
+ keywords: "default for new notebooks styles headings modern",
+ control: Control::Field(notebook_theme),
+ }],
+ },
Group {
heading: "Proofing",
rows: &[Row {
@@ -273,6 +281,7 @@ pub struct Options {
picture: bool,
/// Live Share's relay; empty uses Snowbound's.
relay: String,
+ notebook_theme: Option,
pub(crate) keyboard: crate::keys::Keyboard,
}
@@ -312,6 +321,10 @@ fn font_colors() -> Id {
id().child("font-colors")
}
+fn notebook_themes() -> Id {
+ id().child("notebook-themes")
+}
+
/// Whether `word`, lowercase, starts a word of `text`: "pen" finds "Pen" and "pen-like",
/// not "Open".
fn starts_word(text: &str, word: &str) -> bool {
@@ -372,6 +385,7 @@ impl State {
presence: self.live_options.presence,
picture: self.live_options.picture,
relay: self.live_options.relay.clone().unwrap_or_default(),
+ notebook_theme: self.notebook_theme.clone(),
keyboard: crate::keys::Keyboard::new(),
});
self.ui.open_popup(id());
@@ -679,6 +693,7 @@ impl State {
lock_after: options.lock_idle.then_some(options.lock_minutes),
lock_on_leave: options.lock_on_leave,
};
+ self.notebook_theme = options.notebook_theme;
self.updates.set_automatic(options.automatic_updates);
let relay = options.relay.trim();
self.live_options = crate::settings::Live {
@@ -790,6 +805,39 @@ fn appearance(state: &mut State, options: &mut Options) {
}
}
+fn notebook_theme(state: &mut State, options: &mut Options) {
+ let ui = &mut state.ui;
+ let combo = ui.id("combo");
+ let themes = notebook::sidecar::themes::built_in();
+ let choices: Vec<(Option<&str>, &str)> = std::iter::once((None, "No Theme"))
+ .chain(
+ themes
+ .iter()
+ .map(|theme| (Some(theme.id.as_str()), theme.name.as_str())),
+ )
+ .collect();
+ let chosen = options.notebook_theme.as_deref();
+ let current = choices
+ .iter()
+ .find(|(id, _)| *id == chosen)
+ .map_or("", |(_, name)| name);
+ let title = "Default for new notebooks";
+ ui::shell::combo(ui, "combo", title, current, 140.0, notebook_themes(), true);
+ let items: Vec- = choices
+ .iter()
+ .map(|(id, name)| Item {
+ text: name,
+ checked: Some(*id == chosen),
+ current: *id == chosen,
+ ..Item::default()
+ })
+ .collect();
+ let anchor = Anchor::Below(ui.rect(combo).unwrap_or_default());
+ if let Some(index) = ui::popup::menu(ui, notebook_themes(), anchor, &items, None) {
+ options.notebook_theme = choices[index].0.map(Into::into);
+ }
+}
+
fn lock_after(state: &mut State, options: &mut Options) {
let ui = &mut state.ui;
let combo = ui.id("combo");
diff --git a/crates/snowbound/src/settings.rs b/crates/snowbound/src/settings.rs
index de22af5a31355bc0ddbde5b337c4c170644d6390..b9326922040d493e3a1d6193971dd3f9c1f5f12e 100644
--- a/crates/snowbound/src/settings.rs
+++ b/crates/snowbound/src/settings.rs
@@ -52,6 +52,8 @@ pub struct Settings {
pub recent: Vec,
pub passwords: Passwords,
pub live: Live,
+ /// Options' "Default for new notebooks": the built-in theme new notebooks take.
+ pub notebook_theme: NotebookTheme,
}
/// Options' Live Share.
@@ -76,6 +78,17 @@ impl Default for Live {
}
}
+/// A built-in theme by id, Modern until chosen otherwise; none leaves notebooks OneNote's.
+#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
+#[serde(transparent)]
+pub struct NotebookTheme(pub Option);
+
+impl Default for NotebookTheme {
+ fn default() -> Self {
+ Self(Some(notebook::sidecar::themes::DEFAULT.into()))
+ }
+}
+
/// Options' Passwords, as OneNote 2010's Advanced page keeps them.
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
#[serde(default)]
@@ -217,6 +230,7 @@ impl crate::State {
keys: crate::commands::Keymap::current().saved(),
recent: self.trail.recent.clone(),
passwords: self.passwords,
+ notebook_theme: NotebookTheme(self.notebook_theme.clone()),
live: self.live_options.clone(),
};
if let Err(error) = settings.save(path) {
@@ -307,6 +321,7 @@ mod tests {
picture: false,
relay: Some("wss://relay.example.net".into()),
},
+ notebook_theme: NotebookTheme(None),
};
settings.save(&path).unwrap();
assert_eq!(Settings::load(&path), settings);
@@ -323,6 +338,9 @@ mod tests {
let toolbar = Settings::load(&path).toolbar;
assert_eq!(toolbar.highlight, None);
assert_eq!(toolbar.font_color, Toolbar::default().font_color);
+ // New notebooks take Modern until another theme, or none, is picked.
+ let theme = Settings::load(&path).notebook_theme;
+ assert_eq!(theme.0.as_deref(), Some(notebook::sidecar::themes::DEFAULT));
notebook::fs::remove_dir_all(&directory).unwrap();
}
}
diff --git a/crates/snowbound/src/themes.rs b/crates/snowbound/src/themes.rs
index ec6557246b06bd5e5b6202af376ab370570de245..5cbd818d42c4a6635b636eaea2f346777b27fa79 100644
--- a/crates/snowbound/src/themes.rs
+++ b/crates/snowbound/src/themes.rs
@@ -183,10 +183,16 @@ impl State {
section: Option,
) {
let themes = library.themes();
- let assigned = themes.assigned(&target).map(|theme| theme.id);
- let all = themes.all();
+ let assigned = themes.assigned(&target);
+ let mut all = themes.all();
+ // A retired built-in the scope wears is listed while it does.
+ if let Some(assigned) = &assigned
+ && !all.contains(assigned)
+ {
+ all.push(assigned.clone());
+ }
let selected = assigned
- .and_then(|id| all.iter().position(|theme| theme.id == id))
+ .and_then(|assigned| all.iter().position(|theme| theme.id == assigned.id))
.unwrap_or(0);
self.themes = Some(Dialog {
scope,
@@ -315,9 +321,9 @@ impl State {
..Spec::default()
},
);
+ let built = stored::is_built_in(&dialog.themes[dialog.selected].id);
let duplicate = ui::button(ui, "duplicate", "Duplicate").clicked;
- let delete =
- dialog.selected >= built_in().len() && ui::button(ui, "delete", "Delete").clicked;
+ let delete = !built && ui::button(ui, "delete", "Delete").clicked;
ui.close();
ui.close();
// The selected theme's styles.
@@ -330,7 +336,6 @@ impl State {
..Spec::default()
},
);
- let built = dialog.selected < built_in().len();
if built {
ui.leaf(
"locked",
@@ -591,7 +596,7 @@ impl State {
copy(dialog, name);
}
if let Some(change) = change {
- if dialog.selected < built_in().len() {
+ if built {
let name = format!("My {}", dialog.themes[dialog.selected].name);
copy(dialog, name);
}
@@ -606,7 +611,7 @@ impl State {
dialog.changed.push(edited.id.clone());
}
}
- if delete && dialog.selected >= built_in().len() {
+ if delete {
let mut gone = dialog.themes.remove(dialog.selected);
gone.deleted = true;
dialog.deleted.push(gone);
diff --git a/tools/test_styles.py b/tools/test_styles.py
index d86bab10e65cfd6c4b79162838bb90335d6eb144..64ea381929e7678b3ef6c2d9ffccd79d49ad3024 100644
--- a/tools/test_styles.py
+++ b/tools/test_styles.py
@@ -35,7 +35,7 @@ class StylesTest(unittest.TestCase):
'OneNote': ('Calibri', 16.0, True, '#17365D'),
'Manuscript': ('Georgia', 20.0, False, '#3B2F2A'),
'Editorial': ('Georgia', 18.0, True, '#9A3B1F'),
- 'Modern': ('Arial', 16.0, True, '#0E6E6E'),
+ 'Modern': ('Arial', 16.0, True, '#2E5CB8'),
}
seen = set()
for path in sorted((FIXTURE / 'cold/read').glob('page-*.xml')):
@@ -44,6 +44,9 @@ class StylesTest(unittest.TestCase):
seen.add(theme)
self.assertEqual(set(defined), GALLERY, theme)
self.assertEqual(defined['h1'], [heading[theme]], theme)
+ if theme == 'Modern':
+ # OneNote's own page title, in Arial.
+ self.assertEqual(defined['PageTitle'], [('Arial', 17.0, False, 'automatic')])
self.assertEqual(seen, set(heading))
def test_onenote_s_own_styles_read_under_the_same_names(self):