authorclover caruso <git@paperclover.net> 2026-10-16 02:01:55-07:00
committerclover caruso <git@paperclover.net> 2026-10-16 02:24:41-07:00
log4534c84f0003ef1bc0827ad8d1ab37a6ce30655f
tree791cd40813ab1bbc8239e1b17b2d248b2045a5ae
parentf6222caecf1c286dee2a1746307bf90d715044cf
signature Signed by SSH key SHA256:52mNGHRsVFBDED9IAX5pe+LRWUefqTbxEReunq21QvU (~clover)

refactor: hold the page view in a Pane

Splitting the window needs everything one page view owns in one place. State.pane now holds the view, which page it shows (Showing: the space, the open version, the conflict pages and versions listed beneath it, the selected conflicting change), its loads in flight, Back and Forward, the stale and focus flags, and the locked section or sectionless notebook it shows instead of a page. Session keeps only what belongs to the section, so one section can later back two panes; its methods that depended on the open page take a Showing, and loads hand over the page they read beside the session. The recent pages leave Trail for State, since they are one list kept in the settings while Back and Forward become each pane's own. Nothing visible or behavioural changes: there is still one pane. Assisted-by: claude-opus-5.5

29 files changed, 667 insertions(+), 561 deletions(-)

crates/snowbound/src/attachment.rs+13-6
...@@ -10,10 +10,14 @@ use std::{...@@ -10,10 +10,14 @@ use std::{
10impl State {10impl State {
11 /// Inserts a file at the caret or window point `at`, as a picture when requested.11 /// Inserts a file at the caret or window point `at`, as a picture when requested.
12 pub(crate) fn import_file(&mut self, path: &Path, at: Option<[f32; 2]>, picture: bool) {12 pub(crate) fn import_file(&mut self, path: &Path, at: Option<[f32; 2]>, picture: bool) {
13 if self.session.as_ref().is_some_and(crate::Session::read_only) {13 if self
14 .session
15 .as_ref()
16 .is_some_and(|session| session.read_only(&self.pane.showing))
17 {
14 return;18 return;
15 }19 }
16 let loading = self.loading;20 let loading = self.pane.loading;
17 let reply = self.reply(21 let reply = self.reply(
18 move |state, (path, read): (PathBuf, std::io::Result<Vec<u8>>)| {22 move |state, (path, read): (PathBuf, std::io::Result<Vec<u8>>)| {
19 let bytes = match read {23 let bytes = match read {
...@@ -27,7 +31,7 @@ impl State {...@@ -27,7 +31,7 @@ impl State {
27 return Ok(());31 return Ok(());
28 }32 }
29 };33 };
30 if state.loading != loading {34 if state.pane.loading != loading {
31 platform::alert(35 platform::alert(
32 "File wasn't inserted",36 "File wasn't inserted",
33 "Return to the page and insert the file again.",37 "Return to the page and insert the file again.",
...@@ -37,7 +41,7 @@ impl State {...@@ -37,7 +41,7 @@ impl State {
37 if state41 if state
38 .session42 .session
39 .as_ref()43 .as_ref()
40 .is_some_and(crate::Session::read_only)44 .is_some_and(|session| session.read_only(&state.pane.showing))
41 {45 {
42 return Ok(());46 return Ok(());
43 }47 }
...@@ -60,8 +64,11 @@ impl State {...@@ -60,8 +64,11 @@ impl State {
60 tags: Vec::new(),64 tags: Vec::new(),
61 };65 };
62 let response = match at {66 let response = match at {
63 Some(point) => state.view.drop_attachment(state.page_point(point), file)?,67 Some(point) => state
64 None => state.view.insert_attachment(file)?,68 .pane
69 .view
70 .drop_attachment(state.page_point(point), file)?,
71 None => state.pane.view.insert_attachment(file)?,
65 };72 };
66 state.respond(response);73 state.respond(response);
67 Ok(())74 Ok(())
crates/snowbound/src/commands.rs+44-42
...@@ -1070,7 +1070,7 @@ impl State {...@@ -1070,7 +1070,7 @@ impl State {
1070 }1070 }
10711071
1072 pub(crate) fn format_state(&self) -> FormatState {1072 pub(crate) fn format_state(&self) -> FormatState {
1073 self.view.editor.format_state().unwrap_or_default()1073 self.pane.view.editor.format_state().unwrap_or_default()
1074 }1074 }
10751075
1076 /// Queues `choice` to run after this frame's input, as a key or a click on it does.1076 /// Queues `choice` to run after this frame's input, as a key or a click on it does.
...@@ -1127,7 +1127,7 @@ impl State {...@@ -1127,7 +1127,7 @@ impl State {
11271127
1128 pub(crate) fn status(&self, choice: &Choice, format: &FormatState) -> Status {1128 pub(crate) fn status(&self, choice: &Choice, format: &FormatState) -> Status {
1129 let session = self.session.as_ref();1129 let session = self.session.as_ref();
1130 let welcome = session.is_none() && !self.temporary && self.sectionless.is_none();1130 let welcome = session.is_none() && !self.temporary && self.pane.sectionless.is_none();
1131 let modal = self.options.is_some()1131 let modal = self.options.is_some()
1132 || self.password.is_some()1132 || self.password.is_some()
1133 || self.themes.is_some()1133 || self.themes.is_some()
...@@ -1138,13 +1138,14 @@ impl State {...@@ -1138,13 +1138,14 @@ impl State {
1138 #[cfg(any(windows, target_os = "linux"))]1138 #[cfg(any(windows, target_os = "linux"))]
1139 let modal = modal || self.prompt.strong_count() != 0;1139 let modal = modal || self.prompt.strong_count() != 0;
1140 let page = (session.is_some() || self.temporary) && !modal;1140 let page = (session.is_some() || self.temporary) && !modal;
1141 let writable = page && !session.is_some_and(|session| session.read_only());1141 let writable =
1142 page && !session.is_some_and(|session| session.read_only(&self.pane.showing));
1142 // Picked drawings leave the text selection behind them unseen, so it takes no edits.1143 // Picked drawings leave the text selection behind them unseen, so it takes no edits.
1143 let typing = self.view.accepts_text() && self.view.ink_selection().is_empty();1144 let typing = self.pane.view.accepts_text() && self.pane.view.ink_selection().is_empty();
1144 let text = writable && typing;1145 let text = writable && typing;
1145 let tagged = writable1146 let tagged = writable
1146 && matches!(1147 && matches!(
1147 self.view.object_focus(),1148 self.pane.view.object_focus(),
1148 Some(1149 Some(
1149 canvas::interaction::ObjectFocus::Image(_)1150 canvas::interaction::ObjectFocus::Image(_)
1150 | canvas::interaction::ObjectFocus::File(_)1151 | canvas::interaction::ObjectFocus::File(_)
...@@ -1152,12 +1153,12 @@ impl State {...@@ -1152,12 +1153,12 @@ impl State {
1152 );1153 );
1153 // Palette commands act after its search field returns focus to the editor.1154 // Palette commands act after its search field returns focus to the editor.
1154 let field = self.ui.focused_field(Some(crate::palette::id())).is_some();1155 let field = self.ui.focused_field(Some(crate::palette::id())).is_some();
1155 let [anchor, focus] = self.view.editor.selection().positions;1156 let [anchor, focus] = self.pane.view.editor.selection().positions;
1156 let selected = page1157 let selected = page
1157 && !field1158 && !field
1158 && (typing && anchor != focus1159 && (typing && anchor != focus
1159 || matches!(1160 || matches!(
1160 self.view.object_focus(),1161 self.pane.view.object_focus(),
1161 Some(canvas::interaction::ObjectFocus::Image(_))1162 Some(canvas::interaction::ObjectFocus::Image(_))
1162 ));1163 ));
1163 let enabled = |enabled| Status {1164 let enabled = |enabled| Status {
...@@ -1178,7 +1179,7 @@ impl State {...@@ -1178,7 +1179,7 @@ impl State {
1178 Choice::Pen(_) => Id::Pen,1179 Choice::Pen(_) => Id::Pen,
1179 Choice::Symbol(_) => Id::Symbol,1180 Choice::Symbol(_) => Id::Symbol,
1180 };1181 };
1181 let tool = self.view.tool();1182 let tool = self.pane.view.tool();
1182 match id {1183 match id {
1183 Id::Settings1184 Id::Settings
1184 | Id::NewNotebook1185 | Id::NewNotebook
...@@ -1267,8 +1268,8 @@ impl State {...@@ -1267,8 +1268,8 @@ impl State {
1267 checked: Some(false),1268 checked: Some(false),
1268 },1269 },
1269 |session| Status {1270 |session| Status {
1270 enabled: !session.page_versions(session.space).is_empty(),1271 enabled: !session.page_versions(self.pane.showing.space).is_empty(),
1271 checked: Some(session.shown_history == Some(session.space)),1272 checked: Some(self.pane.showing.shown_history == Some(self.pane.showing.space)),
1272 },1273 },
1273 ),1274 ),
1274 Id::FullPageView => Status {1275 Id::FullPageView => Status {
...@@ -1312,7 +1313,7 @@ impl State {...@@ -1312,7 +1313,7 @@ impl State {
1312 },1313 },
1313 Id::SnapToGrid => Status {1314 Id::SnapToGrid => Status {
1314 enabled: !modal,1315 enabled: !modal,
1315 checked: Some(self.view.snap_to_grid),1316 checked: Some(self.pane.view.snap_to_grid),
1316 },1317 },
1317 Id::HideSpelling => Status {1318 Id::HideSpelling => Status {
1318 enabled: !modal && self.spelling.is_some(),1319 enabled: !modal && self.spelling.is_some(),
...@@ -1320,9 +1321,9 @@ impl State {...@@ -1320,9 +1321,9 @@ impl State {
1320 },1321 },
1321 Id::MarkdownShortcuts => Status {1322 Id::MarkdownShortcuts => Status {
1322 enabled: !modal,1323 enabled: !modal,
1323 checked: Some(self.view.editor.markdown.is_some()),1324 checked: Some(self.pane.view.editor.markdown.is_some()),
1324 },1325 },
1325 Id::Spelling => enabled(text && self.view.spelling.is_some()),1326 Id::Spelling => enabled(text && self.pane.view.spelling.is_some()),
1326 Id::FormatPainter => Status {1327 Id::FormatPainter => Status {
1327 enabled: text,1328 enabled: text,
1328 checked: Some(self.painter.is_some()),1329 checked: Some(self.painter.is_some()),
...@@ -1343,7 +1344,7 @@ impl State {...@@ -1343,7 +1344,7 @@ impl State {
1343 Id::Transport(transport) => self.transport_status(transport),1344 Id::Transport(transport) => self.transport_status(transport),
1344 Id::InsertSpace => Status {1345 Id::InsertSpace => Status {
1345 enabled: writable,1346 enabled: writable,
1346 checked: Some(self.view.inserting_space()),1347 checked: Some(self.pane.view.inserting_space()),
1347 },1348 },
1348 Id::SelectType => Status {1349 Id::SelectType => Status {
1349 enabled: page,1350 enabled: page,
...@@ -1427,7 +1428,7 @@ impl State {...@@ -1427,7 +1428,7 @@ impl State {
1427 if matches!(formatting, Formatting::Font(_)) || self.toolbar != kept {1428 if matches!(formatting, Formatting::Font(_)) || self.toolbar != kept {
1428 self.save_settings();1429 self.save_settings();
1429 }1430 }
1430 let response = self.view.format(formatting)?;1431 let response = self.pane.view.format(formatting)?;
1431 self.respond(response);1432 self.respond(response);
1432 Ok(())1433 Ok(())
1433 }1434 }
...@@ -1442,7 +1443,7 @@ impl State {...@@ -1442,7 +1443,7 @@ impl State {
1442 self.session1443 self.session
1443 .as_ref()1444 .as_ref()
1444 .map(|session| &session.library)1445 .map(|session| &session.library)
1445 .or(self.sectionless.as_ref())1446 .or(self.pane.sectionless.as_ref())
1446 }1447 }
14471448
1448 /// Runs `choice` where it applies now.1449 /// Runs `choice` where it applies now.
...@@ -1463,22 +1464,22 @@ impl State {...@@ -1463,22 +1464,22 @@ impl State {
1463 Choice::Color(color) => return format(self, Formatting::Color(color)),1464 Choice::Color(color) => return format(self, Formatting::Color(color)),
1464 Choice::List(style) => return format(self, Formatting::List(style)),1465 Choice::List(style) => return format(self, Formatting::List(style)),
1465 Choice::Table([rows, columns]) => {1466 Choice::Table([rows, columns]) => {
1466 let response = self.view.insert_table(rows, columns)?;1467 let response = self.pane.view.insert_table(rows, columns)?;
1467 self.respond(response);1468 self.respond(response);
1468 return Ok(());1469 return Ok(());
1469 }1470 }
1470 Choice::PageColor(color) => {1471 Choice::PageColor(color) => {
1471 return self.paper_page(color, self.view.editor.rule_lines(), None);1472 return self.paper_page(color, self.pane.view.editor.rule_lines(), None);
1472 }1473 }
1473 Choice::Art(name) => {1474 Choice::Art(name) => {
1474 return self.with_art(name.and_then(canvas::template::find), |state, art| {1475 return self.with_art(name.and_then(canvas::template::find), |state, art| {
1475 let editor = &state.view.editor;1476 let editor = &state.pane.view.editor;
1476 state.paper_page(editor.page_color(), editor.rule_lines(), Some(art))1477 state.paper_page(editor.page_color(), editor.rule_lines(), Some(art))
1477 });1478 });
1478 }1479 }
1479 Choice::RuleLines(lines) => {1480 Choice::RuleLines(lines) => {
1480 let lines = lines.map(|index| canvas::template::RULE_LINES[index].1);1481 let lines = lines.map(|index| canvas::template::RULE_LINES[index].1);
1481 return self.paper_page(self.view.editor.page_color(), lines, None);1482 return self.paper_page(self.pane.view.editor.page_color(), lines, None);
1482 }1483 }
1483 Choice::Pen(place) => {1484 Choice::Pen(place) => {
1484 self.toolbar.pen = place;1485 self.toolbar.pen = place;
...@@ -1495,7 +1496,7 @@ impl State {...@@ -1495,7 +1496,7 @@ impl State {
1495 recent.insert(0, symbol);1496 recent.insert(0, symbol);
1496 recent.truncate(crate::symbol::GALLERY);1497 recent.truncate(crate::symbol::GALLERY);
1497 self.save_settings();1498 self.save_settings();
1498 let response = self.view.insert_symbol(symbol)?;1499 let response = self.pane.view.insert_symbol(symbol)?;
1499 self.respond(response);1500 self.respond(response);
1500 return Ok(());1501 return Ok(());
1501 }1502 }
...@@ -1544,18 +1545,19 @@ impl State {...@@ -1544,18 +1545,19 @@ impl State {
1544 title: String::new(),1545 title: String::new(),
1545 },1546 },
1546 Id::NewSubpage => Work::NewPage {1547 Id::NewSubpage => Work::NewPage {
1547 under: self.session.as_ref().map(|session| session.space),1548 under: self.session.as_ref().map(|_| self.pane.showing.space),
1548 title: String::new(),1549 title: String::new(),
1549 },1550 },
1550 Id::PageVersions => {1551 Id::PageVersions => {
1551 let session = self.session.as_ref().ok_or("No notebook is open")?;1552 self.session.as_ref().ok_or("No notebook is open")?;
1552 Work::History {1553 Work::History {
1553 page: session.space,1554 page: self.pane.showing.space,
1554 show: session.shown_history != Some(session.space),1555 show: self.pane.showing.shown_history != Some(self.pane.showing.space),
1555 }1556 }
1556 }1557 }
1557 Id::CopyPageLink => {1558 Id::CopyPageLink => {
1558 let space = self.session.as_ref().ok_or("No notebook is open")?.space;1559 self.session.as_ref().ok_or("No notebook is open")?;
1560 let space = self.pane.showing.space;
1559 let link = self.page_link(space, None)?;1561 let link = self.page_link(space, None)?;
1560 self.clipboard.set_text(link)?;1562 self.clipboard.set_text(link)?;
1561 return Ok(());1563 return Ok(());
...@@ -1604,7 +1606,7 @@ impl State {...@@ -1604,7 +1606,7 @@ impl State {
1604 }1606 }
1605 Id::Undo | Id::Redo => return self.step(id == Id::Redo),1607 Id::Undo | Id::Redo => return self.step(id == Id::Redo),
1606 Id::Cut | Id::Copy => {1608 Id::Cut | Id::Copy => {
1607 let response = self.view.copy(id == Id::Cut)?;1609 let response = self.pane.view.copy(id == Id::Cut)?;
1608 self.respond(response);1610 self.respond(response);
1609 return Ok(());1611 return Ok(());
1610 }1612 }
...@@ -1624,7 +1626,7 @@ impl State {...@@ -1624,7 +1626,7 @@ impl State {
1624 Id::FormatPainter => {1626 Id::FormatPainter => {
1625 self.painter = match self.painter {1627 self.painter = match self.painter {
1626 Some(_) => None,1628 Some(_) => None,
1627 None => Some(self.view.editor.painted_format()?),1629 None => Some(self.pane.view.editor.painted_format()?),
1628 };1630 };
1629 return Ok(());1631 return Ok(());
1630 }1632 }
...@@ -1632,7 +1634,7 @@ impl State {...@@ -1632,7 +1634,7 @@ impl State {
1632 match field {1634 match field {
1633 Some(field) => self.ui.focus_all(field),1635 Some(field) => self.ui.focus_all(field),
1634 None => {1636 None => {
1635 let response = self.view.widen_selection()?;1637 let response = self.pane.view.widen_selection()?;
1636 self.respond(response);1638 self.respond(response);
1637 }1639 }
1638 }1640 }
...@@ -1668,11 +1670,11 @@ impl State {...@@ -1668,11 +1670,11 @@ impl State {
1668 }1670 }
1669 Id::ZoomIn | Id::ZoomOut | Id::ActualSize => {1671 Id::ZoomIn | Id::ZoomOut | Id::ActualSize => {
1670 let zoom = match id {1672 let zoom = match id {
1671 Id::ZoomIn => self.view.zoom() * 1.1,1673 Id::ZoomIn => self.pane.view.zoom() * 1.1,
1672 Id::ZoomOut => self.view.zoom() / 1.1,1674 Id::ZoomOut => self.pane.view.zoom() / 1.1,
1673 _ => 1.0,1675 _ => 1.0,
1674 };1676 };
1675 let response = self.view.set_zoom(zoom)?;1677 let response = self.pane.view.set_zoom(zoom)?;
1676 self.respond(response);1678 self.respond(response);
1677 return Ok(());1679 return Ok(());
1678 }1680 }
...@@ -1725,12 +1727,12 @@ impl State {...@@ -1725,12 +1727,12 @@ impl State {
1725 return Ok(());1727 return Ok(());
1726 }1728 }
1727 Id::SnapToGrid => {1729 Id::SnapToGrid => {
1728 self.view.snap_to_grid = !self.view.snap_to_grid;1730 self.pane.view.snap_to_grid = !self.pane.view.snap_to_grid;
1729 self.save_settings();1731 self.save_settings();
1730 return Ok(());1732 return Ok(());
1731 }1733 }
1732 Id::MarkdownShortcuts => {1734 Id::MarkdownShortcuts => {
1733 self.view.editor.markdown = markdown(self.view.editor.markdown.is_none());1735 self.pane.view.editor.markdown = markdown(self.pane.view.editor.markdown.is_none());
1734 self.save_settings();1736 self.save_settings();
1735 return Ok(());1737 return Ok(());
1736 }1738 }
...@@ -1743,7 +1745,7 @@ impl State {...@@ -1743,7 +1745,7 @@ impl State {
1743 Id::RecordAudio | Id::RecordVideo => return self.record(id == Id::RecordVideo),1745 Id::RecordAudio | Id::RecordVideo => return self.record(id == Id::RecordVideo),
1744 Id::Transport(transport) => return self.run_transport(transport),1746 Id::Transport(transport) => return self.run_transport(transport),
1745 Id::InsertSpace => {1747 Id::InsertSpace => {
1746 let response = self.view.insert_space();1748 let response = self.pane.view.insert_space();
1747 self.respond(response);1749 self.respond(response);
1748 return Ok(());1750 return Ok(());
1749 }1751 }
...@@ -1756,7 +1758,7 @@ impl State {...@@ -1756,7 +1758,7 @@ impl State {
1756 Id::Shape(kind) => Tool::Shape(kind, pens[0].shape()),1758 Id::Shape(kind) => Tool::Shape(kind, pens[0].shape()),
1757 _ => Tool::Select,1759 _ => Tool::Select,
1758 };1760 };
1759 let response = self.view.set_tool(tool);1761 let response = self.pane.view.set_tool(tool);
1760 self.respond(response);1762 self.respond(response);
1761 return Ok(());1763 return Ok(());
1762 }1764 }
...@@ -1765,7 +1767,7 @@ impl State {...@@ -1765,7 +1767,7 @@ impl State {
1765 return Ok(());1767 return Ok(());
1766 }1768 }
1767 Id::Equation => {1769 Id::Equation => {
1768 let response = self.view.insert_equation()?;1770 let response = self.pane.view.insert_equation()?;
1769 self.respond(response);1771 self.respond(response);
1770 return Ok(());1772 return Ok(());
1771 }1773 }
...@@ -1779,7 +1781,7 @@ impl State {...@@ -1779,7 +1781,7 @@ impl State {
1779 Id::Time => time,1781 Id::Time => time,
1780 _ => format!("{date} {time}"),1782 _ => format!("{date} {time}"),
1781 };1783 };
1782 let response = self.view.insert_text(format!("{text} "))?;1784 let response = self.pane.view.insert_text(format!("{text} "))?;
1783 self.respond(response);1785 self.respond(response);
1784 return Ok(());1786 return Ok(());
1785 }1787 }
...@@ -1803,7 +1805,7 @@ impl State {...@@ -1803,7 +1805,7 @@ impl State {
1803 // Clear Formatting at a caret makes the paragraph Normal, as OneNote 2010's1805 // Clear Formatting at a caret makes the paragraph Normal, as OneNote 2010's
1804 // Ctrl+Shift+N does; over a selection it clears the characters' formatting.1806 // Ctrl+Shift+N does; over a selection it clears the characters' formatting.
1805 Id::ClearFormatting => {1807 Id::ClearFormatting => {
1806 let [anchor, focus] = self.view.editor.selection().positions;1808 let [anchor, focus] = self.pane.view.editor.selection().positions;
1807 if anchor == focus {1809 if anchor == focus {
1808 return format(self, Formatting::Style(self.gallery_style("p")));1810 return format(self, Formatting::Style(self.gallery_style("p")));
1809 }1811 }
...@@ -2096,9 +2098,9 @@ mod tests {...@@ -2096,9 +2098,9 @@ mod tests {
2096 for effect in [2098 for effect in [
2097 "self.respond(",2099 "self.respond(",
2098 "self.changed",2100 "self.changed",
2099 "self.view.format(",2101 "self.pane.view.format(",
2100 "self.view.set_zoom(",2102 "self.pane.view.set_zoom(",
2101 "self.view.insert",2103 "self.pane.view.insert",
2102 "self.open_link_dialog(",2104 "self.open_link_dialog(",
2103 ] {2105 ] {
2104 assert!(2106 assert!(
crates/snowbound/src/conflict_render.rs+21-15
...@@ -10,7 +10,7 @@ const SCALE: f32 = 2.0;...@@ -10,7 +10,7 @@ const SCALE: f32 = 2.0;
1010
11/// One view laid out: the bar and the page on the left, the page list on the right; `menu`11/// One view laid out: the bar and the page on the left, the page list on the right; `menu`
12/// opens the conflict page's menu.12/// opens the conflict page's menu.
13fn layout(ui: &mut Ui, session: &Session, menu: bool) -> Vec<Command> {13fn layout(ui: &mut Ui, session: &Session, showing: &Showing, menu: bool) -> Vec<Command> {
14 let theme = ui.theme.clone();14 let theme = ui.theme.clone();
15 let section = theme.section(section_color(None));15 let section = theme.section(section_color(None));
16 ui.begin(SIZE, SCALE, Instant::now());16 ui.begin(SIZE, SCALE, Instant::now());
...@@ -36,7 +36,7 @@ fn layout(ui: &mut Ui, session: &Session, menu: bool) -> Vec<Command> {...@@ -36,7 +36,7 @@ fn layout(ui: &mut Ui, session: &Session, menu: bool) -> Vec<Command> {
36 },36 },
37 );37 );
38 let mut commands: Vec<Command> = session38 let mut commands: Vec<Command> = session
39 .bar()39 .bar(showing)
40 .and_then(|bar| conflict_bar(ui, bar, &["synthetic", "Songs"], [false, true]))40 .and_then(|bar| conflict_bar(ui, bar, &["synthetic", "Songs"], [false, true]))
41 .into_iter()41 .into_iter()
42 .collect();42 .collect();
...@@ -64,6 +64,7 @@ fn layout(ui: &mut Ui, session: &Session, menu: bool) -> Vec<Command> {...@@ -64,6 +64,7 @@ fn layout(ui: &mut Ui, session: &Session, menu: bool) -> Vec<Command> {
64 &theme,64 &theme,
65 &section,65 &section,
66 session,66 session,
67 showing,
67 [&HashSet::new(); 2],68 [&HashSet::new(); 2],
68 &HashMap::new(),69 &HashMap::new(),
69 (0.0, false),70 (0.0, false),
...@@ -242,8 +243,7 @@ fn conflict_views_offer_the_versions_and_render_offscreen() {...@@ -242,8 +243,7 @@ fn conflict_views_offer_the_versions_and_render_offscreen() {
242 let library = Arc::new(crate::Library::section(&file, &directory.join("cache")));243 let library = Arc::new(crate::Library::section(&file, &directory.join("cache")));
243 let section = library.open(&library.location, || {}).unwrap();244 let section = library.open(&library.location, || {}).unwrap();
244 let path = library.location.clone();245 let path = library.location.clone();
245 let (mut session, _) = read_session(section, library, path, None).unwrap();246 let (session, page, _) = read_session(section, library, path, None).unwrap();
246 let page = session.space;
247 let version = session.versions(page)[0].space;247 let version = session.versions(page)[0].space;
248 let output = std::env::var_os("SNOWBOUND_CONFLICT_RENDER").map(PathBuf::from);248 let output = std::env::var_os("SNOWBOUND_CONFLICT_RENDER").map(PathBuf::from);
249 let mut gpu = output.as_deref().map(gpu);249 let mut gpu = output.as_deref().map(gpu);
...@@ -262,15 +262,18 @@ fn conflict_views_offer_the_versions_and_render_offscreen() {...@@ -262,15 +262,18 @@ fn conflict_views_offer_the_versions_and_render_offscreen() {
262 Theme::dark(),262 Theme::dark(),
263 ),263 ),
264 ] {264 ] {
265 session.space = space;265 let showing = Showing {
266 session.shown = shown;266 space,
267 shown,
268 ..Showing::default()
269 };
267 let mut ui = Ui::new(appearance, std::time::Duration::from_millis(500));270 let mut ui = Ui::new(appearance, std::time::Duration::from_millis(500));
268 // Later frames lay out with earlier frames' measurements, and a menu fades in.271 // Later frames lay out with earlier frames' measurements, and a menu fades in.
269 for _ in 0..8 {272 for _ in 0..8 {
270 layout(&mut ui, &session, menu);273 layout(&mut ui, &session, &showing, menu);
271 std::thread::sleep(std::time::Duration::from_millis(30));274 std::thread::sleep(std::time::Duration::from_millis(30));
272 }275 }
273 let bar = session.bar();276 let bar = session.bar(&showing);
274 match (name, bar) {277 match (name, bar) {
275 ("page" | "page-dark", Some(Bar::Page { shown: false, .. }))278 ("page" | "page-dark", Some(Bar::Page { shown: false, .. }))
276 | ("versions", Some(Bar::Page { shown: true, .. }))279 | ("versions", Some(Bar::Page { shown: true, .. }))
...@@ -326,8 +329,7 @@ fn page_versions_list_open_read_only_and_render_offscreen() {...@@ -326,8 +329,7 @@ fn page_versions_list_open_read_only_and_render_offscreen() {
326 let library = Arc::new(crate::Library::section(&file, &directory.join("cache")));329 let library = Arc::new(crate::Library::section(&file, &directory.join("cache")));
327 let section = library.open(&library.location, || {}).unwrap();330 let section = library.open(&library.location, || {}).unwrap();
328 let path = library.location.clone();331 let path = library.location.clone();
329 let (mut session, _) = read_session(section, library, path, None).unwrap();332 let (session, page, _) = read_session(section, library, path, None).unwrap();
330 let page = session.space;
331 let versions: Vec<_> = session333 let versions: Vec<_> = session
332 .page_versions(page)334 .page_versions(page)
333 .iter()335 .iter()
...@@ -374,10 +376,14 @@ fn page_versions_list_open_read_only_and_render_offscreen() {...@@ -374,10 +376,14 @@ fn page_versions_list_open_read_only_and_render_offscreen() {
374 Theme::dark(),376 Theme::dark(),
375 ),377 ),
376 ] {378 ] {
377 session.shown_history = shown;379 let showing = Showing {
378 session.version = version;380 space: page,
379 assert_eq!(session.read_only(), version.is_some(), "{name}");381 version,
380 match (session.bar(), version) {382 shown_history: shown,
383 ..Showing::default()
384 };
385 assert_eq!(session.read_only(&showing), version.is_some(), "{name}");
386 match (session.bar(&showing), version) {
381 (387 (
382 Some(Bar::History {388 Some(Bar::History {
383 version: open,389 version: open,
...@@ -393,7 +399,7 @@ fn page_versions_list_open_read_only_and_render_offscreen() {...@@ -393,7 +399,7 @@ fn page_versions_list_open_read_only_and_render_offscreen() {
393 }399 }
394 let mut ui = Ui::new(appearance, std::time::Duration::from_millis(500));400 let mut ui = Ui::new(appearance, std::time::Duration::from_millis(500));
395 for _ in 0..8 {401 for _ in 0..8 {
396 layout(&mut ui, &session, menu);402 layout(&mut ui, &session, &showing, menu);
397 std::thread::sleep(std::time::Duration::from_millis(30));403 std::thread::sleep(std::time::Duration::from_millis(30));
398 }404 }
399 for context in &versions {405 for context in &versions {
crates/snowbound/src/history.rs+16-14
...@@ -25,9 +25,9 @@ impl Session {...@@ -25,9 +25,9 @@ impl Session {
2525
26 /// Whether the open page takes no edits: a conflict page, a version, or a page of the26 /// Whether the open page takes no edits: a conflict page, a version, or a page of the
27 /// recycle bin.27 /// recycle bin.
28 pub(crate) fn read_only(&self) -> bool {28 pub(crate) fn read_only(&self, showing: &Showing) -> bool {
29 self.version.is_some()29 showing.version.is_some()
30 || self.conflict(self.space).is_some()30 || self.conflict(showing.space).is_some()
31 || crate::recycle::binned(&self.tabs[self.tab].path)31 || crate::recycle::binned(&self.tabs[self.tab].path)
32 }32 }
3333
...@@ -54,13 +54,13 @@ impl Session {...@@ -54,13 +54,13 @@ impl Session {
54 }54 }
55 }55 }
5656
57 pub(crate) fn refresh_history(&mut self) -> Result<(), Box<dyn Error>> {57 pub(crate) fn refresh_history(&mut self, showing: &mut Showing) -> Result<(), Box<dyn Error>> {
58 self.history = self.section.versions()?;58 self.history = self.section.versions()?;
59 if self59 if showing
60 .shown_history60 .shown_history
61 .is_some_and(|page| self.page_versions(page).is_empty())61 .is_some_and(|page| self.page_versions(page).is_empty())
62 {62 {
63 self.shown_history = None;63 showing.shown_history = None;
64 }64 }
65 Ok(())65 Ok(())
66 }66 }
...@@ -142,9 +142,9 @@ pub fn menu(...@@ -142,9 +142,9 @@ pub fn menu(
142impl State {142impl State {
143 /// Shows or hides a page's versions under it; hiding leaves an open version for its page.143 /// Shows or hides a page's versions under it; hiding leaves an open version for its page.
144 pub(crate) fn show_history(&mut self, page: ExGuid, show: bool) -> Result<(), Box<dyn Error>> {144 pub(crate) fn show_history(&mut self, page: ExGuid, show: bool) -> Result<(), Box<dyn Error>> {
145 let session = self.session.as_mut().ok_or("No section is open")?;145 self.session.as_ref().ok_or("No section is open")?;
146 session.shown_history = show.then_some(page);146 self.pane.showing.shown_history = show.then_some(page);
147 if !show && session.version.is_some() {147 if !show && self.pane.showing.version.is_some() {
148 self.commands.push(Command::OpenPage(page));148 self.commands.push(Command::OpenPage(page));
149 }149 }
150 Ok(())150 Ok(())
...@@ -174,7 +174,7 @@ impl State {...@@ -174,7 +174,7 @@ impl State {
174 .restore_version_async(page, version, &self.author);174 .restore_version_async(page, version, &self.author);
175 self.section_edit(operation, move |state, _| {175 self.section_edit(operation, move |state, _| {
176 if let Some(session) = &mut state.session {176 if let Some(session) = &mut state.session {
177 session.refresh_history()?;177 session.refresh_history(&mut state.pane.showing)?;
178 }178 }
179 state.edited(vec![page]);179 state.edited(vec![page]);
180 state.commands.push(Command::OpenPage(page));180 state.commands.push(Command::OpenPage(page));
...@@ -193,7 +193,7 @@ impl State {...@@ -193,7 +193,7 @@ impl State {
193 .delete_versions_async(&[(page, vec![version])]);193 .delete_versions_async(&[(page, vec![version])]);
194 self.section_edit(operation, move |state, _| {194 self.section_edit(operation, move |state, _| {
195 if let Some(session) = &mut state.session {195 if let Some(session) = &mut state.session {
196 session.refresh_history()?;196 session.refresh_history(&mut state.pane.showing)?;
197 }197 }
198 state.commands.push(Command::OpenPage(page));198 state.commands.push(Command::OpenPage(page));
199 Ok(())199 Ok(())
...@@ -274,9 +274,11 @@ impl State {...@@ -274,9 +274,11 @@ impl State {
274 };274 };
275 self.section_edit(operation, move |state, ()| {275 self.section_edit(operation, move |state, ()| {
276 if let Some(session) = &mut state.session {276 if let Some(session) = &mut state.session {
277 session.refresh_history()?;277 session.refresh_history(&mut state.pane.showing)?;
278 if session.version.is_some() {278 if state.pane.showing.version.is_some() {
279 state.commands.push(Command::OpenPage(session.space));279 state
280 .commands
281 .push(Command::OpenPage(state.pane.showing.space));
280 }282 }
281 }283 }
282 Ok(())284 Ok(())
crates/snowbound/src/link.rs+14-13
...@@ -75,7 +75,7 @@ impl State {...@@ -75,7 +75,7 @@ impl State {
75 /// Ctrl+K and the toolbar's Link: the dialog opens on what the selection links.75 /// Ctrl+K and the toolbar's Link: the dialog opens on what the selection links.
76 pub(crate) fn open_link_dialog(&mut self) {76 pub(crate) fn open_link_dialog(&mut self) {
77 // As OneNote 2010's, Link does nothing over a selection across paragraphs.77 // As OneNote 2010's, Link does nothing over a selection across paragraphs.
78 let Some((text, address)) = self.view.link_prefill() else {78 let Some((text, address)) = self.pane.view.link_prefill() else {
79 return;79 return;
80 };80 };
81 self.link = Some(LinkDialog {81 self.link = Some(LinkDialog {
...@@ -239,7 +239,7 @@ impl State {...@@ -239,7 +239,7 @@ impl State {
239 }239 }
240 let dialog = self.link.as_ref().unwrap();240 let dialog = self.link.as_ref().unwrap();
241 if ok && !dialog.address.trim().is_empty() {241 if ok && !dialog.address.trim().is_empty() {
242 let response = self.view.set_link(&dialog.text, &dialog.address)?;242 let response = self.pane.view.set_link(&dialog.text, &dialog.address)?;
243 self.respond(response);243 self.respond(response);
244 } else if !cancel {244 } else if !cancel {
245 return Ok(());245 return Ok(());
...@@ -252,7 +252,7 @@ impl State {...@@ -252,7 +252,7 @@ impl State {
252252
253 /// A secondary press on text, a picture or a file opens its context menu there.253 /// A secondary press on text, a picture or a file opens its context menu there.
254 pub(crate) fn open_text_menu(&mut self) -> Result<(), Box<dyn Error>> {254 pub(crate) fn open_text_menu(&mut self) -> Result<(), Box<dyn Error>> {
255 let Some((response, context)) = self.view.context()? else {255 let Some((response, context)) = self.pane.view.context()? else {
256 return Ok(());256 return Ok(());
257 };257 };
258 self.respond(response);258 self.respond(response);
...@@ -338,15 +338,15 @@ impl State {...@@ -338,15 +338,15 @@ impl State {
338 let correction = context.spelling.unwrap();338 let correction = context.spelling.unwrap();
339 let response = match chosen_text.as_str() {339 let response = match chosen_text.as_str() {
340 _ if chosen < correction.suggestions.len() => {340 _ if chosen < correction.suggestions.len() => {
341 self.view.correct(&correction, &chosen_text)?341 self.pane.view.correct(&correction, &chosen_text)?
342 }342 }
343 "Delete Repeated Word" => self.view.correct(&correction, "")?,343 "Delete Repeated Word" => self.pane.view.correct(&correction, "")?,
344 "Spelling" => {344 "Spelling" => {
345 self.open_spelling_pane();345 self.open_spelling_pane();
346 return Ok(());346 return Ok(());
347 }347 }
348 text => {348 text => {
349 if let Some(spelling) = &self.view.spelling {349 if let Some(spelling) = &self.pane.view.spelling {
350 if text == "Ignore" {350 if text == "Ignore" {
351 spelling.ignore(&correction.word);351 spelling.ignore(&correction.word);
352 } else {352 } else {
...@@ -407,7 +407,8 @@ impl State {...@@ -407,7 +407,8 @@ impl State {
407 separated: true,407 separated: true,
408 disabled: disabled(commands::Id::Cut)408 disabled: disabled(commands::Id::Cut)
409 || image.size.is_none_or(|size| {409 || image.size.is_none_or(|size| {
410 self.view410 self.pane
411 .view
411 .editor412 .editor
412 .image_placement(image.id)413 .image_placement(image.id)
413 .is_none_or(|(_, shown)| shown == size)414 .is_none_or(|(_, shown)| shown == size)
...@@ -554,7 +555,7 @@ impl State {...@@ -554,7 +555,7 @@ impl State {
554 return Ok(());555 return Ok(());
555 }556 }
556 "Restore to Original Size" => {557 "Restore to Original Size" => {
557 let response = self.view.restore_picture_size(image.id)?;558 let response = self.pane.view.restore_picture_size(image.id)?;
558 self.respond(response);559 self.respond(response);
559 return Ok(());560 return Ok(());
560 }561 }
...@@ -581,15 +582,15 @@ impl State {...@@ -581,15 +582,15 @@ impl State {
581 return Ok(());582 return Ok(());
582 }583 }
583 "Copy Link to Paragraph" => {584 "Copy Link to Paragraph" => {
584 if let Some(session) = &self.session {585 if self.session.is_some() {
585 let link = self.page_link(session.space, context.paragraph)?;586 let link = self.page_link(self.pane.showing.space, context.paragraph)?;
586 self.clipboard.set_text(link)?;587 self.clipboard.set_text(link)?;
587 }588 }
588 return Ok(());589 return Ok(());
589 }590 }
590 "Select Link" => self.view.unlink(true)?,591 "Select Link" => self.pane.view.unlink(true)?,
591 "Remove Link" => self.view.unlink(false)?,592 "Remove Link" => self.pane.view.unlink(false)?,
592 text => self.view.switch_equation(text == "Linear")?,593 text => self.pane.view.switch_equation(text == "Linear")?,
593 };594 };
594 self.respond(response);595 self.respond(response);
595 Ok(())596 Ok(())
crates/snowbound/src/live.rs+12-11
...@@ -590,8 +590,9 @@ impl State {...@@ -590,8 +590,9 @@ impl State {
590 let Some(session) = &self.session else {590 let Some(session) = &self.session else {
591 return live::Presence::default();591 return live::Presence::default();
592 };592 };
593 let outline = self.view.editor.active_outline();593 let outline = self.pane.view.editor.active_outline();
594 let [anchor, focus] = self594 let [anchor, focus] = self
595 .pane
595 .view596 .view
596 .editor597 .editor
597 .selection()598 .selection()
...@@ -599,7 +600,7 @@ impl State {...@@ -599,7 +600,7 @@ impl State {
599 .map(|position| spot(outline, position));600 .map(|position| spot(outline, position));
600 live::Presence {601 live::Presence {
601 section: section(session),602 section: section(session),
602 page: Some(session.space.into()),603 page: Some(self.pane.showing.space.into()),
603 caret: anchor604 caret: anchor
604 .zip(focus)605 .zip(focus)
605 .map(|(anchor, focus)| Caret { anchor, focus }),606 .map(|(anchor, focus)| Caret { anchor, focus }),
...@@ -815,26 +816,26 @@ impl State {...@@ -815,26 +816,26 @@ impl State {
815 self.commands816 self.commands
816 .push(Command::OpenSection(library, tab.path.clone()));817 .push(Command::OpenSection(library, tab.path.clone()));
817 }818 }
818 } else if presence.page != Some(session.space.into()) {819 } else if presence.page != Some(self.pane.showing.space.into()) {
819 if asked != Some(place) {820 if asked != Some(place) {
820 self.commands.push(Command::OpenPage(presence.page?.into()));821 self.commands.push(Command::OpenPage(presence.page?.into()));
821 }822 }
822 } else {823 } else {
823 self.peers.going = None;824 self.peers.going = None;
824 let caret = presence.caret?;825 let caret = presence.caret?;
825 let (outline, focus) = find(&self.view.editor, caret.focus)?;826 let (outline, focus) = find(&self.pane.view.editor, caret.focus)?;
826 let rect = outline827 let rect = outline
827 .caret_at(focus, parley::Affinity::Downstream, 0.0)828 .caret_at(focus, parley::Affinity::Downstream, 0.0)
828 .ok()?;829 .ok()?;
829 let [x, y] = outline.origin();830 let [x, y] = outline.origin();
830 let viewport = self.view.viewport;831 let viewport = self.pane.view.viewport;
831 let [width, height] = viewport.size.map(|side| side as f32);832 let [width, height] = viewport.size.map(|side| side as f32);
832 let middle = ((rect.y0 + rect.y1) as f32 / 2.0 + y) * viewport.scale;833 let middle = ((rect.y0 + rect.y1) as f32 / 2.0 + y) * viewport.scale;
833 let mut responses = vec![self.view.scroll_to(1, middle - height / 2.0).ok()?];834 let mut responses = vec![self.pane.view.scroll_to(1, middle - height / 2.0).ok()?];
834 let across = (rect.x0 as f32 + x) * viewport.scale + viewport.origin[0];835 let across = (rect.x0 as f32 + x) * viewport.scale + viewport.origin[0];
835 if !(0.0..width).contains(&across) {836 if !(0.0..width).contains(&across) {
836 let left = (rect.x0 as f32 + x) * viewport.scale - width / 2.0;837 let left = (rect.x0 as f32 + x) * viewport.scale - width / 2.0;
837 responses.push(self.view.scroll_to(0, left).ok()?);838 responses.push(self.pane.view.scroll_to(0, left).ok()?);
838 }839 }
839 for response in responses {840 for response in responses {
840 self.respond(response);841 self.respond(response);
...@@ -924,9 +925,9 @@ impl State {...@@ -924,9 +925,9 @@ impl State {
924 let Some([left, top, right, bottom]) = self.ui.laid_out(page()) else {925 let Some([left, top, right, bottom]) = self.ui.laid_out(page()) else {
925 return;926 return;
926 };927 };
927 let here = (section(session), Some(session.space.into()));928 let here = (section(session), Some(self.pane.showing.space.into()));
928 let scale = self.ui.scale();929 let scale = self.ui.scale();
929 let viewport = self.view.viewport;930 let viewport = self.pane.view.viewport;
930 // A point on the page in points from the page box's corner.931 // A point on the page in points from the page box's corner.
931 let shown = |[x, y]: [f32; 2]| {932 let shown = |[x, y]: [f32; 2]| {
932 [933 [
...@@ -943,7 +944,7 @@ impl State {...@@ -943,7 +944,7 @@ impl State {
943 else {944 else {
944 continue;945 continue;
945 };946 };
946 let Some((outline, focus)) = find(&self.view.editor, caret.focus) else {947 let Some((outline, focus)) = find(&self.pane.view.editor, caret.focus) else {
947 continue;948 continue;
948 };949 };
949 let Ok(rect) = outline.caret_at(focus, parley::Affinity::Downstream, 0.0) else {950 let Ok(rect) = outline.caret_at(focus, parley::Affinity::Downstream, 0.0) else {
...@@ -971,7 +972,7 @@ impl State {...@@ -971,7 +972,7 @@ impl State {
971 if !(0.0..right - left).contains(&x0) {972 if !(0.0..right - left).contains(&x0) {
972 continue;973 continue;
973 }974 }
974 let selection = find(&self.view.editor, caret.anchor)975 let selection = find(&self.pane.view.editor, caret.anchor)
975 .filter(|(other, _)| other.id == outline.id)976 .filter(|(other, _)| other.id == outline.id)
976 .and_then(|(_, anchor)| outline.range_rects([anchor, focus].into()).ok())977 .and_then(|(_, anchor)| outline.range_rects([anchor, focus].into()).ok())
977 .unwrap_or_default()978 .unwrap_or_default()
crates/snowbound/src/main.rs+318-252
...@@ -539,7 +539,44 @@ fn icloud_listed(proxy: EventLoopProxy<UserEvent>) -> impl Fn() + Send + Sync +...@@ -539,7 +539,44 @@ fn icloud_listed(proxy: EventLoopProxy<UserEvent>) -> impl Fn() + Send + Sync +
539 }539 }
540}540}
541541
542/// The open section and the page the editor shows.542/// A page view, and which page, locked section or empty notebook it shows.
543struct Pane {
544 view: PageView,
545 showing: Showing,
546 /// The newest read requested; older ones are dropped when they finish.
547 loading: u64,
548 opening: Option<Opening>,
549 /// The section tab, if another, that the newest read opens, and when it was asked for.
550 switching: Option<(Option<usize>, Instant)>,
551 /// Pages visited, for Back and Forward.
552 trail: navigation::Trail,
553 /// The stored page changed elsewhere while marked text was pending; it is shown once
554 /// the composition ends.
555 stale: bool,
556 /// Whether the page last heard it had keyboard focus.
557 page_focused: bool,
558 /// The notebook shown while it has no section open, as one whose last section was
559 /// deleted.
560 sectionless: Option<Arc<Library>>,
561 /// A password-protected section shown locked, in place of an open one.
562 locked: Option<protection::Locked>,
563}
564
565/// Which page of the open section a pane shows, and what the page list shows beneath it.
566#[derive(Default)]
567struct Showing {
568 space: ExGuid,
569 /// The version of page `space` open, by its context.
570 version: Option<ExGuid>,
571 /// The page whose conflict pages the list shows beneath it.
572 shown: Option<ExGuid>,
573 /// Which of the open conflict page's conflicting changes is selected, in page order.
574 change: Option<usize>,
575 /// The page whose versions the list shows beneath it (Show Page Versions).
576 shown_history: Option<ExGuid>,
577}
578
579/// An open section.
543struct Session {580struct Session {
544 section: notebook::session::Section,581 section: notebook::session::Section,
545 /// The notebook the section belongs to.582 /// The notebook the section belongs to.
...@@ -549,21 +586,12 @@ struct Session {...@@ -549,21 +586,12 @@ struct Session {
549 tab: usize,586 tab: usize,
550 /// Spaces, titles and outline levels in section order.587 /// Spaces, titles and outline levels in section order.
551 pages: Vec<(ExGuid, String, u32)>,588 pages: Vec<(ExGuid, String, u32)>,
552 space: ExGuid,
553 /// The section's sync status as last read.589 /// The section's sync status as last read.
554 sync: notebook::session::SyncStatus,590 sync: notebook::session::SyncStatus,
555 /// Each page's conflict pages, the versions a merge could not take.591 /// Each page's conflict pages, the versions a merge could not take.
556 conflicts: Vec<(ExGuid, Vec<onestore::ConflictPage>)>,592 conflicts: Vec<(ExGuid, Vec<onestore::ConflictPage>)>,
557 /// The page whose conflict pages the list shows beneath it.
558 shown: Option<ExGuid>,
559 /// Which of the open conflict page's conflicting changes is selected, in page order.
560 change: Option<usize>,
561 /// Each page's versions, newest first.593 /// Each page's versions, newest first.
562 history: Vec<(ExGuid, Vec<onestore::PageVersion>)>,594 history: Vec<(ExGuid, Vec<onestore::PageVersion>)>,
563 /// The page whose versions the list shows beneath it (Show Page Versions).
564 shown_history: Option<ExGuid>,
565 /// The version of page `space` open, by its context.
566 version: Option<ExGuid>,
567}595}
568596
569/// The information bar above a page with conflict pages, or above one of them.597/// The information bar above a page with conflict pages, or above one of them.
...@@ -602,13 +630,13 @@ impl Session {...@@ -602,13 +630,13 @@ impl Session {
602 Ok(())630 Ok(())
603 }631 }
604632
605 fn title(&self) -> &str {633 fn title(&self, space: ExGuid) -> &str {
606 match self.conflict(self.space) {634 match self.conflict(space) {
607 Some((_, version)) => &version.title,635 Some((_, version)) => &version.title,
608 None => self636 None => self
609 .pages637 .pages
610 .iter()638 .iter()
611 .find(|(space, ..)| *space == self.space)639 .find(|(listed, ..)| *listed == space)
612 .map_or("", |(_, title, _)| title),640 .map_or("", |(_, title, _)| title),
613 }641 }
614 }642 }
...@@ -630,33 +658,34 @@ impl Session {...@@ -630,33 +658,34 @@ impl Session {
630 })658 })
631 }659 }
632660
633 fn bar(&self) -> Option<Bar> {661 fn bar(&self, showing: &Showing) -> Option<Bar> {
634 if let Some(version) = self.version {662 let space = showing.space;
663 if let Some(version) = showing.version {
635 return Some(Bar::History {664 return Some(Bar::History {
636 page: self.space,665 page: space,
637 version,666 version,
638 grouped: self.grouped(),667 grouped: self.grouped(),
639 });668 });
640 }669 }
641 match self.conflict(self.space) {670 match self.conflict(space) {
642 Some((page, _)) => Some(Bar::Version {671 Some((page, _)) => Some(Bar::Version {
643 page,672 page,
644 version: self.space,673 version: space,
645 }),674 }),
646 None => (!self.versions(self.space).is_empty()).then_some(Bar::Page {675 None => (!self.versions(space).is_empty()).then_some(Bar::Page {
647 page: self.space,676 page: space,
648 shown: self.shown == Some(self.space),677 shown: showing.shown == Some(space),
649 }),678 }),
650 }679 }
651 }680 }
652681
653 fn refresh_conflicts(&mut self) -> Result<(), Box<dyn Error>> {682 fn refresh_conflicts(&mut self, showing: &mut Showing) -> Result<(), Box<dyn Error>> {
654 self.conflicts = self.section.conflicts()?;683 self.conflicts = self.section.conflicts()?;
655 if self684 if showing
656 .shown685 .shown
657 .is_some_and(|page| self.versions(page).is_empty())686 .is_some_and(|page| self.versions(page).is_empty())
658 {687 {
659 self.shown = None;688 showing.shown = None;
660 }689 }
661 Ok(())690 Ok(())
662 }691 }
...@@ -685,9 +714,9 @@ impl Session {...@@ -685,9 +714,9 @@ impl Session {
685/// section, or a notebook read again after a change, with the catalog path the open714/// section, or a notebook read again after a change, with the catalog path the open
686/// section has in it.715/// section has in it.
687enum Loaded {716enum Loaded {
688 Section(Box<Session>, Page),717 Section(Box<Session>, ExGuid, Page),
689 /// A section just created, whose name opens for renaming as OneNote's New Section does.718 /// A section just created, whose name opens for renaming as OneNote's New Section does.
690 Created(Box<Session>, Page),719 Created(Box<Session>, ExGuid, Page),
691 Page(ExGuid, Page),720 Page(ExGuid, Page),
692 /// A page's version, by the page's space and the version's context.721 /// A page's version, by the page's space and the version's context.
693 Version(ExGuid, ExGuid, Page),722 Version(ExGuid, ExGuid, Page),
...@@ -697,8 +726,8 @@ enum Loaded {...@@ -697,8 +726,8 @@ enum Loaded {
697726
698/// What an opening page replaces: the section, or the page of the open one.727/// What an opening page replaces: the section, or the page of the open one.
699enum Shown {728enum Shown {
700 Section(Box<Session>),729 Section(Box<Session>, ExGuid),
701 Created(Box<Session>),730 Created(Box<Session>, ExGuid),
702 Page(ExGuid),731 Page(ExGuid),
703 Version(ExGuid, ExGuid),732 Version(ExGuid, ExGuid),
704}733}
...@@ -822,19 +851,14 @@ struct State {...@@ -822,19 +851,14 @@ struct State {
822 loads: (mpsc::Sender<Read>, mpsc::Receiver<Read>),851 loads: (mpsc::Sender<Read>, mpsc::Receiver<Read>),
823 /// Lays out pages the loader threads read, off the frame.852 /// Lays out pages the loader threads read, off the frame.
824 layouts: Arc<Mutex<TextEngine>>,853 layouts: Arc<Mutex<TextEngine>>,
825 /// The newest read requested; older ones are dropped when they finish.
826 loading: u64,
827 opening: Option<Opening>,
828 /// The section tab, if another, that the newest read opens, and when it was asked for.
829 switching: Option<(Option<usize>, Instant)>,
830 prefetch: prefetch::Prefetch,854 prefetch: prefetch::Prefetch,
831 /// Where each page was left this run, by `Library::key` and page space, as OneNote855 /// Where each page was left this run, by `Library::key` and page space, as OneNote
832 /// returns to it until the notebook closes.856 /// returns to it until the notebook closes.
833 places: HashMap<(String, ExGuid), Place>,857 places: HashMap<(String, ExGuid), Place>,
834 /// The page each section showed last this run, by `Library::key`.858 /// The page each section showed last this run, by `Library::key`.
835 last_pages: HashMap<String, ExGuid>,859 last_pages: HashMap<String, ExGuid>,
836 /// Pages visited, for Back and Forward.860 /// Each page shown lately once, latest first, kept between launches.
837 trail: navigation::Trail,861 recent: Vec<navigation::Place>,
838 /// What Undo and Redo take back across pages and sections.862 /// What Undo and Redo take back across pages and sections.
839 undo: undo::Timeline,863 undo: undo::Timeline,
840 swipe: navigation::Swipe,864 swipe: navigation::Swipe,
...@@ -853,9 +877,11 @@ struct State {...@@ -853,9 +877,11 @@ struct State {
853 /// Backends that failed to start this run, and why.877 /// Backends that failed to start this run, and why.
854 unavailable: Vec<(settings::Backend, String)>,878 unavailable: Vec<(settings::Backend, String)>,
855 ui: Ui,879 ui: Ui,
856 view: PageView,880 /// The page view the window shows.
881 pane: Pane,
857 /// The open notebooks in the sidebar's order.882 /// The open notebooks in the sidebar's order.
858 notebooks: Vec<Arc<Library>>,883 notebooks: Vec<Arc<Library>>,
884 /// The open section, the one the pane shows.
859 session: Option<Session>,885 session: Option<Session>,
860 /// A page not kept in any notebook, from a text file or `--page`.886 /// A page not kept in any notebook, from a text file or `--page`.
861 temporary: bool,887 temporary: bool,
...@@ -938,11 +964,6 @@ struct State {...@@ -938,11 +964,6 @@ struct State {
938 titlebar: [[f32; 4]; 2],964 titlebar: [[f32; 4]; 2],
939 /// A section or group being renamed in the sidebar.965 /// A section or group being renamed in the sidebar.
940 renaming: Option<rename::Renaming>,966 renaming: Option<rename::Renaming>,
941 /// The notebook shown while it has no section open, as one whose last section was
942 /// deleted.
943 sectionless: Option<Arc<Library>>,
944 /// A password-protected section shown locked, in place of an open one.
945 locked: Option<protection::Locked>,
946 /// The password dialog while it is open.967 /// The password dialog while it is open.
947 password: Option<protection::Asking>,968 password: Option<protection::Asking>,
948 /// Options' Passwords.969 /// Options' Passwords.
...@@ -977,11 +998,6 @@ struct State {...@@ -977,11 +998,6 @@ struct State {
977 changed: bool,998 changed: bool,
978 /// Only the view scrolled or zoomed during this frame, to be announced after it.999 /// Only the view scrolled or zoomed during this frame, to be announced after it.
979 moved: bool,1000 moved: bool,
980 /// The stored page changed elsewhere while marked text was pending; it is shown once
981 /// the composition ends.
982 stale: bool,
983 /// Whether the page last heard it had keyboard focus.
984 page_focused: bool,
985 /// The pointer in logical pixels, for window drags from the strip.1001 /// The pointer in logical pixels, for window drags from the strip.
986 pointer: [f32; 2],1002 pointer: [f32; 2],
987 /// When the strip was last pressed, to zoom on a double click.1003 /// When the strip was last pressed, to zoom on a double click.
...@@ -1180,6 +1196,7 @@ impl State {...@@ -1180,6 +1196,7 @@ impl State {
1180 icloud::look_up(icloud_listed(proxy.clone()));1196 icloud::look_up(icloud_listed(proxy.clone()));
1181 let mut notebooks = Vec::new();1197 let mut notebooks = Vec::new();
1182 let mut session = None;1198 let mut session = None;
1199 let mut showing = Showing::default();
1183 let mut sectionless = None;1200 let mut sectionless = None;
1184 let mut locked = None;1201 let mut locked = None;
1185 let (editor, scene) = match input {1202 let (editor, scene) = match input {
...@@ -1213,9 +1230,10 @@ impl State {...@@ -1213,9 +1230,10 @@ impl State {
1213 .0;1230 .0;
1214 notebooks.push(Arc::clone(&library));1231 notebooks.push(Arc::clone(&library));
1215 let path = library.location.clone();1232 let path = library.location.clone();
1216 let (opened, page) = read_session(section, library, path, Some(space))?;1233 let (opened, space, page) = read_session(section, library, path, Some(space))?;
1217 let (scene, editor) = PageScene::from_page(page, &mut engine)?;1234 let (scene, editor) = PageScene::from_page(page, &mut engine)?;
1218 session = Some(opened);1235 session = Some(opened);
1236 showing.space = space;
1219 (editor, Some((scene, [0.0; 2])))1237 (editor, Some((scene, [0.0; 2])))
1220 }1238 }
1221 Input::Notebooks { locations, current } => {1239 Input::Notebooks { locations, current } => {
...@@ -1249,10 +1267,11 @@ impl State {...@@ -1249,10 +1267,11 @@ impl State {
1249 match shown {1267 match shown {
1250 Some((library, path, left)) if !library.locked(&path) => {1268 Some((library, path, left)) if !library.locked(&path) => {
1251 let section = library.open_async(&path, notify(proxy.clone())).await?;1269 let section = library.open_async(&path, notify(proxy.clone())).await?;
1252 let (opened, page) =1270 let (opened, space, page) =
1253 read_session(section, Arc::clone(library), path, left)?;1271 read_session(section, Arc::clone(library), path, left)?;
1254 let (scene, editor) = PageScene::from_page(page, &mut engine)?;1272 let (scene, editor) = PageScene::from_page(page, &mut engine)?;
1255 session = Some(opened);1273 session = Some(opened);
1274 showing.space = space;
1256 (editor, Some((scene, [0.0; 2])))1275 (editor, Some((scene, [0.0; 2])))
1257 }1276 }
1258 shown => {1277 shown => {
...@@ -1348,14 +1367,25 @@ impl State {...@@ -1348,14 +1367,25 @@ impl State {
1348 drawing,1367 drawing,
1349 unavailable,1368 unavailable,
1350 ui,1369 ui,
1351 view: PageView::new(1370 pane: Pane {
1352 editor,1371 view: PageView::new(
1353 engine,1372 editor,
1354 scene,1373 engine,
1355 [size.width, size.height],1374 scene,
1356 dpr,1375 [size.width, size.height],
1357 platform::double_click_interval(),1376 dpr,
1358 ),1377 platform::double_click_interval(),
1378 ),
1379 showing,
1380 loading: 0,
1381 opening: None,
1382 switching: None,
1383 trail: navigation::Trail::default(),
1384 stale: false,
1385 page_focused: true,
1386 sectionless,
1387 locked,
1388 },
1359 notebooks,1389 notebooks,
1360 session,1390 session,
1361 temporary,1391 temporary,
...@@ -1398,8 +1428,6 @@ impl State {...@@ -1398,8 +1428,6 @@ impl State {
1398 light_pages: stored.light_pages,1428 light_pages: stored.light_pages,
1399 titlebar,1429 titlebar,
1400 renaming: None,1430 renaming: None,
1401 sectionless,
1402 locked,
1403 password: None,1431 password: None,
1404 passwords: stored.passwords,1432 passwords: stored.passwords,
1405 notebook_theme: (stored.notebook_theme.0)1433 notebook_theme: (stored.notebook_theme.0)
...@@ -1420,8 +1448,6 @@ impl State {...@@ -1420,8 +1448,6 @@ impl State {
1420 commands: Vec::new(),1448 commands: Vec::new(),
1421 changed: false,1449 changed: false,
1422 moved: false,1450 moved: false,
1423 stale: false,
1424 page_focused: true,
1425 pointer: [0.0; 2],1451 pointer: [0.0; 2],
1426 strip_press: None,1452 strip_press: None,
1427 strip_held: false,1453 strip_held: false,
...@@ -1439,18 +1465,11 @@ impl State {...@@ -1439,18 +1465,11 @@ impl State {
1439 loads: mpsc::channel(),1465 loads: mpsc::channel(),
1440 prefetch,1466 prefetch,
1441 layouts,1467 layouts,
1442 loading: 0,
1443 opening: None,
1444 switching: None,
1445 places: HashMap::new(),1468 places: HashMap::new(),
1446 last_pages: (stored.recent.iter().rev())1469 last_pages: (stored.recent.iter().rev())
1447 .map(|place| (library::key(&place.notebook, &place.section), place.page))1470 .map(|place| (library::key(&place.notebook, &place.section), place.page))
1448 .collect(),1471 .collect(),
1449 trail: {1472 recent: stored.recent,
1450 let mut trail = navigation::Trail::default();
1451 trail.recent = stored.recent;
1452 trail
1453 },
1454 undo: undo::Timeline::default(),1473 undo: undo::Timeline::default(),
1455 swipe: navigation::Swipe::default(),1474 swipe: navigation::Swipe::default(),
1456 access_adapter,1475 access_adapter,
...@@ -1464,9 +1483,9 @@ impl State {...@@ -1464,9 +1483,9 @@ impl State {
1464 servers: stored.servers,1483 servers: stored.servers,
1465 correction: None,1484 correction: None,
1466 };1485 };
1467 state.view.snap_to_grid = !stored.ignore_grid;1486 state.pane.view.snap_to_grid = !stored.ignore_grid;
1468 state.view.editor.default_font = stored.default_font;1487 state.pane.view.editor.default_font = stored.default_font;
1469 state.view.editor.markdown = commands::markdown(!stored.ignore_markdown);1488 state.pane.view.editor.markdown = commands::markdown(!stored.ignore_markdown);
1470 // A notebook opened from its server that couldn't sign in asks to, as the Finder does.1489 // A notebook opened from its server that couldn't sign in asks to, as the Finder does.
1471 let unsigned = state.notebooks.iter().find(|library| {1490 let unsigned = state.notebooks.iter().find(|library| {
1472 library.notebook.is_err() && library::server_address(&library.location).is_some()1491 library.notebook.is_err() && library::server_address(&library.location).is_some()
...@@ -1557,7 +1576,7 @@ impl State {...@@ -1557,7 +1576,7 @@ impl State {
1557 let scale = self.scale();1576 let scale = self.scale();
1558 if scale != self.ui.scale() {1577 if scale != self.ui.scale() {
1559 self.renderer.clear_glyph_cache();1578 self.renderer.clear_glyph_cache();
1560 let response = self.view.scale_factor_changed(scale)?;1579 let response = self.pane.view.scale_factor_changed(scale)?;
1561 self.respond(response);1580 self.respond(response);
1562 }1581 }
1563 #[cfg(feature = "live")]1582 #[cfg(feature = "live")]
...@@ -1572,10 +1591,10 @@ impl State {...@@ -1572,10 +1591,10 @@ impl State {
1572 platform::resize_direction(&self.window, self.pointer)1591 platform::resize_direction(&self.window, self.pointer)
1573 .map(CursorIcon::from)1592 .map(CursorIcon::from)
1574 .or_else(|| self.ui.cursor())1593 .or_else(|| self.ui.cursor())
1575 .unwrap_or_else(|| cursor_icon(self.view.cursor())),1594 .unwrap_or_else(|| cursor_icon(self.pane.view.cursor())),
1576 );1595 );
1577 let ime = if self.ui.focused() == Some(page()) {1596 let ime = if self.ui.focused() == Some(page()) {
1578 self.view.accepts_text()1597 self.pane.view.accepts_text()
1579 } else {1598 } else {
1580 search::takes_text(self.ui.focused())1599 search::takes_text(self.ui.focused())
1581 };1600 };
...@@ -1590,8 +1609,8 @@ impl State {...@@ -1590,8 +1609,8 @@ impl State {
1590 let commands = std::mem::take(&mut self.commands);1609 let commands = std::mem::take(&mut self.commands);
1591 let follow = !commands.is_empty()1610 let follow = !commands.is_empty()
1592 || self.ui.wants_frame()1611 || self.ui.wants_frame()
1593 || self.opening.is_some()1612 || self.pane.opening.is_some()
1594 || self.switching.is_some();1613 || self.pane.switching.is_some();
1595 for command in commands {1614 for command in commands {
1596 self.apply(command)?;1615 self.apply(command)?;
1597 }1616 }
...@@ -1601,7 +1620,7 @@ impl State {...@@ -1601,7 +1620,7 @@ impl State {
1601 self.after_move()?;1620 self.after_move()?;
1602 }1621 }
1603 self.moved = false;1622 self.moved = false;
1604 if self.stale {1623 if self.pane.stale {
1605 self.refresh()?;1624 self.refresh()?;
1606 }1625 }
1607 // A covered window shows nothing, so animations wait for it to be uncovered.1626 // A covered window shows nothing, so animations wait for it to be uncovered.
...@@ -1669,8 +1688,8 @@ impl State {...@@ -1669,8 +1688,8 @@ impl State {
1669 ((rect[2] - rect[0]) * scale).round() as u32,1688 ((rect[2] - rect[0]) * scale).round() as u32,
1670 ((rect[3] - rect[1]) * scale).round() as u32,1689 ((rect[3] - rect[1]) * scale).round() as u32,
1671 ];1690 ];
1672 if size != self.view.viewport.size {1691 if size != self.pane.view.viewport.size {
1673 let response = self.view.resized(size)?;1692 let response = self.pane.view.resized(size)?;
1674 self.respond(response);1693 self.respond(response);
1675 }1694 }
1676 }1695 }
...@@ -1743,8 +1762,8 @@ impl State {...@@ -1743,8 +1762,8 @@ impl State {
1743 platform::update_menu(|| self.statuses());1762 platform::update_menu(|| self.statuses());
1744 let welcome = self.session.is_none()1763 let welcome = self.session.is_none()
1745 && !self.temporary1764 && !self.temporary
1746 && self.sectionless.is_none()1765 && self.pane.sectionless.is_none()
1747 && self.locked.is_none();1766 && self.pane.locked.is_none();
1748 self.toolbar(&theme, !welcome)?;1767 self.toolbar(&theme, !welcome)?;
1749 if welcome {1768 if welcome {
1750 self.welcome();1769 self.welcome();
...@@ -1916,7 +1935,7 @@ impl State {...@@ -1916,7 +1935,7 @@ impl State {
1916 }1935 }
1917 (clicked, open_tab)1936 (clicked, open_tab)
1918 }1937 }
1919 None if self.locked.is_some() => self.locked_tabs(row, &section, theme.strip),1938 None if self.pane.locked.is_some() => self.locked_tabs(row, &section, theme.strip),
1920 None => {1939 None => {
1921 let tabs = [("Temporary page", section_color(None), false)];1940 let tabs = [("Temporary page", section_color(None), false)];
1922 let shown = if self.temporary { &tabs[..] } else { &[] };1941 let shown = if self.temporary { &tabs[..] } else { &[] };
...@@ -1994,13 +2013,16 @@ impl State {...@@ -1994,13 +2013,16 @@ impl State {
1994 },2013 },
1995 );2014 );
1996 if let Some(session) = &self.session2015 if let Some(session) = &self.session
1997 && let Some(bar) = session.bar()2016 && let Some(bar) = session.bar(&self.pane.showing)
1998 {2017 {
1999 let changes = self.changes().len();2018 let changes = self.changes().len();
2000 let sections: Vec<&str> = session.tabs.iter().map(|tab| tab.name.as_str()).collect();2019 let sections: Vec<&str> = session.tabs.iter().map(|tab| tab.name.as_str()).collect();
2001 let steps = [2020 let steps = [
2002 session.change.is_some_and(|at| at > 0),2021 self.pane.showing.change.is_some_and(|at| at > 0),
2003 session.change.map_or(changes > 0, |at| at + 1 < changes),2022 self.pane
2023 .showing
2024 .change
2025 .map_or(changes > 0, |at| at + 1 < changes),
2004 ];2026 ];
2005 self.commands2027 self.commands
2006 .extend(conflict_bar(&mut self.ui, bar, &sections, steps));2028 .extend(conflict_bar(&mut self.ui, bar, &sections, steps));
...@@ -2008,7 +2030,7 @@ impl State {...@@ -2008,7 +2030,7 @@ impl State {
2008 if self.in_recycle_bin() {2030 if self.in_recycle_bin() {
2009 recycle::bar(&mut self.ui);2031 recycle::bar(&mut self.ui);
2010 }2032 }
2011 if self.session.is_none() && self.locked.is_some() {2033 if self.session.is_none() && self.pane.locked.is_some() {
2012 self.locked_page();2034 self.locked_page();
2013 self.ui.close();2035 self.ui.close();
2014 self.ui.close();2036 self.ui.close();
...@@ -2021,7 +2043,12 @@ impl State {...@@ -2021,7 +2043,12 @@ impl State {
2021 self.context_menu();2043 self.context_menu();
2022 return Ok((section, open_tab, None));2044 return Ok((section, open_tab, None));
2023 }2045 }
2024 if let Some(library) = self.sectionless.clone().filter(|_| self.session.is_none()) {2046 if let Some(library) = self
2047 .pane
2048 .sectionless
2049 .clone()
2050 .filter(|_| self.session.is_none())
2051 {
2025 self.no_sections(library);2052 self.no_sections(library);
2026 self.ui.close();2053 self.ui.close();
2027 self.ui.close();2054 self.ui.close();
...@@ -2053,7 +2080,7 @@ impl State {...@@ -2053,7 +2080,7 @@ impl State {
2053 self.template_strip(&theme);2080 self.template_strip(&theme);
2054 }2081 }
2055 self.transport(&theme)?;2082 self.transport(&theme)?;
2056 let scroll = self.view.scroll();2083 let scroll = self.pane.view.scroll();
2057 for (index, axis) in [Axis::X, Axis::Y]2084 for (index, axis) in [Axis::X, Axis::Y]
2058 .into_iter()2085 .into_iter()
2059 .filter(|_| !opening)2086 .filter(|_| !opening)
...@@ -2063,13 +2090,13 @@ impl State {...@@ -2063,13 +2090,13 @@ impl State {
2063 &mut self.ui,2090 &mut self.ui,
2064 index,2091 index,
2065 axis,2092 axis,
2066 -self.view.viewport.origin[index],2093 -self.pane.view.viewport.origin[index],
2067 [scroll.min[index], scroll.max[index]],2094 [scroll.min[index], scroll.max[index]],
2068 self.view.viewport.size[index] as f32,2095 self.pane.view.viewport.size[index] as f32,
2069 scroll.max[1 - index] > scroll.min[1 - index],2096 scroll.max[1 - index] > scroll.min[1 - index],
2070 [0.18, 0.18, 0.18, 0.45],2097 [0.18, 0.18, 0.18, 0.45],
2071 ) {2098 ) {
2072 let response = self.view.scroll_to(index, offset)?;2099 let response = self.pane.view.scroll_to(index, offset)?;
2073 self.respond(response);2100 self.respond(response);
2074 }2101 }
2075 }2102 }
...@@ -2077,7 +2104,7 @@ impl State {...@@ -2077,7 +2104,7 @@ impl State {
2077 if !opening {2104 if !opening {
2078 self.peer_carets();2105 self.peer_carets();
2079 }2106 }
2080 let task = self.view.task_under_pointer().map(|[x0, y0, x1, y1]| {2107 let task = self.pane.view.task_under_pointer().map(|[x0, y0, x1, y1]| {
2081 let scale = self.ui.scale();2108 let scale = self.ui.scale();
2082 self.ui.leaf(2109 self.ui.leaf(
2083 "task",2110 "task",
...@@ -2101,9 +2128,9 @@ impl State {...@@ -2101,9 +2128,9 @@ impl State {
2101 }2128 }
2102 let signal = self.ui.signal(page());2129 let signal = self.ui.signal(page());
2103 let focused = self.ui.window_focused && self.ui.focused() == Some(page());2130 let focused = self.ui.window_focused && self.ui.focused() == Some(page());
2104 if focused != self.page_focused {2131 if focused != self.pane.page_focused {
2105 self.page_focused = focused;2132 self.pane.page_focused = focused;
2106 let response = self.view.focus_changed(focused)?;2133 let response = self.pane.view.focus_changed(focused)?;
2107 self.respond(response);2134 self.respond(response);
2108 }2135 }
2109 self.page_events(signal.events)?;2136 self.page_events(signal.events)?;
...@@ -2427,7 +2454,7 @@ impl State {...@@ -2427,7 +2454,7 @@ impl State {
2427 let fonts = &self.fonts;2454 let fonts = &self.fonts;
2428 let recent_fonts = &self.recent_fonts;2455 let recent_fonts = &self.recent_fonts;
2429 let pens = &self.toolbar;2456 let pens = &self.toolbar;
2430 let engine = &self.view.engine;2457 let engine = &self.pane.view.engine;
2431 let session = self.session.as_ref();2458 let session = self.session.as_ref();
2432 let update = self.updates.status();2459 let update = self.updates.status();
2433 let sheet = self.gallery_sheet();2460 let sheet = self.gallery_sheet();
...@@ -3126,8 +3153,8 @@ impl State {...@@ -3126,8 +3153,8 @@ impl State {
3126 color: ui.theme.paper,3153 color: ui.theme.paper,
3127 ink: ui.theme.paper_ink,3154 ink: ui.theme.paper_ink,
3128 },3155 },
3129 self.view.editor.page_color(),3156 self.pane.view.editor.page_color(),
3130 self.view.editor.rule_lines(),3157 self.pane.view.editor.rule_lines(),
3131 false,3158 false,
3132 &mut self.page_color_preview,3159 &mut self.page_color_preview,
3133 )3160 )
...@@ -3187,7 +3214,7 @@ impl State {...@@ -3187,7 +3214,7 @@ impl State {
3187 let format = self.format_state();3214 let format = self.format_state();
3188 let [out, into] =3215 let [out, into] =
3189 [Cmd::ZoomOut, Cmd::ZoomIn].map(|id| self.status(&Choice::Command(id), &format));3216 [Cmd::ZoomOut, Cmd::ZoomIn].map(|id| self.status(&Choice::Command(id), &format));
3190 let level = format!("{:.0}%", self.view.zoom() * 100.0);3217 let level = format!("{:.0}%", self.pane.view.zoom() * 100.0);
3191 let field = self.ui.id("zoom field");3218 let field = self.ui.id("zoom field");
3192 if self.ui.focused() != Some(field) {3219 if self.ui.focused() != Some(field) {
3193 self.zoom_typed = None;3220 self.zoom_typed = None;
...@@ -3303,7 +3330,7 @@ impl State {...@@ -3303,7 +3330,7 @@ impl State {
3303 self.choose(choice);3330 self.choose(choice);
3304 }3331 }
3305 if let Some(percent) = zoom {3332 if let Some(percent) = zoom {
3306 let response = self.view.set_zoom(percent / 100.0)?;3333 let response = self.pane.view.set_zoom(percent / 100.0)?;
3307 self.respond(response);3334 self.respond(response);
3308 }3335 }
3309 Ok(())3336 Ok(())
...@@ -3402,7 +3429,7 @@ impl State {...@@ -3402,7 +3429,7 @@ impl State {
3402 node.set_orientation(accesskit::Orientation::Vertical);3429 node.set_orientation(accesskit::Orientation::Vertical);
3403 }3430 }
3404 // Another section's pages are on their way.3431 // Another section's pages are on their way.
3405 if self.loading().is_some() && self.switching.is_some_and(|(tab, _)| tab.is_some()) {3432 if self.loading().is_some() && self.pane.switching.is_some_and(|(tab, _)| tab.is_some()) {
3406 self.ui.close();3433 self.ui.close();
3407 return None;3434 return None;
3408 }3435 }
...@@ -3419,6 +3446,7 @@ impl State {...@@ -3419,6 +3446,7 @@ impl State {
3419 theme,3446 theme,
3420 section,3447 section,
3421 session,3448 session,
3449 &self.pane.showing,
3422 [&found, &unread],3450 [&found, &unread],
3423 &peers,3451 &peers,
3424 (rounding, self.page_tabs_left),3452 (rounding, self.page_tabs_left),
...@@ -3461,7 +3489,10 @@ impl State {...@@ -3461,7 +3489,10 @@ impl State {
3461 if self.loading().is_some() {3489 if self.loading().is_some() {
3462 return Ok(());3490 return Ok(());
3463 }3491 }
3464 let read_only = self.session.as_ref().is_some_and(Session::read_only);3492 let read_only = self
3493 .session
3494 .as_ref()
3495 .is_some_and(|session| session.read_only(&self.pane.showing));
3465 let scale = self.ui.scale();3496 let scale = self.ui.scale();
3466 let corner = self.ui.laid_out(page()).unwrap_or_default();3497 let corner = self.ui.laid_out(page()).unwrap_or_default();
3467 let device = |point: [f32; 2]| {3498 let device = |point: [f32; 2]| {
...@@ -3472,30 +3503,32 @@ impl State {...@@ -3472,30 +3503,32 @@ impl State {
3472 };3503 };
3473 for event in events {3504 for event in events {
3474 let response = match event {3505 let response = match event {
3475 ui::Event::PointerMoved(point) => self.view.pointer_moved(device(point))?,3506 ui::Event::PointerMoved(point) => self.pane.view.pointer_moved(device(point))?,
3476 ui::Event::Pressure(pressure) => {3507 ui::Event::Pressure(pressure) => {
3477 self.view3508 self.pane
3509 .view
3478 .set_pressure(pressure.filter(|_| self.pen_pressure));3510 .set_pressure(pressure.filter(|_| self.pen_pressure));
3479 continue;3511 continue;
3480 }3512 }
3481 ui::Event::PointerLeft => self.view.pointer_left(),3513 ui::Event::PointerLeft => self.pane.view.pointer_left(),
3482 ui::Event::Button {3514 ui::Event::Button {
3483 button: MouseButton::Left,3515 button: MouseButton::Left,
3484 pressed,3516 pressed,
3485 at,3517 at,
3486 } => {3518 } => {
3487 if pressed {3519 if pressed {
3488 let response = self.view.pointer_pressed(at)?;3520 let response = self.pane.view.pointer_pressed(at)?;
3489 // Drawing or clicking anywhere but the title puts the templates away.3521 // Drawing or clicking anywhere but the title puts the templates away.
3490 if (self.view.inking() || !self.view.editor.active_outline().title)3522 if (self.pane.view.inking()
3491 && let Some(session) = &self.session3523 || !self.pane.view.editor.active_outline().title)
3524 && self.session.is_some()
3492 {3525 {
3493 self.dismissed.insert(session.space);3526 self.dismissed.insert(self.pane.showing.space);
3494 }3527 }
3495 response3528 response
3496 } else {3529 } else {
3497 let response = self.view.pointer_released()?;3530 let response = self.pane.view.pointer_released()?;
3498 let [anchor, focus] = self.view.editor.selection().positions;3531 let [anchor, focus] = self.pane.view.editor.selection().positions;
3499 if anchor != focus3532 if anchor != focus
3500 && let Some(painted) = self.painter.take()3533 && let Some(painted) = self.painter.take()
3501 {3534 {
...@@ -3521,7 +3554,7 @@ impl State {...@@ -3521,7 +3554,7 @@ impl State {
3521 ui::Event::Ime(Ime::Preedit(..) | Ime::Commit(_)) if read_only => continue,3554 ui::Event::Ime(Ime::Preedit(..) | Ime::Commit(_)) if read_only => continue,
3522 ui::Event::Key { ref key, .. }3555 ui::Event::Key { ref key, .. }
3523 if read_only3556 if read_only
3524 && !self.view.modifiers().command3557 && !self.pane.view.modifiers().command
3525 && !matches!(3558 && !matches!(
3526 key,3559 key,
3527 Key::Named(3560 Key::Named(
...@@ -3538,20 +3571,25 @@ impl State {...@@ -3538,20 +3571,25 @@ impl State {
3538 {3571 {
3539 continue;3572 continue;
3540 }3573 }
3541 ui::Event::Wheel(delta) => self.view.wheel([delta[0] * scale, delta[1] * scale])?,3574 ui::Event::Wheel(delta) => {
3575 self.pane.view.wheel([delta[0] * scale, delta[1] * scale])?
3576 }
3542 ui::Event::Key { key, text } => {3577 ui::Event::Key { key, text } => {
3543 self.view.key(&ui::edit_key(&key), text.as_deref())?3578 self.pane.view.key(&ui::edit_key(&key), text.as_deref())?
3544 }3579 }
3545 ui::Event::Ime(Ime::Preedit(text, cursor)) => self.view.compose(text, cursor)?,3580 ui::Event::Ime(Ime::Preedit(text, cursor)) => {
3546 ui::Event::Ime(Ime::Commit(text)) => self.view.commit_text(text)?,3581 self.pane.view.compose(text, cursor)?
3547 ui::Event::Ime(Ime::Disabled) => self.view.cancel_composition()?,
3548 ui::Event::Modifiers(modifiers) => {
3549 self.view.modifiers_changed(ui::edit_modifiers(modifiers))?
3550 }3582 }
3583 ui::Event::Ime(Ime::Commit(text)) => self.pane.view.commit_text(text)?,
3584 ui::Event::Ime(Ime::Disabled) => self.pane.view.cancel_composition()?,
3585 ui::Event::Modifiers(modifiers) => self
3586 .pane
3587 .view
3588 .modifiers_changed(ui::edit_modifiers(modifiers))?,
3551 };3589 };
3552 self.respond(response);3590 self.respond(response);
3553 }3591 }
3554 if self.view.editor.marked_range().is_none() {3592 if self.pane.view.editor.marked_range().is_none() {
3555 platform::clear_marked_text(&self.window);3593 platform::clear_marked_text(&self.window);
3556 }3594 }
3557 Ok(())3595 Ok(())
...@@ -3579,7 +3617,7 @@ impl State {...@@ -3579,7 +3617,7 @@ impl State {
3579 let tab = match &self.session {3617 let tab = match &self.session {
3580 // Back to the open section before another one opened.3618 // Back to the open section before another one opened.
3581 Some(session)3619 Some(session)
3582 if self.switching.is_some()3620 if self.pane.switching.is_some()
3583 && Arc::ptr_eq(&session.library, &library)3621 && Arc::ptr_eq(&session.library, &library)
3584 && session.tabs[session.tab].path == path =>3622 && session.tabs[session.tab].path == path =>
3585 {3623 {
...@@ -3591,34 +3629,37 @@ impl State {...@@ -3591,34 +3629,37 @@ impl State {
3591 }3629 }
3592 _ => None,3630 _ => None,
3593 };3631 };
3594 self.switching = Some((tab, Instant::now()));3632 self.pane.switching = Some((tab, Instant::now()));
3595 let notify = notify(self.proxy.clone());3633 let notify = notify(self.proxy.clone());
3596 let last = self.last_pages.get(&library.key(&path)).copied();3634 let last = self.last_pages.get(&library.key(&path)).copied();
3597 self.load_async(move || async move {3635 self.load_async(move || async move {
3598 let start = Instant::now();3636 let start = Instant::now();
3599 let section = library.open_async(&path, notify).await?;3637 let section = library.open_async(&path, notify).await?;
3600 lap("switch section open", start);3638 lap("switch section open", start);
3601 let (session, page) = read_session(section, library, path, last)?;3639 let (session, space, page) = read_session(section, library, path, last)?;
3602 Ok(Loaded::Section(Box::new(session), page))3640 Ok(Loaded::Section(Box::new(session), space, page))
3603 });3641 });
3604 }3642 }
3605 Command::OpenPage(space) => {3643 Command::OpenPage(space) => {
3606 let session = self.session.as_ref().ok_or("No section is open")?;3644 let session = self.session.as_ref().ok_or("No section is open")?;
3607 if self.switching.is_some() && session.space == space && session.version.is_none() {3645 if self.pane.switching.is_some()
3646 && self.pane.showing.space == space
3647 && self.pane.showing.version.is_none()
3648 {
3608 self.stop_loading();3649 self.stop_loading();
3609 return Ok(());3650 return Ok(());
3610 }3651 }
3611 self.switching = Some((None, Instant::now()));3652 self.pane.switching = Some((None, Instant::now()));
3612 let read = session.reader(space);3653 let read = session.reader(space);
3613 self.load(move || Ok(Loaded::Page(space, read()?)));3654 self.load(move || Ok(Loaded::Page(space, read()?)));
3614 }3655 }
3615 Command::Versions { page, show } => {3656 Command::Versions { page, show } => {
3616 let session = self.session.as_mut().ok_or("No section is open")?;3657 let session = self.session.as_mut().ok_or("No section is open")?;
3617 session.shown = show.then_some(page);3658 self.pane.showing.shown = show.then_some(page);
3618 let open = if show {3659 let open = if show {
3619 session.versions(page).first().map(|version| version.space)3660 session.versions(page).first().map(|version| version.space)
3620 } else {3661 } else {
3621 (session.space != page).then_some(page)3662 (self.pane.showing.space != page).then_some(page)
3622 };3663 };
3623 self.commands.extend(open.map(Command::OpenPage));3664 self.commands.extend(open.map(Command::OpenPage));
3624 }3665 }
...@@ -3627,7 +3668,7 @@ impl State {...@@ -3627,7 +3668,7 @@ impl State {
3627 let operation = session.section.delete_pages_async(&[version]);3668 let operation = session.section.delete_pages_async(&[version]);
3628 self.section_edit(operation, move |state, _| {3669 self.section_edit(operation, move |state, _| {
3629 if let Some(session) = &mut state.session {3670 if let Some(session) = &mut state.session {
3630 session.refresh_conflicts()?;3671 session.refresh_conflicts(&mut state.pane.showing)?;
3631 }3672 }
3632 state.commands.push(Command::OpenPage(page));3673 state.commands.push(Command::OpenPage(page));
3633 Ok(())3674 Ok(())
...@@ -3635,9 +3676,9 @@ impl State {...@@ -3635,9 +3676,9 @@ impl State {
3635 }3676 }
3636 Command::CopyVersion { version, tab } => {3677 Command::CopyVersion { version, tab } => {
3637 let session = self.session.as_mut().ok_or("No section is open")?;3678 let session = self.session.as_mut().ok_or("No section is open")?;
3638 let page = match session.version {3679 let page = match self.pane.showing.version {
3639 Some(open) if open == version => {3680 Some(open) if open == version => {
3640 session.section.version(session.space, open)?3681 session.section.version(self.pane.showing.space, open)?
3641 }3682 }
3642 _ => session.section.page(version)?,3683 _ => session.section.page(version)?,
3643 };3684 };
...@@ -3729,7 +3770,7 @@ impl State {...@@ -3729,7 +3770,7 @@ impl State {
3729 Command::Choose(choice) => self.run(choice)?,3770 Command::Choose(choice) => self.run(choice)?,
3730 Command::TagPicture => self.pick_tag_picture(),3771 Command::TagPicture => self.pick_tag_picture(),
3731 Command::Text(text) => {3772 Command::Text(text) => {
3732 if let Some(context) = self.view.caret_context() {3773 if let Some(context) = self.pane.view.caret_context() {
3733 self.text_command(context, text)?;3774 self.text_command(context, text)?;
3734 }3775 }
3735 }3776 }
...@@ -3747,8 +3788,8 @@ impl State {...@@ -3747,8 +3788,8 @@ impl State {
3747 &mut self,3788 &mut self,
3748 read: impl FnOnce() -> F + Send + 'static,3789 read: impl FnOnce() -> F + Send + 'static,
3749 ) {3790 ) {
3750 self.loading += 1;3791 self.pane.loading += 1;
3751 let (id, sender, redraw) = (self.loading, self.loads.0.clone(), self.redraw.clone());3792 let (id, sender, redraw) = (self.pane.loading, self.loads.0.clone(), self.redraw.clone());
3752 let layouts = Arc::clone(&self.layouts);3793 let layouts = Arc::clone(&self.layouts);
3753 let requested = Instant::now();3794 let requested = Instant::now();
3754 let scenes = Arc::clone(&self.prefetch.scenes);3795 let scenes = Arc::clone(&self.prefetch.scenes);
...@@ -3757,8 +3798,8 @@ impl State {...@@ -3757,8 +3798,8 @@ impl State {
3757 let laid = read().await.and_then(|loaded| {3798 let laid = read().await.and_then(|loaded| {
3758 lap("switch read", requested);3799 lap("switch read", requested);
3759 let (shown, page) = match loaded {3800 let (shown, page) = match loaded {
3760 Loaded::Section(session, page) => (Shown::Section(session), page),3801 Loaded::Section(session, space, page) => (Shown::Section(session, space), page),
3761 Loaded::Created(session, page) => (Shown::Created(session), page),3802 Loaded::Created(session, space, page) => (Shown::Created(session, space), page),
3762 Loaded::Page(space, page) => (Shown::Page(space), page),3803 Loaded::Page(space, page) => (Shown::Page(space), page),
3763 Loaded::Version(space, version, page) => (Shown::Version(space, version), page),3804 Loaded::Version(space, version, page) => (Shown::Version(space, version), page),
3764 Loaded::Library(library, path) => return Ok(Laid::Library(library, path)),3805 Loaded::Library(library, path) => return Ok(Laid::Library(library, path)),
...@@ -3766,8 +3807,8 @@ impl State {...@@ -3766,8 +3807,8 @@ impl State {
3766 let mut engine = layouts.lock().map_err(|_| "Page layout failed")?;3807 let mut engine = layouts.lock().map_err(|_| "Page layout failed")?;
3767 let start = Instant::now();3808 let start = Instant::now();
3768 let key = match &shown {3809 let key = match &shown {
3769 Shown::Section(session) | Shown::Created(session) => {3810 Shown::Section(session, space) | Shown::Created(session, space) => {
3770 Some((session.key(), session.space))3811 Some((session.key(), *space))
3771 }3812 }
3772 Shown::Page(space) => open.map(|open| (open, *space)),3813 Shown::Page(space) => open.map(|open| (open, *space)),
3773 Shown::Version(..) => None,3814 Shown::Version(..) => None,
...@@ -3846,12 +3887,12 @@ impl State {...@@ -3846,12 +3887,12 @@ impl State {
3846 for (id, loaded) in reads {3887 for (id, loaded) in reads {
3847 match loaded {3888 match loaded {
3848 // A later load supersedes what this one shows, but not its failure.3889 // A later load supersedes what this one shows, but not its failure.
3849 Ok(_) if id != self.loading => {}3890 Ok(_) if id != self.pane.loading => {}
3850 Ok(Laid::Page(opening)) => self.opening = Some(*opening),3891 Ok(Laid::Page(opening)) => self.pane.opening = Some(*opening),
3851 Ok(Laid::Library(library, path)) => self.adopt(library, path.as_deref())?,3892 Ok(Laid::Library(library, path)) => self.adopt(library, path.as_deref())?,
3852 Err(error) => {3893 Err(error) => {
3853 if id == self.loading {3894 if id == self.pane.loading {
3854 self.switching = None;3895 self.pane.switching = None;
3855 }3896 }
3856 platform::alert("Couldn't open", &error);3897 platform::alert("Couldn't open", &error);
3857 }3898 }
...@@ -3861,17 +3902,18 @@ impl State {...@@ -3861,17 +3902,18 @@ impl State {
3861 color: self.ui.theme.paper,3902 color: self.ui.theme.paper,
3862 ink: self.ui.theme.paper_ink,3903 ink: self.ui.theme.paper_ink,
3863 };3904 };
3864 self.prefetch.draw(&self.view, paper, &self.redraw);3905 self.prefetch.draw(&self.pane.view, paper, &self.redraw);
3865 let Some(mut opening) = self.opening.take() else {3906 let Some(mut opening) = self.pane.opening.take() else {
3866 return Ok(());3907 return Ok(());
3867 };3908 };
3868 let paper = paper.colored(opening.editor.page_color());3909 let paper = paper.colored(opening.editor.page_color());
3869 if !self3910 if !self
3911 .pane
3870 .view3912 .view
3871 .prepare(&mut opening.scene, &opening.editor, paper, &self.redraw)3913 .prepare(&mut opening.scene, &opening.editor, paper, &self.redraw)
3872 && opening.since.elapsed() < HOLD3914 && opening.since.elapsed() < HOLD
3873 {3915 {
3874 self.opening = Some(opening);3916 self.pane.opening = Some(opening);
3875 return Ok(());3917 return Ok(());
3876 }3918 }
3877 lap("switch pictures", opening.since);3919 lap("switch pictures", opening.since);
...@@ -3880,7 +3922,7 @@ impl State {...@@ -3880,7 +3922,7 @@ impl State {
38803922
3881 fn show_page(&mut self, mut opening: Opening) -> Result<(), Box<dyn Error>> {3923 fn show_page(&mut self, mut opening: Opening) -> Result<(), Box<dyn Error>> {
3882 let requested = opening.requested;3924 let requested = opening.requested;
3883 if let Shown::Section(session) | Shown::Created(session) = &mut opening.loaded3925 if let Shown::Section(session, _) | Shown::Created(session, _) = &mut opening.loaded
3884 && let Some(library) = self3926 && let Some(library) = self
3885 .notebooks3927 .notebooks
3886 .iter()3928 .iter()
...@@ -3905,7 +3947,7 @@ impl State {...@@ -3905,7 +3947,7 @@ impl State {
3905 } else if let Some(path) = library.first_section() {3947 } else if let Some(path) = library.first_section() {
3906 return self.apply(Command::OpenSection(library, path));3948 return self.apply(Command::OpenSection(library, path));
3907 } else {3949 } else {
3908 self.switching = None;3950 self.pane.switching = None;
3909 return self.adopt(library, None);3951 return self.adopt(library, None);
3910 }3952 }
3911 }3953 }
...@@ -3916,22 +3958,24 @@ impl State {...@@ -3916,22 +3958,24 @@ impl State {
3916 let mut leaving = self3958 let mut leaving = self
3917 .session3959 .session
3918 .as_ref()3960 .as_ref()
3919 .filter(|session| !session.read_only())3961 .filter(|session| !session.read_only(&self.pane.showing))
3920 .map(|session| session.space);3962 .map(|_| self.pane.showing.space);
3921 if let Some(session) = &self.session {3963 if let Some(session) = &self.session {
3922 let key = session.key();3964 let key = session.key();
3923 self.places3965 self.places.insert(
3924 .insert((key.clone(), session.space), self.view.place());3966 (key.clone(), self.pane.showing.space),
3925 self.last_pages.insert(key.clone(), session.space);3967 self.pane.view.place(),
3926 if session.version.is_none()3968 );
3927 && let Some(scene) = self.view.scene.take()3969 self.last_pages.insert(key.clone(), self.pane.showing.space);
3970 if self.pane.showing.version.is_none()
3971 && let Some(scene) = self.pane.view.scene.take()
3928 {3972 {
3929 self.prefetch.keep(key, session.space, scene);3973 self.prefetch.keep(key, self.pane.showing.space, scene);
3930 }3974 }
3931 }3975 }
3932 let created = matches!(opening.loaded, Shown::Created(_));3976 let created = matches!(opening.loaded, Shown::Created(..));
3933 match opening.loaded {3977 match opening.loaded {
3934 Shown::Section(session) | Shown::Created(session) => {3978 Shown::Section(session, space) | Shown::Created(session, space) => {
3935 // The settings keep the notebook shown, not the section.3979 // The settings keep the notebook shown, not the section.
3936 let other_notebook = self3980 let other_notebook = self
3937 .session3981 .session
...@@ -3940,7 +3984,7 @@ impl State {...@@ -3940,7 +3984,7 @@ impl State {
3940 self.list(&session.library);3984 self.list(&session.library);
3941 // The section left stays open a while for coming back to; the notebook's3985 // The section left stays open a while for coming back to; the notebook's
3942 // background sync takes it over once it is closed.3986 // background sync takes it over once it is closed.
3943 self.locked = None;3987 self.pane.locked = None;
3944 if let Some(previous) = self.session.replace(*session) {3988 if let Some(previous) = self.session.replace(*session) {
3945 let path = &previous.tabs[previous.tab].path;3989 let path = &previous.tabs[previous.tab].path;
3946 let locked = self.leave(&previous.library, path);3990 let locked = self.leave(&previous.library, path);
...@@ -3956,7 +4000,11 @@ impl State {...@@ -3956,7 +4000,11 @@ impl State {
3956 });4000 });
3957 }4001 }
3958 }4002 }
3959 self.sectionless = None;4003 self.pane.showing = Showing {
4004 space,
4005 ..Showing::default()
4006 };
4007 self.pane.sectionless = None;
3960 if other_notebook {4008 if other_notebook {
3961 self.save_settings();4009 self.save_settings();
3962 }4010 }
...@@ -3964,12 +4012,12 @@ impl State {...@@ -3964,12 +4012,12 @@ impl State {
3964 Shown::Page(space) | Shown::Version(space, _) => {4012 Shown::Page(space) | Shown::Version(space, _) => {
3965 let session = self.session.as_mut().ok_or("No section is open")?;4013 let session = self.session.as_mut().ok_or("No section is open")?;
3966 let pages = session.section.pages()?;4014 let pages = session.section.pages()?;
3967 session.space = space;4015 self.pane.showing.space = space;
3968 session.version = match opening.loaded {4016 self.pane.showing.version = match opening.loaded {
3969 Shown::Version(_, version) => Some(version),4017 Shown::Version(_, version) => Some(version),
3970 _ => None,4018 _ => None,
3971 };4019 };
3972 session.change = None;4020 self.pane.showing.change = None;
3973 // Pages created, moved or deleted since the list was read.4021 // Pages created, moved or deleted since the list was read.
3974 session.pages = pages;4022 session.pages = pages;
3975 leaving = leaving.filter(|left| session.pages.iter().any(|(s, ..)| s == left));4023 leaving = leaving.filter(|left| session.pages.iter().any(|(s, ..)| s == left));
...@@ -3977,9 +4025,12 @@ impl State {...@@ -3977,9 +4025,12 @@ impl State {
3977 }4025 }
3978 let place = self.session.as_ref().and_then(|session| {4026 let place = self.session.as_ref().and_then(|session| {
3979 let key = session.key();4027 let key = session.key();
3980 self.places.get(&(key, session.space)).copied()4028 self.places.get(&(key, self.pane.showing.space)).copied()
3981 });4029 });
3982 let left = self.view.open(opening.editor, Some(opening.scene), place);4030 let left = self
4031 .pane
4032 .view
4033 .open(opening.editor, Some(opening.scene), place);
3983 // Each page takes up the history it was left with.4034 // Each page takes up the history it was left with.
3984 if let Some(leaving) = leaving {4035 if let Some(leaving) = leaving {
3985 self.undo.park(leaving, left);4036 self.undo.park(leaving, left);
...@@ -3987,19 +4038,19 @@ impl State {...@@ -3987,19 +4038,19 @@ impl State {
3987 let shown = self4038 let shown = self
3988 .session4039 .session
3989 .as_ref()4040 .as_ref()
3990 .filter(|session| !session.read_only())4041 .filter(|session| !session.read_only(&self.pane.showing))
3991 .map(|session| session.space);4042 .map(|_| self.pane.showing.space);
3992 if let Some(parked) = shown.and_then(|space| self.undo.resume(space)) {4043 if let Some(parked) = shown.and_then(|space| self.undo.resume(space)) {
3993 self.view.resume(parked)?;4044 self.pane.view.resume(parked)?;
3994 }4045 }
3995 lap("switch shown", requested);4046 lap("switch shown", requested);
3996 self.switching = None;4047 self.pane.switching = None;
3997 self.visited();4048 self.visited();
3998 if let Some((space, action)) = self.after_open.take()4049 if let Some((space, action)) = self.after_open.take()
3999 && let Some(session) = self4050 && let Some(session) = self
4000 .session4051 .session
4001 .as_ref()4052 .as_ref()
4002 .filter(|session| session.space == space)4053 .filter(|_| self.pane.showing.space == space)
4003 {4054 {
4004 let target = menus::Target::Page {4055 let target = menus::Target::Page {
4005 library: Arc::clone(&session.library),4056 library: Arc::clone(&session.library),
...@@ -4029,7 +4080,7 @@ impl State {...@@ -4029,7 +4080,7 @@ impl State {
4029 self.list(&library);4080 self.list(&library);
4030 let shown = |shown: &Library| shown.location == library.location;4081 let shown = |shown: &Library| shown.location == library.location;
4031 let Some(path) = path else {4082 let Some(path) = path else {
4032 if self.sectionless.as_deref().is_some_and(shown)4083 if self.pane.sectionless.as_deref().is_some_and(shown)
4033 || self4084 || self
4034 .session4085 .session
4035 .as_ref()4086 .as_ref()
...@@ -4043,7 +4094,7 @@ impl State {...@@ -4043,7 +4094,7 @@ impl State {
4043 }4094 }
4044 });4095 });
4045 }4096 }
4046 self.sectionless = Some(Arc::clone(&library));4097 self.pane.sectionless = Some(Arc::clone(&library));
4047 self.title();4098 self.title();
4048 }4099 }
4049 self.show_arrived(&library);4100 self.show_arrived(&library);
...@@ -4104,7 +4155,7 @@ impl State {...@@ -4104,7 +4155,7 @@ impl State {
4104 fn after_move(&mut self) -> Result<(), Box<dyn Error>> {4155 fn after_move(&mut self) -> Result<(), Box<dyn Error>> {
4105 let scale = self.ui.scale();4156 let scale = self.ui.scale();
4106 let corner = self.ui.laid_out(page()).unwrap_or_default();4157 let corner = self.ui.laid_out(page()).unwrap_or_default();
4107 let [x0, y0, x1, y1] = self.view.caret_area()?;4158 let [x0, y0, x1, y1] = self.pane.view.caret_area()?;
4108 self.window.set_ime_cursor_area(4159 self.window.set_ime_cursor_area(
4109 LogicalPosition::new(x0 / scale + corner[0], y0 / scale + corner[1]),4160 LogicalPosition::new(x0 / scale + corner[0], y0 / scale + corner[1]),
4110 LogicalSize::new((x1 - x0) / scale, (y1 - y0) / scale),4161 LogicalSize::new((x1 - x0) / scale, (y1 - y0) / scale),
...@@ -4132,11 +4183,11 @@ impl State {...@@ -4132,11 +4183,11 @@ impl State {
4132 } else {4183 } else {
4133 ""4184 ""
4134 };4185 };
4135 format!("{}{bin} · {file}", session.title())4186 format!("{}{bin} · {file}", session.title(self.pane.showing.space))
4136 }4187 }
4137 None if !self.temporary => "Snowbound".to_owned(),4188 None if !self.temporary => "Snowbound".to_owned(),
4138 None => {4189 None => {
4139 let editor = &self.view.editor;4190 let editor = &self.pane.view.editor;
4140 if !editor.active_outline().title {4191 if !editor.active_outline().title {
4141 return;4192 return;
4142 }4193 }
...@@ -4169,27 +4220,27 @@ impl State {...@@ -4169,27 +4220,27 @@ impl State {
4169 }4220 }
41704221
4171 fn edit_date(&mut self, field: DateField) {4222 fn edit_date(&mut self, field: DateField) {
4172 let Some(date) = self.view.editor.date() else {4223 let Some(date) = self.pane.view.editor.date() else {
4173 return;4224 return;
4174 };4225 };
4175 let timestamp = date.timestamp();4226 let timestamp = date.timestamp();
4176 self.view.editor.finish_composition();4227 self.pane.view.editor.finish_composition();
4177 platform::clear_marked_text(&self.window);4228 platform::clear_marked_text(&self.window);
4178 let title = match field {4229 let title = match field {
4179 DateField::Date => "Change Page Date",4230 DateField::Date => "Change Page Date",
4180 DateField::Time => "Change Page Time",4231 DateField::Time => "Change Page Time",
4181 };4232 };
4182 let page = self.session.as_ref().map(|session| session.space);4233 let page = self.session.as_ref().map(|_| self.pane.showing.space);
4183 let reply = self.reply(4234 let reply = self.reply(
4184 move |state, chosen: Result<(u64, [String; 2]), &'static str>| {4235 move |state, chosen: Result<(u64, [String; 2]), &'static str>| {
4185 let (changed, text) = chosen?;4236 let (changed, text) = chosen?;
4186 // The answer belongs to the page that asked, unless another has opened since.4237 // The answer belongs to the page that asked, unless another has opened since.
4187 if state.session.as_ref().map(|session| session.space) != page4238 if state.session.as_ref().map(|_| state.pane.showing.space) != page
4188 || state.view.editor.date().map(|date| date.timestamp()) != Some(timestamp)4239 || state.pane.view.editor.date().map(|date| date.timestamp()) != Some(timestamp)
4189 {4240 {
4190 return Ok(());4241 return Ok(());
4191 }4242 }
4192 let response = state.view.change_date(changed, text)?;4243 let response = state.pane.view.change_date(changed, text)?;
4193 state.respond(response);4244 state.respond(response);
4194 Ok(())4245 Ok(())
4195 },4246 },
...@@ -4211,13 +4262,13 @@ impl State {...@@ -4211,13 +4262,13 @@ impl State {
4211 let Some((_, version)) = self4262 let Some((_, version)) = self
4212 .session4263 .session
4213 .as_ref()4264 .as_ref()
4214 .and_then(|session| session.conflict(session.space))4265 .and_then(|session| session.conflict(self.pane.showing.space))
4215 else {4266 else {
4216 return Vec::new();4267 return Vec::new();
4217 };4268 };
4218 let marked = |id: &ExGuid| version.objects.contains(id);4269 let marked = |id: &ExGuid| version.objects.contains(id);
4219 let mut changes = Vec::new();4270 let mut changes = Vec::new();
4220 for outline in self.view.editor.outlines() {4271 for outline in self.pane.view.editor.outlines() {
4221 for (at, node) in outline.document().text_nodes().enumerate() {4272 for (at, node) in outline.document().text_nodes().enumerate() {
4222 let Some(text) = node.text() else {4273 let Some(text) = node.text() else {
4223 continue;4274 continue;
...@@ -4234,10 +4285,10 @@ impl State {...@@ -4234,10 +4285,10 @@ impl State {
4234 /// Selects the next or previous conflicting change, as OneNote's conflict menu does.4285 /// Selects the next or previous conflicting change, as OneNote's conflict menu does.
4235 fn select_change(&mut self, forward: bool) -> Result<(), Box<dyn Error>> {4286 fn select_change(&mut self, forward: bool) -> Result<(), Box<dyn Error>> {
4236 let changes = self.changes();4287 let changes = self.changes();
4237 let Some(session) = &mut self.session else {4288 if self.session.is_none() {
4238 return Ok(());4289 return Ok(());
4239 };4290 }
4240 let next = match (session.change, forward) {4291 let next = match (self.pane.showing.change, forward) {
4241 (None, true) => 0,4292 (None, true) => 0,
4242 (Some(at), true) => at + 1,4293 (Some(at), true) => at + 1,
4243 (Some(at), false) if at > 0 => at - 1,4294 (Some(at), false) if at > 0 => at - 1,
...@@ -4246,12 +4297,12 @@ impl State {...@@ -4246,12 +4297,12 @@ impl State {
4246 let Some(&(outline, paragraph, length)) = changes.get(next) else {4297 let Some(&(outline, paragraph, length)) = changes.get(next) else {
4247 return Ok(());4298 return Ok(());
4248 };4299 };
4249 session.change = Some(next);4300 self.pane.showing.change = Some(next);
4250 let at = |offset| canvas::document::TextPosition { paragraph, offset };4301 let at = |offset| canvas::document::TextPosition { paragraph, offset };
4251 self.view.editor.focus_outline(outline)?;4302 self.pane.view.editor.focus_outline(outline)?;
4252 self.view.editor.select([at(0), at(length)].into())?;4303 self.pane.view.editor.select([at(0), at(length)].into())?;
4253 self.ui.set_focus(Some(page()));4304 self.ui.set_focus(Some(page()));
4254 let response = self.view.focus_text()?;4305 let response = self.pane.view.focus_text()?;
4255 self.respond(response);4306 self.respond(response);
4256 self.window.request_redraw();4307 self.window.request_redraw();
4257 Ok(())4308 Ok(())
...@@ -4281,7 +4332,7 @@ impl State {...@@ -4281,7 +4332,7 @@ impl State {
4281 /// Hands the section the ops the editor recorded since the last call, as one edit; the4332 /// Hands the section the ops the editor recorded since the last call, as one edit; the
4282 /// section thread stores it.4333 /// section thread stores it.
4283 fn persist(&mut self) -> Result<(), Box<dyn Error>> {4334 fn persist(&mut self) -> Result<(), Box<dyn Error>> {
4284 let ops = match self.view.editor.take_ops() {4335 let ops = match self.pane.view.editor.take_ops() {
4285 Ok(ops) => ops,4336 Ok(ops) => ops,
4286 Err(error) => return self.refused(&error.to_string()),4337 Err(error) => return self.refused(&error.to_string()),
4287 };4338 };
...@@ -4291,12 +4342,12 @@ impl State {...@@ -4291,12 +4342,12 @@ impl State {
4291 if ops.is_empty() {4342 if ops.is_empty() {
4292 return Ok(());4343 return Ok(());
4293 }4344 }
4294 if session.read_only() {4345 if session.read_only(&self.pane.showing) {
4295 // A conflict page or a version is read-only: it returns to what is stored.4346 // A conflict page or a version is read-only: it returns to what is stored.
4296 self.stale = true;4347 self.pane.stale = true;
4297 return Ok(());4348 return Ok(());
4298 }4349 }
4299 let space = session.space;4350 let space = self.pane.showing.space;
4300 session.section.apply(4351 session.section.apply(
4301 &self.author,4352 &self.author,
4302 onestore::op::Edit {4353 onestore::op::Edit {
...@@ -4308,18 +4359,20 @@ impl State {...@@ -4308,18 +4359,20 @@ impl State {
4308 },4359 },
4309 )?;4360 )?;
4310 // The page list shows the title as it is typed, even when the caret left it since.4361 // The page list shows the title as it is typed, even when the caret left it since.
4311 let editor = &self.view.editor;4362 let editor = &self.pane.view.editor;
4312 let title = (editor.outlines().iter())4363 let title = (editor.outlines().iter())
4313 .find(|outline| outline.title)4364 .find(|outline| outline.title)
4314 .and_then(|outline| outline.document().paragraphs().last())4365 .and_then(|outline| outline.document().paragraphs().last())
4315 .map(|paragraph| onestore::op::title_line(paragraph.text()));4366 .map(|paragraph| onestore::op::title_line(paragraph.text()));
4316 if editor.active_outline().title4367 if editor.active_outline().title
4317 || title.is_some_and(|title| !title.is_empty() && title != session.title())4368 || title.is_some_and(|title| {
4369 !title.is_empty() && title != session.title(self.pane.showing.space)
4370 })
4318 {4371 {
4319 session.pages = session.section.pages()?;4372 session.pages = session.section.pages()?;
4320 }4373 }
4321 self.edited(vec![space]);4374 self.edited(vec![space]);
4322 let editor = &self.view.editor;4375 let editor = &self.pane.view.editor;
4323 self.undo4376 self.undo
4324 .edited(space, editor.history_depth(), editor.typing().is_some());4377 .edited(space, editor.history_depth(), editor.typing().is_some());
4325 Ok(())4378 Ok(())
...@@ -4328,17 +4381,17 @@ impl State {...@@ -4328,17 +4381,17 @@ impl State {
4328 /// Shows the page as stored after a change made elsewhere, in place: the caret,4381 /// Shows the page as stored after a change made elsewhere, in place: the caret,
4329 /// selection, scroll and drawn pictures stay. Waits for marked text to end.4382 /// selection, scroll and drawn pictures stay. Waits for marked text to end.
4330 fn refresh(&mut self) -> Result<(), Box<dyn Error>> {4383 fn refresh(&mut self) -> Result<(), Box<dyn Error>> {
4331 if self.view.editor.marked_range().is_some() {4384 if self.pane.view.editor.marked_range().is_some() {
4332 self.stale = true;4385 self.pane.stale = true;
4333 return Ok(());4386 return Ok(());
4334 }4387 }
4335 self.stale = false;4388 self.pane.stale = false;
4336 self.persist()?;4389 self.persist()?;
4337 let Some(session) = &self.session else {4390 let Some(session) = &self.session else {
4338 return Ok(());4391 return Ok(());
4339 };4392 };
4340 let page = session.reader(session.space)()?;4393 let page = session.reader(self.pane.showing.space)()?;
4341 let response = self.view.refresh(page)?;4394 let response = self.pane.view.refresh(page)?;
4342 self.respond(response);4395 self.respond(response);
4343 self.title();4396 self.title();
4344 self.window.request_redraw();4397 self.window.request_redraw();
...@@ -4349,10 +4402,13 @@ impl State {...@@ -4349,10 +4402,13 @@ impl State {
4349 /// text the editor showed goes on the clipboard so nothing typed is lost unannounced.4402 /// text the editor showed goes on the clipboard so nothing typed is lost unannounced.
4350 fn refused(&mut self, error: &str) -> Result<(), Box<dyn Error>> {4403 fn refused(&mut self, error: &str) -> Result<(), Box<dyn Error>> {
4351 eprintln!("Saving failed: {error}");4404 eprintln!("Saving failed: {error}");
4352 self.clipboard.set_text(page_text(&self.view.editor))?;4405 self.clipboard.set_text(page_text(&self.pane.view.editor))?;
4353 // What the editor recorded since builds on the refused edit.4406 // What the editor recorded since builds on the refused edit.
4354 let _ = self.view.editor.take_ops();4407 let _ = self.pane.view.editor.take_ops();
4355 self.view.editor.cancel_composition(&mut self.view.engine)?;4408 self.pane
4409 .view
4410 .editor
4411 .cancel_composition(&mut self.pane.view.engine)?;
4356 platform::clear_marked_text(&self.window);4412 platform::clear_marked_text(&self.window);
4357 self.refresh()?;4413 self.refresh()?;
4358 platform::alert(4414 platform::alert(
...@@ -4413,13 +4469,13 @@ impl State {...@@ -4413,13 +4469,13 @@ impl State {
4413 match event {4469 match event {
4414 Event::Changed(spaces) => {4470 Event::Changed(spaces) => {
4415 listed = true;4471 listed = true;
4416 changed |= spaces.contains(&session.space);4472 changed |= spaces.contains(&self.pane.showing.space);
4417 elsewhere.extend_from_slice(&spaces);4473 elsewhere.extend_from_slice(&spaces);
4418 self.search4474 self.search
4419 .changed(session.key(), session.section.replica(), spaces);4475 .changed(session.key(), session.section.replica(), spaces);
4420 }4476 }
4421 Event::Rejected { spaces, error } => {4477 Event::Rejected { spaces, error } => {
4422 if spaces.contains(&session.space) {4478 if spaces.contains(&self.pane.showing.space) {
4423 rejected = Some(error);4479 rejected = Some(error);
4424 } else {4480 } else {
4425 eprintln!("Saving failed: {error}");4481 eprintln!("Saving failed: {error}");
...@@ -4447,23 +4503,26 @@ impl State {...@@ -4447,23 +4503,26 @@ impl State {
4447 return Ok(());4503 return Ok(());
4448 };4504 };
4449 if listed {4505 if listed {
4450 let back = session.shown;4506 let back = self.pane.showing.shown;
4451 session.pages = session.section.pages()?;4507 session.pages = session.section.pages()?;
4452 session.refresh_conflicts()?;4508 session.refresh_conflicts(&mut self.pane.showing)?;
4453 session.refresh_history()?;4509 session.refresh_history(&mut self.pane.showing)?;
4454 let listed = |space: ExGuid| session.pages.iter().any(|(page, ..)| *page == space);4510 let listed = |space: ExGuid| session.pages.iter().any(|(page, ..)| *page == space);
4455 let gone = session.version.is_some_and(|version| {4511 let gone = self.pane.showing.version.is_some_and(|version| {
4456 !session4512 !session
4457 .page_versions(session.space)4513 .page_versions(self.pane.showing.space)
4458 .iter()4514 .iter()
4459 .any(|listed| listed.context == version)4515 .any(|listed| listed.context == version)
4460 });4516 });
4461 if gone && listed(session.space) {4517 if gone && listed(self.pane.showing.space) {
4462 // A version deleted elsewhere returns to its page.4518 // A version deleted elsewhere returns to its page.
4463 self.commands.push(Command::OpenPage(session.space));4519 self.commands
4520 .push(Command::OpenPage(self.pane.showing.space));
4464 return Ok(());4521 return Ok(());
4465 }4522 }
4466 if !listed(session.space) && session.conflict(session.space).is_none() {4523 if !listed(self.pane.showing.space)
4524 && session.conflict(self.pane.showing.space).is_none()
4525 {
4467 // A page removed elsewhere, or a conflict page deleted elsewhere, which4526 // A page removed elsewhere, or a conflict page deleted elsewhere, which
4468 // returns to its page.4527 // returns to its page.
4469 let open = back4528 let open = back
...@@ -4511,7 +4570,7 @@ impl State {...@@ -4511,7 +4570,7 @@ impl State {
4511 }4570 }
4512 let mut error = None;4571 let mut error = None;
4513 let mut sent = false;4572 let mut sent = false;
4514 let (view, ui, window) = (&self.view, &self.ui, &self.window);4573 let (view, ui, window) = (&self.pane.view, &self.ui, &self.window);
4515 self.access_adapter.update_if_active(|| {4574 self.access_adapter.update_if_active(|| {
4516 sent = true;4575 sent = true;
4517 page_tree(view, ui, window, &mut self.accessibility).unwrap_or_else(|failure| {4576 page_tree(view, ui, window, &mut self.accessibility).unwrap_or_else(|failure| {
...@@ -4592,7 +4651,7 @@ impl State {...@@ -4592,7 +4651,7 @@ impl State {
4592 let mut tree = Tree::new(chrome, true);4651 let mut tree = Tree::new(chrome, true);
4593 if self.ui.laid_out(page()).is_some() {4652 if self.ui.laid_out(page()).is_some() {
4594 let mut fresh = accessibility::Accessibility::default();4653 let mut fresh = accessibility::Accessibility::default();
4595 let page = page_tree(&self.view, &self.ui, &self.window, &mut fresh)?;4654 let page = page_tree(&self.pane.view, &self.ui, &self.window, &mut fresh)?;
4596 tree.update_and_process_changes(page, &mut Unwatched);4655 tree.update_and_process_changes(page, &mut Unwatched);
4597 }4656 }
4598 if focus != graft || self.ui.laid_out(page()).is_some() {4657 if focus != graft || self.ui.laid_out(page()).is_some() {
...@@ -4638,7 +4697,7 @@ impl State {...@@ -4638,7 +4697,7 @@ impl State {
4638 if request.action == Action::Focus {4697 if request.action == Action::Focus {
4639 self.window.focus_window();4698 self.window.focus_window();
4640 self.ui.set_focus(Some(page()));4699 self.ui.set_focus(Some(page()));
4641 let response = self.view.focus_read_only(index)?;4700 let response = self.pane.view.focus_read_only(index)?;
4642 self.respond(response);4701 self.respond(response);
4643 self.window.request_redraw();4702 self.window.request_redraw();
4644 }4703 }
...@@ -4647,7 +4706,7 @@ impl State {...@@ -4647,7 +4706,7 @@ impl State {
4647 let Some(outline) = self.accessibility.outline_for_node(request.target_node) else {4706 let Some(outline) = self.accessibility.outline_for_node(request.target_node) else {
4648 return Ok(());4707 return Ok(());
4649 };4708 };
4650 let view = &mut self.view;4709 let view = &mut self.pane.view;
4651 match (request.action, request.data) {4710 match (request.action, request.data) {
4652 (Action::Focus, _) => {4711 (Action::Focus, _) => {
4653 view.editor.focus_outline(outline)?;4712 view.editor.focus_outline(outline)?;
...@@ -4671,7 +4730,7 @@ impl State {...@@ -4671,7 +4730,7 @@ impl State {
4671 _ => return Ok(()),4730 _ => return Ok(()),
4672 }4731 }
4673 self.ui.set_focus(Some(page()));4732 self.ui.set_focus(Some(page()));
4674 let response = self.view.focus_text()?;4733 let response = self.pane.view.focus_text()?;
4675 self.respond(response);4734 self.respond(response);
4676 self.window.request_redraw();4735 self.window.request_redraw();
4677 Ok(())4736 Ok(())
...@@ -4682,8 +4741,12 @@ impl State {...@@ -4682,8 +4741,12 @@ impl State {
4682 if let Some(path) = self.snapshot.take() {4741 if let Some(path) = self.snapshot.take() {
4683 // A hidden window never paints, so nothing polls the pictures between snapshots.4742 // A hidden window never paints, so nothing polls the pictures between snapshots.
4684 let paper = self.paper();4743 let paper = self.paper();
4685 if let Some((scene, _)) = &mut self.view.scene {4744 if let Some((scene, _)) = &mut self.pane.view.scene {
4686 scene.settle(Some(&self.view.editor), self.view.viewport.scale, paper);4745 scene.settle(
4746 Some(&self.pane.view.editor),
4747 self.pane.view.viewport.scale,
4748 paper,
4749 );
4687 }4750 }
4688 self.snapshot(&path)?;4751 self.snapshot(&path)?;
4689 }4752 }
...@@ -4724,7 +4787,7 @@ impl State {...@@ -4724,7 +4787,7 @@ impl State {
47244787
4725 /// Marks misspelled words on the page unless Hide Spelling Errors is on.4788 /// Marks misspelled words on the page unless Hide Spelling Errors is on.
4726 fn show_spelling(&mut self) {4789 fn show_spelling(&mut self) {
4727 self.view.spelling = self.spelling.clone().filter(|_| !self.hide_spelling);4790 self.pane.view.spelling = self.spelling.clone().filter(|_| !self.hide_spelling);
4728 self.window.request_redraw();4791 self.window.request_redraw();
4729 }4792 }
47304793
...@@ -4736,7 +4799,7 @@ impl State {...@@ -4736,7 +4799,7 @@ impl State {
4736 }4799 }
4737 .colored(4800 .colored(
4738 self.page_color_preview4801 self.page_color_preview
4739 .unwrap_or_else(|| self.view.editor.page_color()),4802 .unwrap_or_else(|| self.pane.view.editor.page_color()),
4740 )4803 )
4741 }4804 }
47424805
...@@ -4758,9 +4821,9 @@ impl State {...@@ -4758,9 +4821,9 @@ impl State {
4758 fn paint(&mut self, target: &draw::Target) -> Result<(), Box<dyn Error>> {4821 fn paint(&mut self, target: &draw::Target) -> Result<(), Box<dyn Error>> {
4759 let start = Instant::now();4822 let start = Instant::now();
4760 let paper = self.paper();4823 let paper = self.paper();
4761 self.view.update_pictures(paper, &self.redraw);4824 self.pane.view.update_pictures(paper, &self.redraw);
4762 if let Some(session) = &self.session {4825 if let Some(session) = &self.session {
4763 self.view.tag_art = session.library.tag_art();4826 self.pane.view.tag_art = session.library.tag_art();
4764 }4827 }
4765 let theme = &self.ui.theme;4828 let theme = &self.ui.theme;
4766 let scale = self.ui.scale();4829 let scale = self.ui.scale();
...@@ -4772,20 +4835,20 @@ impl State {...@@ -4772,20 +4835,20 @@ impl State {
4772 canvas::gpu::Viewport {4835 canvas::gpu::Viewport {
4773 scale,4836 scale,
4774 origin: [0.0; 2],4837 origin: [0.0; 2],
4775 ..self.view.viewport4838 ..self.pane.view.viewport
4776 },4839 },
4777 ),4840 ),
4778 None => (4841 None => (
4779 self.view.primitives(TextColors {4842 self.pane.view.primitives(TextColors {
4780 caret: theme.caret,4843 caret: theme.caret,
4781 selection: if self.page_focused {4844 selection: if self.pane.page_focused {
4782 theme.selection4845 theme.selection
4783 } else {4846 } else {
4784 theme.inactive_selection4847 theme.inactive_selection
4785 },4848 },
4786 paper,4849 paper,
4787 })?,4850 })?,
4788 self.view.viewport,4851 self.pane.view.viewport,
4789 ),4852 ),
4790 };4853 };
4791 lap("page primitives", start);4854 lap("page primitives", start);
...@@ -4853,14 +4916,17 @@ impl State {...@@ -4853,14 +4916,17 @@ impl State {
4853 fn pinch(&mut self, factor: f32) -> Result<(), Box<dyn Error>> {4916 fn pinch(&mut self, factor: f32) -> Result<(), Box<dyn Error>> {
4854 if self.over_page() {4917 if self.over_page() {
4855 // The pointer here, as the page's own lags by the moves queued for the next frame.4918 // The pointer here, as the page's own lags by the moves queued for the next frame.
4856 let response = self.view.pinch(factor, self.page_point(self.pointer))?;4919 let response = self
4920 .pane
4921 .view
4922 .pinch(factor, self.page_point(self.pointer))?;
4857 self.respond(response);4923 self.respond(response);
4858 }4924 }
4859 Ok(())4925 Ok(())
4860 }4926 }
48614927
4862 fn swipe_scroll(&mut self, phase: winit::event::TouchPhase, delta: [f32; 2]) {4928 fn swipe_scroll(&mut self, phase: winit::event::TouchPhase, delta: [f32; 2]) {
4863 let scroll = self.view.scroll();4929 let scroll = self.pane.view.scroll();
4864 let armed = scroll.max[0] <= scroll.min[0] && self.over_page();4930 let armed = scroll.max[0] <= scroll.min[0] && self.over_page();
4865 if let Some(forward) = self.swipe.scroll(phase, delta, armed, Instant::now()) {4931 if let Some(forward) = self.swipe.scroll(phase, delta, armed, Instant::now()) {
4866 self.travel(forward);4932 self.travel(forward);
...@@ -5209,6 +5275,7 @@ fn page_rows(...@@ -5209,6 +5275,7 @@ fn page_rows(
5209 theme: &Theme,5275 theme: &Theme,
5210 section: &ui::Section,5276 section: &ui::Section,
5211 session: &Session,5277 session: &Session,
5278 showing: &Showing,
5212 marked: [&HashSet<ExGuid>; 2],5279 marked: [&HashSet<ExGuid>; 2],
5213 peers: &HashMap<ExGuid, Vec<[f32; 4]>>,5280 peers: &HashMap<ExGuid, Vec<[f32; 4]>>,
5214 shape: (f32, bool),5281 shape: (f32, bool),
...@@ -5255,6 +5322,7 @@ fn page_rows(...@@ -5255,6 +5322,7 @@ fn page_rows(
5255 theme,5322 theme,
5256 section,5323 section,
5257 session,5324 session,
5325 showing,
5258 marked,5326 marked,
5259 peers,5327 peers,
5260 shape,5328 shape,
...@@ -5283,6 +5351,7 @@ fn page_rows(...@@ -5283,6 +5351,7 @@ fn page_rows(
5283 theme,5351 theme,
5284 section,5352 section,
5285 session,5353 session,
5354 showing,
5286 marked,5355 marked,
5287 peers,5356 peers,
5288 shape,5357 shape,
...@@ -5305,6 +5374,7 @@ fn page_row(...@@ -5305,6 +5374,7 @@ fn page_row(
5305 theme: &Theme,5374 theme: &Theme,
5306 section: &ui::Section,5375 section: &ui::Section,
5307 session: &Session,5376 session: &Session,
5377 showing: &Showing,
5308 [found, unread]: [&HashSet<ExGuid>; 2],5378 [found, unread]: [&HashSet<ExGuid>; 2],
5309 peers: &HashMap<ExGuid, Vec<[f32; 4]>>,5379 peers: &HashMap<ExGuid, Vec<[f32; 4]>>,
5310 shape: (f32, bool),5380 shape: (f32, bool),
...@@ -5334,7 +5404,7 @@ fn page_row(...@@ -5334,7 +5404,7 @@ fn page_row(
5334 shift,5404 shift,
5335 lifted,5405 lifted,
5336 };5406 };
5337 let selected = *space == session.space && session.version.is_none();5407 let selected = *space == showing.space && showing.version.is_none();
5338 if selected && !lifted {5408 if selected && !lifted {
5339 rows.open = Some(ui.id(space));5409 rows.open = Some(ui.id(space));
5340 }5410 }
...@@ -5366,19 +5436,19 @@ fn page_row(...@@ -5366,19 +5436,19 @@ fn page_row(
5366 };5436 };
5367 // Each row: its id, its label, whether it is open, and whether it is a page version.5437 // Each row: its id, its label, whether it is open, and whether it is a page version.
5368 let history = (session.page_versions(*space).iter())5438 let history = (session.page_versions(*space).iter())
5369 .filter(|_| session.shown_history == Some(*space))5439 .filter(|_| showing.shown_history == Some(*space))
5370 .map(|version| {5440 .map(|version| {
5371 let open = session.version == Some(version.context);5441 let open = showing.version == Some(version.context);
5372 (version.context, history::label(version), open, true)5442 (version.context, history::label(version), open, true)
5373 });5443 });
5374 let conflicts = (versions.iter())5444 let conflicts = (versions.iter())
5375 .filter(|_| session.shown == Some(*space))5445 .filter(|_| showing.shown == Some(*space))
5376 .map(|version| {5446 .map(|version| {
5377 let label = match version.created {5447 let label = match version.created {
5378 Some(created) => format!("{} {}", platform::short_date(created), version.user),5448 Some(created) => format!("{} {}", platform::short_date(created), version.user),
5379 None => version.user.clone(),5449 None => version.user.clone(),
5380 };5450 };
5381 (version.space, label, version.space == session.space, false)5451 (version.space, label, version.space == showing.space, false)
5382 });5452 });
5383 for (id, label, selected, version) in history.chain(conflicts) {5453 for (id, label, selected, version) in history.chain(conflicts) {
5384 let tab = PageTab {5454 let tab = PageTab {
...@@ -6236,14 +6306,14 @@ pub fn plain(error: &(dyn Error + 'static), thing: &str) -> String {...@@ -6236,14 +6306,14 @@ pub fn plain(error: &(dyn Error + 'static), thing: &str) -> String {
6236 }6306 }
6237}6307}
62386308
6239/// The session for `section`, at catalog `path` in `library`, showing `space` or its first6309/// The session for `section`, at catalog `path` in `library`, with `space` or its first
6240/// page, and that page.6310/// page and that page.
6241fn read_session(6311fn read_session(
6242 section: notebook::session::Section,6312 section: notebook::session::Section,
6243 library: Arc<Library>,6313 library: Arc<Library>,
6244 path: String,6314 path: String,
6245 space: Option<ExGuid>,6315 space: Option<ExGuid>,
6246) -> Result<(Session, Page), Box<dyn Error>> {6316) -> Result<(Session, ExGuid, Page), Box<dyn Error>> {
6247 let folder = path.rsplit_once('/').map_or("", |(folder, _)| folder);6317 let folder = path.rsplit_once('/').map_or("", |(folder, _)| folder);
6248 let tabs = library.tabs(folder);6318 let tabs = library.tabs(folder);
6249 let tab = tabs6319 let tab = tabs
...@@ -6266,15 +6336,11 @@ fn read_session(...@@ -6266,15 +6336,11 @@ fn read_session(
6266 tabs,6336 tabs,
6267 tab,6337 tab,
6268 pages,6338 pages,
6269 space,
6270 sync,6339 sync,
6271 conflicts,6340 conflicts,
6272 shown: None,
6273 change: None,
6274 history,6341 history,
6275 shown_history: None,
6276 version: None,
6277 },6342 },
6343 space,
6278 page,6344 page,
6279 ))6345 ))
6280}6346}
...@@ -6371,7 +6437,7 @@ impl State {...@@ -6371,7 +6437,7 @@ impl State {
6371 }6437 }
6372 UserEvent::InsertText(text) => {6438 UserEvent::InsertText(text) => {
6373 if self.ui.focused() == Some(page()) {6439 if self.ui.focused() == Some(page()) {
6374 match self.view.insert_text(text) {6440 match self.pane.view.insert_text(text) {
6375 Ok(response) => self.respond(response),6441 Ok(response) => self.respond(response),
6376 Err(error) => eprintln!("{error}"),6442 Err(error) => eprintln!("{error}"),
6377 }6443 }
...@@ -6488,7 +6554,7 @@ impl State {...@@ -6488,7 +6554,7 @@ impl State {
6488 // Quiet still when the mark sent on finding it quiet arrives, what a thread sent6554 // Quiet still when the mark sent on finding it quiet arrives, what a thread sent
6489 // as it ended has been taken, however many frames wait in the queue before it.6555 // as it ended has been taken, however many frames wait in the queue before it.
6490 let quiet = self.settled()6556 let quiet = self.settled()
6491 && self.opening.is_none()6557 && self.pane.opening.is_none()
6492 && RUNNING.load(Ordering::Acquire) == 06558 && RUNNING.load(Ordering::Acquire) == 0
6493 && self.search.pending.load(Ordering::Relaxed) == 0;6559 && self.search.pending.load(Ordering::Relaxed) == 0;
6494 if let Some((path, settled, sent)) = self.replay_settle.take() {6560 if let Some((path, settled, sent)) = self.replay_settle.take() {
...@@ -6522,7 +6588,7 @@ impl State {...@@ -6522,7 +6588,7 @@ impl State {
6522 if event.window_id != self.window.id() {6588 if event.window_id != self.window.id() {
6523 return;6589 return;
6524 }6590 }
6525 let was_marked = self.view.editor.marked_range().is_some();6591 let was_marked = self.pane.view.editor.marked_range().is_some();
6526 let result = match event.window_event {6592 let result = match event.window_event {
6527 accesskit_winit::WindowEvent::InitialTreeRequested => {6593 accesskit_winit::WindowEvent::InitialTreeRequested => {
6528 self.deactivate_accessibility();6594 self.deactivate_accessibility();
...@@ -6534,7 +6600,7 @@ impl State {...@@ -6534,7 +6600,7 @@ impl State {
6534 Ok(())6600 Ok(())
6535 }6601 }
6536 };6602 };
6537 if was_marked && self.view.editor.marked_range().is_none() {6603 if was_marked && self.pane.view.editor.marked_range().is_none() {
6538 platform::clear_marked_text(&self.window);6604 platform::clear_marked_text(&self.window);
6539 }6605 }
6540 if let Err(error) = result {6606 if let Err(error) = result {
...@@ -6547,7 +6613,7 @@ impl State {...@@ -6547,7 +6613,7 @@ impl State {
6547impl State {6613impl State {
6548 /// Quits, first asking whether to discard a temporary page holding edits.6614 /// Quits, first asking whether to discard a temporary page holding edits.
6549 fn quit(&self) {6615 fn quit(&self) {
6550 let editor = &self.view.editor;6616 let editor = &self.pane.view.editor;
6551 let unchanged = editor.caret_outline().is_none_or(TextOutline::is_empty)6617 let unchanged = editor.caret_outline().is_none_or(TextOutline::is_empty)
6552 && self.initial_date == editor.date().map(|date| date.timestamp())6618 && self.initial_date == editor.date().map(|date| date.timestamp())
6553 && (self.initial_layouts.iter())6619 && (self.initial_layouts.iter())
...@@ -6804,7 +6870,7 @@ impl ApplicationHandler<UserEvent> for App {...@@ -6804,7 +6870,7 @@ impl ApplicationHandler<UserEvent> for App {
6804 WindowEvent::PinchGesture { delta, .. } => state.pinch(1.0 + delta as f32)?,6870 WindowEvent::PinchGesture { delta, .. } => state.pinch(1.0 + delta as f32)?,
6805 // Smart zoom: to 200% about the fingers, or back to 100%.6871 // Smart zoom: to 200% about the fingers, or back to 100%.
6806 WindowEvent::DoubleTapGesture { .. } => {6872 WindowEvent::DoubleTapGesture { .. } => {
6807 let zoom = state.view.zoom();6873 let zoom = state.pane.view.zoom();
6808 let target = if (zoom - 1.0).abs() < 0.01 { 2.0 } else { 1.0 };6874 let target = if (zoom - 1.0).abs() < 0.01 { 2.0 } else { 1.0 };
6809 state.pinch(target / zoom)?6875 state.pinch(target / zoom)?
6810 }6876 }
...@@ -6826,7 +6892,7 @@ impl ApplicationHandler<UserEvent> for App {...@@ -6826,7 +6892,7 @@ impl ApplicationHandler<UserEvent> for App {
6826 return;6892 return;
6827 }6893 }
6828 let now = Instant::now();6894 let now = Instant::now();
6829 let (repaint, blink) = state.view.blink(now);6895 let (repaint, blink) = state.pane.view.blink(now);
6830 // A due interface change waits on the frame that shows it, which sets the next.6896 // A due interface change waits on the frame that shows it, which sets the next.
6831 let wake = state.ui.wake_at();6897 let wake = state.ui.wake_at();
6832 if repaint || wake.is_some_and(|wake| wake <= now) {6898 if repaint || wake.is_some_and(|wake| wake <= now) {
...@@ -7562,7 +7628,7 @@ mod tests {...@@ -7562,7 +7628,7 @@ mod tests {
7562 let section = library.open(&tab.path, || {}).unwrap();7628 let section = library.open(&tab.path, || {}).unwrap();
7563 let opened = ms(start);7629 let opened = ms(start);
7564 let start = Instant::now();7630 let start = Instant::now();
7565 let (session, page) =7631 let (session, _, page) =
7566 read_session(section, Arc::clone(&library), tab.path.clone(), None).unwrap();7632 read_session(section, Arc::clone(&library), tab.path.clone(), None).unwrap();
7567 let read = ms(start);7633 let read = ms(start);
7568 let start = Instant::now();7634 let start = Instant::now();
crates/snowbound/src/manage.rs+50-41
...@@ -177,8 +177,8 @@ impl State {...@@ -177,8 +177,8 @@ impl State {
177 title: &str,177 title: &str,
178 ) -> Result<PageCreation, Box<dyn Error>> {178 ) -> Result<PageCreation, Box<dyn Error>> {
179 let creation = PageCreation::new(before, Some(title), &self.author)?.titled_in(179 let creation = PageCreation::new(before, Some(title), &self.author)?.titled_in(
180 &self.view.editor.default_font.face,180 &self.pane.view.editor.default_font.face,
181 self.view.editor.default_font.color,181 self.pane.view.editor.default_font.color,
182 )?;182 )?;
183 let [date, time] = platform::date_text(creation.created());183 let [date, time] = platform::date_text(creation.created());
184 Ok(creation.dated(&date, &time)?)184 Ok(creation.dated(&date, &time)?)
...@@ -228,7 +228,7 @@ impl State {...@@ -228,7 +228,7 @@ impl State {
228 session.section.replica().apply(&self.author, edit)?;228 session.section.replica().apply(&self.author, edit)?;
229 #[cfg(target_arch = "wasm32")]229 #[cfg(target_arch = "wasm32")]
230 session.section.apply(&self.author, edit)?;230 session.section.apply(&self.author, edit)?;
231 let shown = session.space;231 let shown = self.pane.showing.space;
232 self.made(Change::Delete {232 self.made(Change::Delete {
233 pages: vec![space],233 pages: vec![space],
234 show: Some(shown),234 show: Some(shown),
...@@ -236,7 +236,8 @@ impl State {...@@ -236,7 +236,8 @@ impl State {
236 });236 });
237 let session = self.session.as_ref().ok_or("No section is open")?;237 let session = self.session.as_ref().ok_or("No section is open")?;
238 let page = session.section.page(space)?;238 let page = session.section.page(space)?;
239 let (scene, editor) = canvas::gpu::page::PageScene::from_page(page, &mut self.view.engine)?;239 let (scene, editor) =
240 canvas::gpu::page::PageScene::from_page(page, &mut self.pane.view.engine)?;
240 self.stop_loading();241 self.stop_loading();
241 let now = crate::Instant::now();242 let now = crate::Instant::now();
242 self.show_page(crate::Opening {243 self.show_page(crate::Opening {
...@@ -248,14 +249,15 @@ impl State {...@@ -248,14 +249,15 @@ impl State {
248 })?;249 })?;
249 self.ui.set_focus(Some(crate::page()));250 self.ui.set_focus(Some(crate::page()));
250 if let Some(title) = self251 if let Some(title) = self
252 .pane
251 .view253 .view
252 .editor254 .editor
253 .outlines()255 .outlines()
254 .iter()256 .iter()
255 .find(|outline| outline.title)257 .find(|outline| outline.title)
256 {258 {
257 self.view.editor.focus_outline(title.id)?;259 self.pane.view.editor.focus_outline(title.id)?;
258 let response = self.view.focus_text()?;260 let response = self.pane.view.focus_text()?;
259 self.respond(response);261 self.respond(response);
260 }262 }
261 Ok(())263 Ok(())
...@@ -267,7 +269,7 @@ impl State {...@@ -267,7 +269,7 @@ impl State {
267 self.with_art(Some(template), move |state, art| {269 self.with_art(Some(template), move |state, art| {
268 state.persist()?;270 state.persist()?;
269 let session = state.session.as_ref().ok_or("No section is open")?;271 let session = state.session.as_ref().ok_or("No section is open")?;
270 let ops = template_ops(&session.section.page(session.space)?, name, art)?;272 let ops = template_ops(&session.section.page(state.pane.showing.space)?, name, art)?;
271 state.edit_page(ops)273 state.edit_page(ops)
272 })274 })
273 }275 }
...@@ -283,16 +285,13 @@ impl State {...@@ -283,16 +285,13 @@ impl State {
283 let Some(template) = template else {285 let Some(template) = template else {
284 return then(self, Vec::new());286 return then(self, Vec::new());
285 };287 };
286 let space = self.session.as_ref().ok_or("No section is open")?.space;288 self.session.as_ref().ok_or("No section is open")?;
289 let space = self.pane.showing.space;
287 let proxy = self.proxy.clone();290 let proxy = self.proxy.clone();
288 crate::spawn(move || {291 crate::spawn(move || {
289 let art = template.pictures().map_err(|error| error.to_string());292 let art = template.pictures().map_err(|error| error.to_string());
290 let _ = proxy.send_event(crate::UserEvent::Then(Box::new(move |state| {293 let _ = proxy.send_event(crate::UserEvent::Then(Box::new(move |state| {
291 if state294 if state.session.is_some() && state.pane.showing.space == space {
292 .session
293 .as_ref()
294 .is_some_and(|open| open.space == space)
295 {
296 then(state, art?)295 then(state, art?)
297 } else {296 } else {
298 Ok(())297 Ok(())
...@@ -310,7 +309,7 @@ impl State {...@@ -310,7 +309,7 @@ impl State {
310 rule_lines: Option<onestore::page::RuleLines>,309 rule_lines: Option<onestore::page::RuleLines>,
311 art: Option<Vec<Image>>,310 art: Option<Vec<Image>>,
312 ) -> Result<(), Box<dyn Error>> {311 ) -> Result<(), Box<dyn Error>> {
313 let response = self.view.set_paper(color, rule_lines, art)?;312 let response = self.pane.view.set_paper(color, rule_lines, art)?;
314 self.respond(response);313 self.respond(response);
315 Ok(())314 Ok(())
316 }315 }
...@@ -321,7 +320,7 @@ impl State {...@@ -321,7 +320,7 @@ impl State {
321 return Ok(());320 return Ok(());
322 }321 }
323 let session = self.session.as_ref().ok_or("No section is open")?;322 let session = self.session.as_ref().ok_or("No section is open")?;
324 let space = session.space;323 let space = self.pane.showing.space;
325 session.section.apply(324 session.section.apply(
326 &self.author,325 &self.author,
327 Edit {326 Edit {
...@@ -469,6 +468,7 @@ impl State {...@@ -469,6 +468,7 @@ impl State {
469 pub(crate) fn show_arrived(&mut self, library: &Arc<Library>) {468 pub(crate) fn show_arrived(&mut self, library: &Arc<Library>) {
470 if self.session.is_some()469 if self.session.is_some()
471 || self470 || self
471 .pane
472 .sectionless472 .sectionless
473 .as_ref()473 .as_ref()
474 .is_some_and(|shown| shown.location != library.location)474 .is_some_and(|shown| shown.location != library.location)
...@@ -476,7 +476,7 @@ impl State {...@@ -476,7 +476,7 @@ impl State {
476 return;476 return;
477 }477 }
478 let first = library.first_section();478 let first = library.first_section();
479 self.sectionless = Some(Arc::clone(library));479 self.pane.sectionless = Some(Arc::clone(library));
480 self.title();480 self.title();
481 if let Some(path) = first {481 if let Some(path) = first {
482 self.commands482 self.commands
...@@ -572,8 +572,8 @@ impl State {...@@ -572,8 +572,8 @@ impl State {
572 .first_section()572 .first_section()
573 .ok_or("The new notebook has no section")?;573 .ok_or("The new notebook has no section")?;
574 let section = library.open_async(&path, notify).await?;574 let section = library.open_async(&path, notify).await?;
575 let (session, page) = read_session(section, library, path, None)?;575 let (session, space, page) = read_session(section, library, path, None)?;
576 Ok(Loaded::Section(Box::new(session), page))576 Ok(Loaded::Section(Box::new(session), space, page))
577 });577 });
578 Ok(())578 Ok(())
579 }579 }
...@@ -623,7 +623,7 @@ impl State {...@@ -623,7 +623,7 @@ impl State {
623 background.discard();623 background.discard();
624 }624 }
625 let shown = |shown: &Arc<Library>| shown.location == library.location;625 let shown = |shown: &Arc<Library>| shown.location == library.location;
626 if self.sectionless.as_ref().is_some_and(shown)626 if self.pane.sectionless.as_ref().is_some_and(shown)
627 || self627 || self
628 .session628 .session
629 .as_ref()629 .as_ref()
...@@ -631,7 +631,7 @@ impl State {...@@ -631,7 +631,7 @@ impl State {
631 {631 {
632 self.persist().unwrap_or_else(|error| eprintln!("{error}"));632 self.persist().unwrap_or_else(|error| eprintln!("{error}"));
633 self.session = None;633 self.session = None;
634 self.sectionless = None;634 self.pane.sectionless = None;
635 match self635 match self
636 .notebooks636 .notebooks
637 .iter()637 .iter()
...@@ -639,7 +639,7 @@ impl State {...@@ -639,7 +639,7 @@ impl State {
639 {639 {
640 Some((open, path)) => self.commands.push(Command::OpenSection(open, path)),640 Some((open, path)) => self.commands.push(Command::OpenSection(open, path)),
641 None => {641 None => {
642 self.sectionless = self.notebooks.first().cloned();642 self.pane.sectionless = self.notebooks.first().cloned();
643 self.title();643 self.title();
644 }644 }
645 }645 }
...@@ -695,15 +695,16 @@ impl State {...@@ -695,15 +695,16 @@ impl State {
695 (self.session).take_if(|session| Arc::ptr_eq(&session.library, &library))695 (self.session).take_if(|session| Arc::ptr_eq(&session.library, &library))
696 {696 {
697 // Its replica goes with it, so nothing may hold it.697 // Its replica goes with it, so nothing may hold it.
698 let _ = self.view.editor.take_ops();698 let _ = self.pane.view.editor.take_ops();
699 if let Err(error) = session.section.close() {699 if let Err(error) = session.section.close() {
700 eprintln!("{location}: {error}");700 eprintln!("{location}: {error}");
701 }701 }
702 self.sectionless = Some(Arc::clone(&library));702 self.pane.sectionless = Some(Arc::clone(&library));
703 }703 }
704 self.trashing.insert(location.clone());704 self.trashing.insert(location.clone());
705 self.close_notebook(&library);705 self.close_notebook(&library);
706 self.trail.forget(&location);706 self.pane.trail.forget(&location);
707 self.recent.retain(|place| place.notebook != location);
707 self.reads.forget(&location);708 self.reads.forget(&location);
708 let ours = crate::library::key(&location, "");709 let ours = crate::library::key(&location, "");
709 self.folded.retain(|key| !key.starts_with(&ours));710 self.folded.retain(|key| !key.starts_with(&ours));
...@@ -754,13 +755,13 @@ impl State {...@@ -754,13 +755,13 @@ impl State {
754 .as_ref()755 .as_ref()
755 .and_then(|_| self.session.take())756 .and_then(|_| self.session.take())
756 .map(|session| session.section);757 .map(|session| session.section);
757 let showing = shown.is_some() || self.sectionless.as_ref().is_some_and(ours);758 let showing = shown.is_some() || self.pane.sectionless.as_ref().is_some_and(ours);
758 if showing {759 if showing {
759 self.sectionless = Some(Arc::clone(&library));760 self.pane.sectionless = Some(Arc::clone(&library));
760 self.title();761 self.title();
761 }762 }
762 self.folder_rename = Some((library.location.clone(), to.clone()));763 self.folder_rename = Some((library.location.clone(), to.clone()));
763 let (cache, proxy, requested) = (self.cache.clone(), self.proxy.clone(), self.loading);764 let (cache, proxy, requested) = (self.cache.clone(), self.proxy.clone(), self.pane.loading);
764 crate::spawn_async(move || async move {765 crate::spawn_async(move || async move {
765 let structure = Arc::clone(&library.structure);766 let structure = Arc::clone(&library.structure);
766 let _structure = structure.lock().await;767 let _structure = structure.lock().await;
...@@ -805,7 +806,7 @@ impl State {...@@ -805,7 +806,7 @@ impl State {
805 state.folder_rename = None;806 state.folder_rename = None;
806 }807 }
807 let restore = showing808 let restore = showing
808 && requested == state.loading809 && requested == state.pane.loading
809 && state.notebook().is_some_and(|current| {810 && state.notebook().is_some_and(|current| {
810 current.location == library.location811 current.location == library.location
811 && Arc::ptr_eq(&current.structure, &library.structure)812 && Arc::ptr_eq(&current.structure, &library.structure)
...@@ -839,7 +840,14 @@ impl State {...@@ -839,7 +840,14 @@ impl State {
839 .retain(|open| open.location != to || !std::mem::replace(&mut listed, true));840 .retain(|open| open.location != to || !std::mem::replace(&mut listed, true));
840 if from != to {841 if from != to {
841 self.undo.close(&from);842 self.undo.close(&from);
842 self.trail.moved(&from, &to);843 self.pane.trail.moved(&from, &to);
844 for place in self
845 .recent
846 .iter_mut()
847 .filter(|place| place.notebook == from)
848 {
849 place.notebook.clone_from(&to);
850 }
843 self.reads.moved(&from, &to);851 self.reads.moved(&from, &to);
844 let (from, to) = (crate::library::key(&from, ""), crate::library::key(&to, ""));852 let (from, to) = (crate::library::key(&from, ""), crate::library::key(&to, ""));
845 let rekey = |key: &String| match key.strip_prefix(&from) {853 let rekey = |key: &String| match key.strip_prefix(&from) {
...@@ -852,7 +860,7 @@ impl State {...@@ -852,7 +860,7 @@ impl State {
852 .collect();860 .collect();
853 }861 }
854 if let Some(shown) = shown {862 if let Some(shown) = shown {
855 self.sectionless = Some(Arc::clone(&reopened));863 self.pane.sectionless = Some(Arc::clone(&reopened));
856 self.title();864 self.title();
857 if let Some(path) = shown865 if let Some(path) = shown
858 .filter(|path| reopened.contains(path))866 .filter(|path| reopened.contains(path))
...@@ -885,8 +893,8 @@ impl State {...@@ -885,8 +893,8 @@ impl State {
885 .filter(|session| Arc::ptr_eq(&session.library, &library))893 .filter(|session| Arc::ptr_eq(&session.library, &library))
886 .map(|session| session.tabs[session.tab].path.clone());894 .map(|session| session.tabs[session.tab].path.clone());
887 let notify = notify(self.proxy.clone());895 let notify = notify(self.proxy.clone());
888 let (proxy, requested) = (self.proxy.clone(), self.loading);896 let (proxy, requested) = (self.proxy.clone(), self.pane.loading);
889 let showing = self.switching.is_none() && self.opening.is_none();897 let showing = self.pane.switching.is_none() && self.pane.opening.is_none();
890 let structure = Arc::clone(&library.structure);898 let structure = Arc::clone(&library.structure);
891 let pending = match &change {899 let pending = match &change {
892 Structure::Rename { path, name } => library.entry_identity(path).map(|identity| {900 Structure::Rename { path, name } => library.entry_identity(path).map(|identity| {
...@@ -991,8 +999,8 @@ impl State {...@@ -991,8 +999,8 @@ impl State {
991 library.keep_key(&path, key);999 library.keep_key(&path, key);
992 }1000 }
993 let section = library.open_async(&path, notify).await?;1001 let section = library.open_async(&path, notify).await?;
994 let (session, page) = read_session(section, library, path, None)?;1002 let (session, space, page) = read_session(section, library, path, None)?;
995 return Ok(Loaded::Section(Box::new(session), page));1003 return Ok(Loaded::Section(Box::new(session), space, page));
996 }1004 }
997 Structure::Properties { name, color } => {1005 Structure::Properties { name, color } => {
998 if name != library.name {1006 if name != library.name {
...@@ -1018,11 +1026,11 @@ impl State {...@@ -1018,11 +1026,11 @@ impl State {
1018 return Ok(Loaded::Library(library, Some(path)));1026 return Ok(Loaded::Library(library, Some(path)));
1019 }1027 }
1020 let section = library.open_async(&path, notify).await?;1028 let section = library.open_async(&path, notify).await?;
1021 let (session, page) = read_session(section, library, path, None)?;1029 let (session, space, page) = read_session(section, library, path, None)?;
1022 Ok(if fresh {1030 Ok(if fresh {
1023 Loaded::Created(Box::new(session), page)1031 Loaded::Created(Box::new(session), space, page)
1024 } else {1032 } else {
1025 Loaded::Section(Box::new(session), page)1033 Loaded::Section(Box::new(session), space, page)
1026 })1034 })
1027 };1035 };
1028 crate::spawn_async(move || async move {1036 crate::spawn_async(move || async move {
...@@ -1040,7 +1048,7 @@ impl State {...@@ -1040,7 +1048,7 @@ impl State {
1040 };1048 };
1041 let library = match &loaded {1049 let library = match &loaded {
1042 Loaded::Library(library, _) => Arc::clone(library),1050 Loaded::Library(library, _) => Arc::clone(library),
1043 Loaded::Section(session, _) | Loaded::Created(session, _) => {1051 Loaded::Section(session, ..) | Loaded::Created(session, ..) => {
1044 Arc::clone(&session.library)1052 Arc::clone(&session.library)
1045 }1053 }
1046 _ => unreachable!("A structure change returns its notebook"),1054 _ => unreachable!("A structure change returns its notebook"),
...@@ -1071,7 +1079,7 @@ impl State {...@@ -1071,7 +1079,7 @@ impl State {
1071 });1079 });
1072 if let Some(path) = path {1080 if let Some(path) = path {
1073 state.adopt(library, Some(&path))?;1081 state.adopt(library, Some(&path))?;
1074 } else if requested != state.loading || !showing {1082 } else if requested != state.pane.loading || !showing {
1075 if let Some(path) = library.first_section() {1083 if let Some(path) = library.first_section() {
1076 state.commands.push(Command::OpenSection(library, path));1084 state.commands.push(Command::OpenSection(library, path));
1077 } else {1085 } else {
...@@ -1079,13 +1087,14 @@ impl State {...@@ -1079,13 +1087,14 @@ impl State {
1079 }1087 }
1080 }1088 }
1081 } else if state1089 } else if state
1090 .pane
1082 .sectionless1091 .sectionless
1083 .as_ref()1092 .as_ref()
1084 .is_some_and(|shown| shown.location == library.location)1093 .is_some_and(|shown| shown.location == library.location)
1085 {1094 {
1086 state.sectionless = Some(library);1095 state.pane.sectionless = Some(library);
1087 }1096 }
1088 if showing && requested == state.loading {1097 if showing && requested == state.pane.loading {
1089 match loaded {1098 match loaded {
1090 Loaded::Library(library, path) => state.adopt(library, path.as_deref())?,1099 Loaded::Library(library, path) => state.adopt(library, path.as_deref())?,
1091 loaded => state.load(move || Ok(loaded)),1100 loaded => state.load(move || Ok(loaded)),
crates/snowbound/src/menus.rs+2-2
...@@ -489,7 +489,7 @@ impl State {...@@ -489,7 +489,7 @@ impl State {
489 let above = at489 let above = at
490 .and_then(|at| at.checked_sub(1))490 .and_then(|at| at.checked_sub(1))
491 .map_or(0, |above| pages[above].2);491 .map_or(0, |above| pages[above].2);
492 let shown = session.shown_history == Some(*space);492 let shown = self.pane.showing.shown_history == Some(*space);
493 let versions = if shown {493 let versions = if shown {
494 "Hide Page Versions"494 "Hide Page Versions"
495 } else {495 } else {
...@@ -1039,7 +1039,7 @@ impl State {...@@ -1039,7 +1039,7 @@ impl State {
1039 new(&action, String::new()).map(|structure| Command::Structure(library, structure))1039 new(&action, String::new()).map(|structure| Command::Structure(library, structure))
1040 }1040 }
1041 (Target::Closed(location), Action::Delete) => {1041 (Target::Closed(location), Action::Delete) => {
1042 self.trail.recent.retain(|place| place.notebook != location);1042 self.recent.retain(|place| place.notebook != location);
1043 self.save_settings();1043 self.save_settings();
1044 None1044 None
1045 }1045 }
crates/snowbound/src/navigation.rs+46-50
...@@ -39,19 +39,17 @@ impl Visit {...@@ -39,19 +39,17 @@ impl Visit {
39 }39 }
40}40}
4141
42/// Pages visited before and after the one shown, and those shown lately.42/// Pages visited before and after the one shown.
43#[derive(Default)]43#[derive(Default)]
44pub struct Trail {44pub struct Trail {
45 back: Vec<Visit>,45 back: Vec<Visit>,
46 here: Option<Visit>,46 here: Option<Visit>,
47 forward: Vec<Visit>,47 forward: Vec<Visit>,
48 /// Each page shown lately once, latest first, kept between launches.
49 pub recent: Vec<Place>,
50}48}
5149
52/// Places Back keeps, at most.50/// Places Back keeps, at most.
53const KEPT: usize = 100;51const KEPT: usize = 100;
54/// Pages `Trail::recent` keeps, at most.52/// Pages `State::recent` keeps, at most.
55const RECENT: usize = 8;53const RECENT: usize = 8;
5654
57impl Trail {55impl Trail {
...@@ -61,8 +59,7 @@ impl Trail {...@@ -61,8 +59,7 @@ impl Trail {
61 .chain(&mut self.here)59 .chain(&mut self.here)
62 .chain(&mut self.forward)60 .chain(&mut self.forward)
63 .map(|visit| &mut visit.notebook);61 .map(|visit| &mut visit.notebook);
64 let places = self.recent.iter_mut().map(|place| &mut place.notebook);62 for notebook in visits.filter(|notebook| *notebook == from) {
65 for notebook in visits.chain(places).filter(|notebook| *notebook == from) {
66 *notebook = to.to_owned();63 *notebook = to.to_owned();
67 }64 }
68 }65 }
...@@ -73,14 +70,6 @@ impl Trail {...@@ -73,14 +70,6 @@ impl Trail {
73 self.back.retain(elsewhere);70 self.back.retain(elsewhere);
74 self.forward.retain(elsewhere);71 self.forward.retain(elsewhere);
75 self.here = self.here.take().filter(elsewhere);72 self.here = self.here.take().filter(elsewhere);
76 self.recent.retain(|place| place.notebook != location);
77 }
78
79 /// Notes `place` shown, as the recent pages list it.
80 pub fn remember(&mut self, place: Place) {
81 self.recent.retain(|recent| *recent != place);
82 self.recent.insert(0, place);
83 self.recent.truncate(RECENT);
84 }73 }
8574
86 /// Notes `visit` shown. Arriving anywhere but where Back or Forward went drops the pages75 /// Notes `visit` shown. Arriving anywhere but where Back or Forward went drops the pages
...@@ -122,23 +111,30 @@ impl Trail {...@@ -122,23 +111,30 @@ impl Trail {
122 pub fn open(&self, exists: impl Fn(&Visit) -> bool) -> [bool; 2] {111 pub fn open(&self, exists: impl Fn(&Visit) -> bool) -> [bool; 2] {
123 [&self.back, &self.forward].map(|side| side.iter().rev().any(&exists))112 [&self.back, &self.forward].map(|side| side.iter().rev().any(&exists))
124 }113 }
114}
125115
126 /// Drops the recent pages whose section `listed` refuses, and those whose section `index`116/// Notes `place` shown, as the recent pages list it.
127 /// has read without them. Returns whether any went.117pub fn remember(recent: &mut Vec<Place>, place: Place) {
128 pub fn prune(&mut self, index: &Index, listed: impl Fn(&Place) -> bool) -> bool {118 recent.retain(|known| *known != place);
129 let read: HashSet<&str> = (index.entries().iter())119 recent.insert(0, place);
130 .map(|entry| entry.section.as_str())120 recent.truncate(RECENT);
131 .collect();121}
132 let before = self.recent.len();122
133 self.recent.retain(|place| {123/// Drops the recent pages whose section `listed` refuses, and those whose section `index`
134 let key = crate::library::key(&place.notebook, &place.section);124/// has read without them. Returns whether any went.
135 listed(place)125pub fn prune(recent: &mut Vec<Place>, index: &Index, listed: impl Fn(&Place) -> bool) -> bool {
136 && (!read.contains(key.as_str())126 let read: HashSet<&str> = (index.entries().iter())
137 || (index.entries().iter())127 .map(|entry| entry.section.as_str())
138 .any(|entry| entry.space == place.page && entry.section == key))128 .collect();
139 });129 let before = recent.len();
140 self.recent.len() != before130 recent.retain(|place| {
141 }131 let key = crate::library::key(&place.notebook, &place.section);
132 listed(place)
133 && (!read.contains(key.as_str())
134 || (index.entries().iter())
135 .any(|entry| entry.space == place.page && entry.section == key))
136 });
137 recent.len() != before
142}138}
143139
144/// Where `visit`'s page is in `notebooks`, as `index` has read them and `open` tells whether140/// Where `visit`'s page is in `notebooks`, as `index` has read them and `open` tells whether
...@@ -189,19 +185,19 @@ impl State {...@@ -189,19 +185,19 @@ impl State {
189 let place = Place {185 let place = Place {
190 notebook: session.library.location.clone(),186 notebook: session.library.location.clone(),
191 section: path.clone(),187 section: path.clone(),
192 page: session.space,188 page: self.pane.showing.space,
193 };189 };
194 if let Ok(section) = session.section.identity() {190 if let Ok(section) = session.section.identity() {
195 self.trail.visit(Visit {191 self.pane.trail.visit(Visit {
196 notebook: place.notebook.clone(),192 notebook: place.notebook.clone(),
197 section,193 section,
198 space: session.space,194 space: self.pane.showing.space,
199 page: self.view.editor.identity(),195 page: self.pane.view.editor.identity(),
200 binned: crate::recycle::binned(path),196 binned: crate::recycle::binned(path),
201 });197 });
202 }198 }
203 let moved = self.trail.recent.first() != Some(&place);199 let moved = self.recent.first() != Some(&place);
204 self.trail.remember(place);200 remember(&mut self.recent, place);
205 if moved {201 if moved {
206 self.save_settings();202 self.save_settings();
207 }203 }
...@@ -222,7 +218,7 @@ impl State {...@@ -222,7 +218,7 @@ impl State {
222218
223 /// Whether Back and Forward have a page to go to.219 /// Whether Back and Forward have a page to go to.
224 pub(crate) fn can_travel(&self) -> [bool; 2] {220 pub(crate) fn can_travel(&self) -> [bool; 2] {
225 self.trail.open(|visit| self.find(visit).is_some())221 self.pane.trail.open(|visit| self.find(visit).is_some())
226 }222 }
227223
228 /// Whether `library`'s section at `path` is the one open.224 /// Whether `library`'s section at `path` is the one open.
...@@ -246,9 +242,9 @@ impl State {...@@ -246,9 +242,9 @@ impl State {
246 /// Shows the page visited before the one shown, or after it going `forward`, wherever it242 /// Shows the page visited before the one shown, or after it going `forward`, wherever it
247 /// went since. Pages since deleted, and those of notebooks since closed, are passed over.243 /// went since. Pages since deleted, and those of notebooks since closed, are passed over.
248 pub(crate) fn travel(&mut self, forward: bool) {244 pub(crate) fn travel(&mut self, forward: bool) {
249 let mut trail = std::mem::take(&mut self.trail);245 let mut trail = std::mem::take(&mut self.pane.trail);
250 let visit = trail.step(forward, |visit| self.find(visit).is_some());246 let visit = trail.step(forward, |visit| self.find(visit).is_some());
251 self.trail = trail;247 self.pane.trail = trail;
252 if let Some((library, path, space)) = visit.and_then(|visit| self.find(&visit)) {248 if let Some((library, path, space)) = visit.and_then(|visit| self.find(&visit)) {
253 self.go(library, path, space);249 self.go(library, path, space);
254 }250 }
...@@ -418,16 +414,16 @@ mod tests {...@@ -418,16 +414,16 @@ mod tests {
418414
419 #[test]415 #[test]
420 fn recent_pages_are_kept_once_latest_first_and_bounded() {416 fn recent_pages_are_kept_once_latest_first_and_bounded() {
421 let mut trail = Trail::default();417 let mut recent = Vec::new();
422 for page in [1, 2, 1] {418 for page in [1, 2, 1] {
423 trail.remember(place("A.one", page));419 remember(&mut recent, place("A.one", page));
424 }420 }
425 assert_eq!(trail.recent, [place("A.one", 1), place("A.one", 2)]);421 assert_eq!(recent, [place("A.one", 1), place("A.one", 2)]);
426 for page in 0..20 {422 for page in 0..20 {
427 trail.remember(place("B.one", page));423 remember(&mut recent, place("B.one", page));
428 }424 }
429 assert_eq!(trail.recent.len(), RECENT);425 assert_eq!(recent.len(), RECENT);
430 assert_eq!(trail.recent[0], place("B.one", 19));426 assert_eq!(recent[0], place("B.one", 19));
431 }427 }
432428
433 #[test]429 #[test]
...@@ -452,7 +448,7 @@ mod tests {...@@ -452,7 +448,7 @@ mod tests {
452 &page("Kept"),448 &page("Kept"),
453 0,449 0,
454 ));450 ));
455 let mut trail = Trail::default();451 let mut recent = Vec::new();
456 // Visited oldest first: the index has read A without page 2, not yet B, and C is gone.452 // Visited oldest first: the index has read A without page 2, not yet B, and C is gone.
457 for gone in [453 for gone in [
458 place("C.one", 1),454 place("C.one", 1),
...@@ -460,12 +456,12 @@ mod tests {...@@ -460,12 +456,12 @@ mod tests {
460 place("A.one", 2),456 place("A.one", 2),
461 kept.clone(),457 kept.clone(),
462 ] {458 ] {
463 trail.remember(gone);459 remember(&mut recent, gone);
464 }460 }
465 let listed = |place: &Place| place.section != "C.one";461 let listed = |place: &Place| place.section != "C.one";
466 assert!(trail.prune(&index, listed));462 assert!(prune(&mut recent, &index, listed));
467 assert_eq!(trail.recent, [kept, place("B.one", 1)]);463 assert_eq!(recent, [kept, place("B.one", 1)]);
468 assert!(!trail.prune(&index, listed), "nothing more goes");464 assert!(!prune(&mut recent, &index, listed), "nothing more goes");
469 }465 }
470466
471 #[test]467 #[test]
crates/snowbound/src/options.rs+4-4
...@@ -390,10 +390,10 @@ impl State {...@@ -390,10 +390,10 @@ impl State {
390 color_scheme: self.color_scheme,390 color_scheme: self.color_scheme,
391 pages_match: !self.light_pages,391 pages_match: !self.light_pages,
392 hide_spelling: self.hide_spelling,392 hide_spelling: self.hide_spelling,
393 markdown: self.view.editor.markdown.is_some(),393 markdown: self.pane.view.editor.markdown.is_some(),
394 automatic_updates: self.updates.automatic(),394 automatic_updates: self.updates.automatic(),
395 pen_pressure: self.pen_pressure,395 pen_pressure: self.pen_pressure,
396 default_font: self.view.editor.default_font.clone(),396 default_font: self.pane.view.editor.default_font.clone(),
397 page_tabs_left: self.page_tabs_left,397 page_tabs_left: self.page_tabs_left,
398 navigation_bar_left: !self.navigation_bar_right,398 navigation_bar_left: !self.navigation_bar_right,
399 lock_after: self.passwords.lock_after,399 lock_after: self.passwords.lock_after,
...@@ -692,9 +692,9 @@ impl State {...@@ -692,9 +692,9 @@ impl State {
692 self.color_scheme = options.color_scheme;692 self.color_scheme = options.color_scheme;
693 self.light_pages = !options.pages_match;693 self.light_pages = !options.pages_match;
694 self.hide_spelling = options.hide_spelling;694 self.hide_spelling = options.hide_spelling;
695 self.view.editor.markdown = crate::commands::markdown(options.markdown);695 self.pane.view.editor.markdown = crate::commands::markdown(options.markdown);
696 self.pen_pressure = options.pen_pressure;696 self.pen_pressure = options.pen_pressure;
697 self.view.editor.default_font = options.default_font;697 self.pane.view.editor.default_font = options.default_font;
698 self.page_tabs_left = options.page_tabs_left;698 self.page_tabs_left = options.page_tabs_left;
699 self.navigation_bar_right = !options.navigation_bar_left;699 self.navigation_bar_right = !options.navigation_bar_left;
700 self.passwords = crate::settings::Passwords {700 self.passwords = crate::settings::Passwords {
crates/snowbound/src/palette.rs+5-5
...@@ -114,7 +114,7 @@ impl State {...@@ -114,7 +114,7 @@ impl State {
114 }114 }
115 }115 }
116 // And what the page's context menu does at the caret, or to the file selected.116 // And what the page's context menu does at the caret, or to the file selected.
117 let text = self.view.caret_context();117 let text = self.pane.view.caret_context();
118 for (item, title) in text.iter().flat_map(|context| self.text_items(context)) {118 for (item, title) in text.iter().flat_map(|context| self.text_items(context)) {
119 if !(commands::COMMANDS.iter()).any(|command| command.title == title) {119 if !(commands::COMMANDS.iter()).any(|command| command.title == title) {
120 commands.push(Row {120 commands.push(Row {
...@@ -334,7 +334,7 @@ impl State {...@@ -334,7 +334,7 @@ impl State {
334 Target::Page {334 Target::Page {
335 library: Arc::clone(library),335 library: Arc::clone(library),
336 path: path.clone(),336 path: path.clone(),
337 space: session.space,337 space: self.pane.showing.space,
338 },338 },
339 Target::Section {339 Target::Section {
340 library: Arc::clone(library),340 library: Arc::clone(library),
...@@ -367,7 +367,7 @@ impl State {...@@ -367,7 +367,7 @@ impl State {
367 .map(Arc::clone)367 .map(Arc::clone)
368 };368 };
369 // A closed notebook keeps its places to open it again, an unreadable one until it reads.369 // A closed notebook keeps its places to open it again, an unreadable one until it reads.
370 let pruned = self.trail.prune(&index, |place| {370 let pruned = crate::navigation::prune(&mut self.recent, &index, |place| {
371 library(&place.notebook)371 library(&place.notebook)
372 .is_none_or(|library| library.notebook.is_err() || library.contains(&place.section))372 .is_none_or(|library| library.notebook.is_err() || library.contains(&place.section))
373 });373 });
...@@ -375,7 +375,7 @@ impl State {...@@ -375,7 +375,7 @@ impl State {
375 (375 (
376 session.library.location.as_str(),376 session.library.location.as_str(),
377 session.tabs[session.tab].path.as_str(),377 session.tabs[session.tab].path.as_str(),
378 session.space,378 self.pane.showing.space,
379 )379 )
380 });380 });
381 let theme = &self.ui.theme;381 let theme = &self.ui.theme;
...@@ -393,7 +393,7 @@ impl State {...@@ -393,7 +393,7 @@ impl State {
393 _ => false,393 _ => false,
394 })394 })
395 };395 };
396 for place in &self.trail.recent {396 for place in &self.recent {
397 let Some(library) = library(&place.notebook) else {397 let Some(library) = library(&place.notebook) else {
398 let target = Target::Closed(place.notebook.clone());398 let target = Target::Closed(place.notebook.clone());
399 if !listed(&closed, &target) {399 if !listed(&closed, &target) {
crates/snowbound/src/paste.rs+7-6
...@@ -124,7 +124,7 @@ impl State {...@@ -124,7 +124,7 @@ impl State {
124 }124 }
125 }125 }
126 Some(Pasted::Clip(clip)) => {126 Some(Pasted::Clip(clip)) => {
127 let response = self.view.paste_clip(clip)?;127 let response = self.pane.view.paste_clip(clip)?;
128 self.respond(response);128 self.respond(response);
129 }129 }
130 Some(Pasted::Page(html, text)) => {130 Some(Pasted::Page(html, text)) => {
...@@ -142,12 +142,12 @@ impl State {...@@ -142,12 +142,12 @@ impl State {
142 });142 });
143 if pieces.is_empty() {143 if pieces.is_empty() {
144 if let Some(text) = text {144 if let Some(text) = text {
145 let response = self.view.paste(&text, language)?;145 let response = self.pane.view.paste(&text, language)?;
146 self.respond(response);146 self.respond(response);
147 }147 }
148 return Ok(());148 return Ok(());
149 }149 }
150 let (places, response) = self.view.paste_pieces(pieces)?;150 let (places, response) = self.pane.view.paste_pieces(pieces)?;
151 self.respond(response);151 self.respond(response);
152 for (place, (source, css)) in places.into_iter().zip(fetched) {152 for (place, (source, css)) in places.into_iter().zip(fetched) {
153 let proxy = self.proxy.clone();153 let proxy = self.proxy.clone();
...@@ -159,7 +159,7 @@ impl State {...@@ -159,7 +159,7 @@ impl State {
159 return;159 return;
160 };160 };
161 let _ = proxy.send_event(crate::UserEvent::Then(Box::new(move |state| {161 let _ = proxy.send_event(crate::UserEvent::Then(Box::new(move |state| {
162 let response = state.view.insert_awaited(place, bytes, size)?;162 let response = state.pane.view.insert_awaited(place, bytes, size)?;
163 state.respond(response);163 state.respond(response);
164 Ok(())164 Ok(())
165 })));165 })));
...@@ -168,7 +168,7 @@ impl State {...@@ -168,7 +168,7 @@ impl State {
168 }168 }
169 Some(Pasted::Text(text)) => {169 Some(Pasted::Text(text)) => {
170 let language = canvas::language::lcid(&platform::input_language());170 let language = canvas::language::lcid(&platform::input_language());
171 let response = self.view.paste(&text, language)?;171 let response = self.pane.view.paste(&text, language)?;
172 self.respond(response);172 self.respond(response);
173 }173 }
174 Some(Pasted::Picture(bytes)) => self.insert_picture(bytes, None)?,174 Some(Pasted::Picture(bytes)) => self.insert_picture(bytes, None)?,
...@@ -194,9 +194,10 @@ impl State {...@@ -194,9 +194,10 @@ impl State {
194 let size = picture_size(&bytes, pixels);194 let size = picture_size(&bytes, pixels);
195 let response = match at {195 let response = match at {
196 Some(point) => self196 Some(point) => self
197 .pane
197 .view198 .view
198 .drop_picture(self.page_point(point), bytes, size)?,199 .drop_picture(self.page_point(point), bytes, size)?,
199 None => self.view.insert_picture(bytes, size)?,200 None => self.pane.view.insert_picture(bytes, size)?,
200 };201 };
201 self.respond(response);202 self.respond(response);
202 Ok(())203 Ok(())
crates/snowbound/src/prefetch.rs+7-7
...@@ -277,7 +277,7 @@ impl State {...@@ -277,7 +277,7 @@ impl State {
277 /// The section tab shown open: one being opened, from the moment it is asked for.277 /// The section tab shown open: one being opened, from the moment it is asked for.
278 pub fn open_tab(&self) -> Option<usize> {278 pub fn open_tab(&self) -> Option<usize> {
279 let session = self.session.as_ref()?;279 let session = self.session.as_ref()?;
280 let opening = self.switching.and_then(|(tab, _)| tab);280 let opening = self.pane.switching.and_then(|(tab, _)| tab);
281 Some(281 Some(
282 opening282 opening
283 .filter(|tab| *tab < session.tabs.len())283 .filter(|tab| *tab < session.tabs.len())
...@@ -287,15 +287,15 @@ impl State {...@@ -287,15 +287,15 @@ impl State {
287287
288 /// Since when the page area shows that a page is opening, once it has taken a while.288 /// Since when the page area shows that a page is opening, once it has taken a while.
289 pub fn loading(&self) -> Option<Instant> {289 pub fn loading(&self) -> Option<Instant> {
290 let (_, since) = self.switching?;290 let (_, since) = self.pane.switching?;
291 (since.elapsed() >= PATIENCE).then_some(since)291 (since.elapsed() >= PATIENCE).then_some(since)
292 }292 }
293293
294 /// Leaves the page shown as it is, dropping whatever was opening in its place.294 /// Leaves the page shown as it is, dropping whatever was opening in its place.
295 pub fn stop_loading(&mut self) {295 pub fn stop_loading(&mut self) {
296 self.loading += 1;296 self.pane.loading += 1;
297 self.opening = None;297 self.pane.opening = None;
298 self.switching = None;298 self.pane.switching = None;
299 }299 }
300300
301 /// Readies the pages beside the open one and the sections beside the open section.301 /// Readies the pages beside the open one and the sections beside the open section.
...@@ -306,7 +306,7 @@ impl State {...@@ -306,7 +306,7 @@ impl State {
306 let at = session306 let at = session
307 .pages307 .pages
308 .iter()308 .iter()
309 .position(|(space, ..)| *space == session.space);309 .position(|(space, ..)| *space == self.pane.showing.space);
310 let pages: Vec<ExGuid> = at310 let pages: Vec<ExGuid> = at
311 .into_iter()311 .into_iter()
312 .flat_map(|at| [at + 1, at.wrapping_sub(1)])312 .flat_map(|at| [at + 1, at.wrapping_sub(1)])
...@@ -330,7 +330,7 @@ impl State {...@@ -330,7 +330,7 @@ impl State {
330 return;330 return;
331 };331 };
332 let key = session.key();332 let key = session.key();
333 if space == session.space || !self.prefetch.ask((key.clone(), Some(space))) {333 if space == self.pane.showing.space || !self.prefetch.ask((key.clone(), Some(space))) {
334 return;334 return;
335 }335 }
336 let read = session.reader(space);336 let read = session.reader(space);
crates/snowbound/src/print.rs+3-3
...@@ -281,11 +281,11 @@ impl State {...@@ -281,11 +281,11 @@ impl State {
281 let session = self.session.as_ref().ok_or("No section is open")?;281 let session = self.session.as_ref().ok_or("No section is open")?;
282 let tab = &session.tabs[session.tab];282 let tab = &session.tabs[session.tab];
283 // The open page as the editor holds it; the others as stored.283 // The open page as the editor holds it; the others as stored.
284 let open = (session.space, self.view.editor.page()?);284 let open = (self.pane.showing.space, self.pane.view.editor.page()?);
285 let at = session285 let at = session
286 .pages286 .pages
287 .iter()287 .iter()
288 .position(|(space, ..)| *space == session.space);288 .position(|(space, ..)| *space == self.pane.showing.space);
289 let spaces: Vec<ExGuid> = match (setup.scope, at) {289 let spaces: Vec<ExGuid> = match (setup.scope, at) {
290 (Scope::PageGroup, Some(at)) => {290 (Scope::PageGroup, Some(at)) => {
291 let levels: Vec<u32> = session.pages.iter().map(|(.., level)| *level).collect();291 let levels: Vec<u32> = session.pages.iter().map(|(.., level)| *level).collect();
...@@ -297,7 +297,7 @@ impl State {...@@ -297,7 +297,7 @@ impl State {
297 (Scope::Section | Scope::Notebook, _) => {297 (Scope::Section | Scope::Notebook, _) => {
298 session.pages.iter().map(|(space, ..)| *space).collect()298 session.pages.iter().map(|(space, ..)| *space).collect()
299 }299 }
300 _ => vec![session.space],300 _ => vec![self.pane.showing.space],
301 };301 };
302 let title = match setup.scope {302 let title = match setup.scope {
303 Scope::Page | Scope::PageGroup => open.1.title.clone(),303 Scope::Page | Scope::PageGroup => open.1.title.clone(),
crates/snowbound/src/protection.rs+5-5
...@@ -89,8 +89,8 @@ impl State {...@@ -89,8 +89,8 @@ impl State {
89 previous.library.keep(path, previous.section);89 previous.library.keep(path, previous.section);
90 }90 }
91 }91 }
92 self.sectionless = None;92 self.pane.sectionless = None;
93 self.locked = Some(Locked { library, path });93 self.pane.locked = Some(Locked { library, path });
94 self.ui.set_focus(Some(notice()));94 self.ui.set_focus(Some(notice()));
95 self.title();95 self.title();
96 Ok(())96 Ok(())
...@@ -113,7 +113,7 @@ impl State {...@@ -113,7 +113,7 @@ impl State {
113 section: &ui::Section,113 section: &ui::Section,
114 strip: [f32; 4],114 strip: [f32; 4],
115 ) -> (Option<Command>, Id) {115 ) -> (Option<Command>, Id) {
116 let Some(locked) = &self.locked else {116 let Some(locked) = &self.pane.locked else {
117 return (None, row);117 return (None, row);
118 };118 };
119 let library = Arc::clone(&locked.library);119 let library = Arc::clone(&locked.library);
...@@ -157,7 +157,7 @@ impl State {...@@ -157,7 +157,7 @@ impl State {
157157
158 /// The locked section's page, as OneNote 2010 draws it: a click or Enter unlocks.158 /// The locked section's page, as OneNote 2010 draws it: a click or Enter unlocks.
159 pub(crate) fn locked_page(&mut self) {159 pub(crate) fn locked_page(&mut self) {
160 let Some(locked) = &self.locked else {160 let Some(locked) = &self.pane.locked else {
161 return;161 return;
162 };162 };
163 let (library, path) = (Arc::clone(&locked.library), locked.path.clone());163 let (library, path) = (Arc::clone(&locked.library), locked.path.clone());
...@@ -266,7 +266,7 @@ impl State {...@@ -266,7 +266,7 @@ impl State {
266266
267 /// The section shown, open or locked, with its notebook.267 /// The section shown, open or locked, with its notebook.
268 pub(crate) fn shown_section(&self) -> Option<(Arc<Library>, String)> {268 pub(crate) fn shown_section(&self) -> Option<(Arc<Library>, String)> {
269 match (&self.session, &self.locked) {269 match (&self.session, &self.pane.locked) {
270 (Some(session), _) if session.library.catalog().is_some() => Some((270 (Some(session), _) if session.library.catalog().is_some() => Some((
271 Arc::clone(&session.library),271 Arc::clone(&session.library),
272 session.tabs[session.tab].path.clone(),272 session.tabs[session.tab].path.clone(),
crates/snowbound/src/recording.rs+15-15
...@@ -93,7 +93,7 @@ impl State {...@@ -93,7 +93,7 @@ impl State {
93 };93 };
94 let [date, time] = platform::date_text(crate::filetime());94 let [date, time] = platform::date_text(crate::filetime());
95 let label = canvas::recording::label(video, &date, &time);95 let label = canvas::recording::label(video, &date, &time);
96 let (id, response) = self.view.start_recording(&label)?;96 let (id, response) = self.pane.view.start_recording(&label)?;
97 self.respond(response);97 self.respond(response);
98 if id.is_none() {98 if id.is_none() {
99 recorder.stop()?()?;99 recorder.stop()?()?;
...@@ -117,7 +117,7 @@ impl State {...@@ -117,7 +117,7 @@ impl State {
117 path,117 path,
118 } => {118 } => {
119 // Nothing written while the file saves links to the recording.119 // Nothing written while the file saves links to the recording.
120 self.view.editor.pause_recording(true);120 self.pane.view.editor.pause_recording(true);
121 let finish = match recorder.stop() {121 let finish = match recorder.stop() {
122 Ok(finish) => finish,122 Ok(finish) => finish,
123 Err(detail) => {123 Err(detail) => {
...@@ -126,8 +126,8 @@ impl State {...@@ -126,8 +126,8 @@ impl State {
126 }126 }
127 };127 };
128 let recorded = path.clone();128 let recorded = path.clone();
129 let id = self.view.editor.recording();129 let id = self.pane.view.editor.recording();
130 let title = self.view.editor.page()?.title;130 let title = self.pane.view.editor.page()?.title;
131 let job = std::thread::spawn(move || {131 let job = std::thread::spawn(move || {
132 finish()?;132 finish()?;
133 let recorded =133 let recorded =
...@@ -150,7 +150,7 @@ impl State {...@@ -150,7 +150,7 @@ impl State {
150 let saved = job150 let saved = job
151 .join()151 .join()
152 .unwrap_or_else(|_| Err("Try recording again.".into()));152 .unwrap_or_else(|_| Err("Try recording again.".into()));
153 if self.view.editor.recording().is_none() {153 if self.pane.view.editor.recording().is_none() {
154 return Ok(());154 return Ok(());
155 }155 }
156 let mut file = match saved {156 let mut file = match saved {
...@@ -162,7 +162,7 @@ impl State {...@@ -162,7 +162,7 @@ impl State {
162 };162 };
163 file.preview = platform::file_icon(&path).map(Into::into);163 file.preview = platform::file_icon(&path).map(Into::into);
164 notebook::fs::remove_file(&path)?;164 notebook::fs::remove_file(&path)?;
165 let response = self.view.finish_recording(file)?;165 let response = self.pane.view.finish_recording(file)?;
166 self.respond(response);166 self.respond(response);
167 Ok(())167 Ok(())
168 }168 }
...@@ -225,7 +225,7 @@ impl State {...@@ -225,7 +225,7 @@ impl State {
225 let (enabled, checked) = match transport {225 let (enabled, checked) = match transport {
226 Transport::Pause => (226 Transport::Pause => (
227 recording || playing.is_some(),227 recording || playing.is_some(),
228 Some(self.view.editor.recording_paused() || playing == Some(false)),228 Some(self.pane.view.editor.recording_paused() || playing == Some(false)),
229 ),229 ),
230 Transport::Stop => (recording || playing.is_some(), None),230 Transport::Stop => (recording || playing.is_some(), None),
231 Transport::Skip(_) | Transport::SeekTo => (playing.is_some(), None),231 Transport::Skip(_) | Transport::SeekTo => (playing.is_some(), None),
...@@ -241,9 +241,9 @@ impl State {...@@ -241,9 +241,9 @@ impl State {
241 (Transport::Stop, Media::Recording { .. }) => self.stop_recording(false)?,241 (Transport::Stop, Media::Recording { .. }) => self.stop_recording(false)?,
242 (Transport::Stop, Media::Playing(_)) => self.media = Media::Idle,242 (Transport::Stop, Media::Playing(_)) => self.media = Media::Idle,
243 (Transport::Pause, Media::Recording { recorder, .. }) => {243 (Transport::Pause, Media::Recording { recorder, .. }) => {
244 let paused = !self.view.editor.recording_paused();244 let paused = !self.pane.view.editor.recording_paused();
245 recorder.pause(paused);245 recorder.pause(paused);
246 self.view.editor.pause_recording(paused);246 self.pane.view.editor.pause_recording(paused);
247 }247 }
248 (Transport::Pause, Media::Playing(playback)) => {248 (Transport::Pause, Media::Playing(playback)) => {
249 let player = &mut playback.player;249 let player = &mut playback.player;
...@@ -281,13 +281,13 @@ impl State {...@@ -281,13 +281,13 @@ impl State {
281 let mut played = None;281 let mut played = None;
282 let (clock_text, picture) = match &mut self.media {282 let (clock_text, picture) = match &mut self.media {
283 Media::Idle => {283 Media::Idle => {
284 let response = self.view.set_played(None)?;284 let response = self.pane.view.set_played(None)?;
285 self.respond(response);285 self.respond(response);
286 return Ok(());286 return Ok(());
287 }287 }
288 Media::Recording { video, .. } => {288 Media::Recording { video, .. } => {
289 let at = self.view.editor.recording_ms().unwrap_or(0);289 let at = self.pane.view.editor.recording_ms().unwrap_or(0);
290 let state = if self.view.editor.recording_paused() {290 let state = if self.pane.view.editor.recording_paused() {
291 "Paused"291 "Paused"
292 } else if *video {292 } else if *video {
293 "Recording video"293 "Recording video"
...@@ -311,8 +311,8 @@ impl State {...@@ -311,8 +311,8 @@ impl State {
311 played = playback311 played = playback
312 .recording312 .recording
313 .filter(|_| self.see_playback)313 .filter(|_| self.see_playback)
314 .and_then(|id| self.view.editor.played_note(id, at))314 .and_then(|id| self.pane.view.editor.played_note(id, at))
315 .and_then(|note| canvas::search::paragraph_match(&self.view.editor, note));315 .and_then(|note| canvas::search::paragraph_match(&self.pane.view.editor, note));
316 let mut picture = None;316 let mut picture = None;
317 if let Some(video) = &mut playback.video {317 if let Some(video) = &mut playback.video {
318 let frame = video.movie.frame(at);318 let frame = video.movie.frame(at);
...@@ -344,7 +344,7 @@ impl State {...@@ -344,7 +344,7 @@ impl State {
344 )344 )
345 }345 }
346 };346 };
347 let response = self.view.set_played(played)?;347 let response = self.pane.view.set_played(played)?;
348 self.respond(response);348 self.respond(response);
349 let picture_height = picture349 let picture_height = picture
350 .as_ref()350 .as_ref()
crates/snowbound/src/recycle.rs+5-5
...@@ -67,8 +67,8 @@ impl State {...@@ -67,8 +67,8 @@ impl State {
67 section.close_async().await?;67 section.close_async().await?;
68 continue;68 continue;
69 }69 }
70 let (session, page) = read_session(section, library, path, None)?;70 let (session, space, page) = read_session(section, library, path, None)?;
71 return Ok(Loaded::Section(Box::new(session), page));71 return Ok(Loaded::Section(Box::new(session), space, page));
72 }72 }
73 Err("The Recycle Bin is empty.".into())73 Err("The Recycle Bin is empty.".into())
74 });74 });
...@@ -127,7 +127,7 @@ impl State {...@@ -127,7 +127,7 @@ impl State {
127 let session = self.session.as_ref().ok_or("No section is open")?;127 let session = self.session.as_ref().ok_or("No section is open")?;
128 let replica = Arc::clone(session.section.replica());128 let replica = Arc::clone(session.section.replica());
129 let library = Arc::clone(&session.library);129 let library = Arc::clone(&session.library);
130 let shown = session.space;130 let shown = self.pane.showing.space;
131 let at = (session.pages.iter()).position(|(space, ..)| gone.contains(space));131 let at = (session.pages.iter()).position(|(space, ..)| gone.contains(space));
132 let kept = |space: &ExGuid| !gone.contains(space);132 let kept = |space: &ExGuid| !gone.contains(space);
133 let after = at.and_then(|at| {133 let after = at.and_then(|at| {
...@@ -149,8 +149,8 @@ impl State {...@@ -149,8 +149,8 @@ impl State {
149 .first_section()149 .first_section()
150 .ok_or("The notebook has no sections")?;150 .ok_or("The notebook has no sections")?;
151 let section = library.open_async(&path, notify(proxy)).await?;151 let section = library.open_async(&path, notify(proxy)).await?;
152 let (session, page) = read_session(section, library, path, None)?;152 let (session, space, page) = read_session(section, library, path, None)?;
153 Ok(Loaded::Section(Box::new(session), page))153 Ok(Loaded::Section(Box::new(session), space, page))
154 });154 });
155 Ok(())155 Ok(())
156 }156 }
crates/snowbound/src/save_as.rs+6-2
...@@ -166,11 +166,15 @@ impl State {...@@ -166,11 +166,15 @@ impl State {
166 .filter(|session| Arc::ptr_eq(&session.library, &library))166 .filter(|session| Arc::ptr_eq(&session.library, &library))
167 .map(|session| {167 .map(|session| {
168 let path = session.tabs[session.tab].path.clone();168 let path = session.tabs[session.tab].path.clone();
169 (path, Arc::clone(session.section.replica()), session.space)169 (
170 path,
171 Arc::clone(session.section.replica()),
172 self.pane.showing.space,
173 )
170 });174 });
171 let section = dialog.section.unwrap_or_default();175 let section = dialog.section.unwrap_or_default();
172 let title = match dialog.scope {176 let title = match dialog.scope {
173 Scope::Page => self.view.editor.page()?.title,177 Scope::Page => self.pane.view.editor.page()?.title,
174 Scope::Section => crate::library::section_name(&section, &None),178 Scope::Section => crate::library::section_name(&section, &None),
175 Scope::Notebook => library.name.clone(),179 Scope::Notebook => library.name.clone(),
176 };180 };
crates/snowbound/src/screenshot.rs+9-4
...@@ -59,17 +59,18 @@ impl State {...@@ -59,17 +59,18 @@ impl State {
59 };59 };
60 self.surface.size = size.map(|side| (side * scale).max(1.0) as u32);60 self.surface.size = size.map(|side| (side * scale).max(1.0) as u32);
61 self.renderer.clear_glyph_cache();61 self.renderer.clear_glyph_cache();
62 let response = self.view.scale_factor_changed(scale)?;62 let response = self.pane.view.scale_factor_changed(scale)?;
63 self.respond(response);63 self.respond(response);
64 self.ui.set_focus(None);64 self.ui.set_focus(None);
65 if let Some(title) = self65 if let Some(title) = self
66 .pane
66 .view67 .view
67 .editor68 .editor
68 .outlines()69 .outlines()
69 .iter()70 .iter()
70 .find(|outline| outline.title)71 .find(|outline| outline.title)
71 {72 {
72 self.view.editor.focus_outline(title.id)?;73 self.pane.view.editor.focus_outline(title.id)?;
73 }74 }
74 #[cfg(target_os = "macos")]75 #[cfg(target_os = "macos")]
75 if snow_leopard() {76 if snow_leopard() {
...@@ -99,8 +100,12 @@ impl State {...@@ -99,8 +100,12 @@ impl State {
99 // The page's box sizes the view whose pictures settle.100 // The page's box sizes the view whose pictures settle.
100 self.layout(size, scale)?;101 self.layout(size, scale)?;
101 let paper = self.paper();102 let paper = self.paper();
102 if let Some((scene, _)) = &mut self.view.scene {103 if let Some((scene, _)) = &mut self.pane.view.scene {
103 scene.settle(Some(&self.view.editor), self.view.viewport.scale, paper);104 scene.settle(
105 Some(&self.pane.view.editor),
106 self.pane.view.viewport.scale,
107 paper,
108 );
104 }109 }
105 // Later frames lay out with earlier frames' measurements while colours ease.110 // Later frames lay out with earlier frames' measurements while colours ease.
106 let deadline = Instant::now() + std::time::Duration::from_secs(2);111 let deadline = Instant::now() + std::time::Duration::from_secs(2);
crates/snowbound/src/search.rs+15-15
...@@ -614,26 +614,26 @@ impl State {...@@ -614,26 +614,26 @@ impl State {
614 /// Marks the query's matches on the page; with `select`, selects the match Find on614 /// Marks the query's matches on the page; with `select`, selects the match Find on
615 /// This Page is at, or the first, or the paragraph, when a result opened the page.615 /// This Page is at, or the first, or the paragraph, when a result opened the page.
616 pub(crate) fn refind(&mut self, select: bool) -> Result<(), Box<dyn Error>> {616 pub(crate) fn refind(&mut self, select: bool) -> Result<(), Box<dyn Error>> {
617 self.view.found = page_matches(&self.view.editor, &self.search.shown());617 self.pane.view.found = page_matches(&self.pane.view.editor, &self.search.shown());
618 if !select {618 if !select {
619 return Ok(());619 return Ok(());
620 }620 }
621 let space = self.session.as_ref().map(|session| session.space);621 let space = self.session.as_ref().map(|_| self.pane.showing.space);
622 let target = if self.search.finding {622 let target = if self.search.finding {
623 self.search.current = match self.search.current {623 self.search.current = match self.search.current {
624 Some(at) if at < self.view.found.len() => Some(at),624 Some(at) if at < self.pane.view.found.len() => Some(at),
625 _ => (!self.view.found.is_empty()).then_some(0),625 _ => (!self.pane.view.found.is_empty()).then_some(0),
626 };626 };
627 self.search627 self.search
628 .current628 .current
629 .and_then(|at| self.view.found.get(at).copied())629 .and_then(|at| self.pane.view.found.get(at).copied())
630 } else if let Some((_, paragraph)) =630 } else if let Some((_, paragraph)) =
631 self.search.reveal.filter(|(page, _)| Some(*page) == space)631 self.search.reveal.filter(|(page, _)| Some(*page) == space)
632 {632 {
633 self.search.reveal = None;633 self.search.reveal = None;
634 match paragraph {634 match paragraph {
635 Some(paragraph) => paragraph_match(&self.view.editor, paragraph),635 Some(paragraph) => paragraph_match(&self.pane.view.editor, paragraph),
636 None => self.view.found.first().copied(),636 None => self.pane.view.found.first().copied(),
637 }637 }
638 } else {638 } else {
639 None639 None
...@@ -645,9 +645,9 @@ impl State {...@@ -645,9 +645,9 @@ impl State {
645 let Some((outline, selection)) = target else {645 let Some((outline, selection)) = target else {
646 return Ok(());646 return Ok(());
647 };647 };
648 self.view.editor.focus_outline(outline)?;648 self.pane.view.editor.focus_outline(outline)?;
649 self.view.editor.select(selection)?;649 self.pane.view.editor.select(selection)?;
650 let response = self.view.focus_text()?;650 let response = self.pane.view.focus_text()?;
651 self.respond(response);651 self.respond(response);
652 Ok(())652 Ok(())
653 }653 }
...@@ -686,7 +686,7 @@ impl State {...@@ -686,7 +686,7 @@ impl State {
686 if !matches!(self.search.task_pane, Some(TaskPane::Search { .. })) {686 if !matches!(self.search.task_pane, Some(TaskPane::Search { .. })) {
687 self.search.query.clear();687 self.search.query.clear();
688 self.search.scope = self.search.default;688 self.search.scope = self.search.default;
689 self.view.found.clear();689 self.pane.view.found.clear();
690 }690 }
691 }691 }
692692
...@@ -711,7 +711,7 @@ impl State {...@@ -711,7 +711,7 @@ impl State {
711 let open = self711 let open = self
712 .session712 .session
713 .as_ref()713 .as_ref()
714 .map(|session| (session.key(), session.space));714 .map(|session| (session.key(), self.pane.showing.space));
715 match open {715 match open {
716 Some((key, shown)) if key == section && shown == space => self.refind(true)?,716 Some((key, shown)) if key == section && shown == space => self.refind(true)?,
717 Some((key, _)) if key == section => self.commands.push(Command::OpenPage(space)),717 Some((key, _)) if key == section => self.commands.push(Command::OpenPage(space)),
...@@ -1130,7 +1130,7 @@ impl State {...@@ -1130,7 +1130,7 @@ impl State {
1130 &[NamedKey::Enter, NamedKey::F3, NamedKey::Escape],1130 &[NamedKey::Enter, NamedKey::F3, NamedKey::Escape],
1131 );1131 );
1132 let before = self.search.query.clone();1132 let before = self.search.query.clone();
1133 let count = self.view.found.len();1133 let count = self.pane.view.found.len();
1134 let label = if self.search.query.trim().is_empty() {1134 let label = if self.search.query.trim().is_empty() {
1135 "Find on page".to_owned()1135 "Find on page".to_owned()
1136 } else if count == 0 {1136 } else if count == 0 {
...@@ -1211,7 +1211,7 @@ impl State {...@@ -1211,7 +1211,7 @@ impl State {
1211 self.end_search();1211 self.end_search();
1212 return Ok(());1212 return Ok(());
1213 }1213 }
1214 let shift = self.view.modifiers().shift;1214 let shift = self.pane.view.modifiers().shift;
1215 for key in keys {1215 for key in keys {
1216 match key {1216 match key {
1217 NamedKey::Escape => {1217 NamedKey::Escape => {
...@@ -1234,7 +1234,7 @@ impl State {...@@ -1234,7 +1234,7 @@ impl State {
1234 (at + count - 1) % count1234 (at + count - 1) % count
1235 };1235 };
1236 self.search.current = Some(next);1236 self.search.current = Some(next);
1237 self.select(self.view.found.get(next).copied())?;1237 self.select(self.pane.view.found.get(next).copied())?;
1238 }1238 }
1239 Ok(())1239 Ok(())
1240 }1240 }
crates/snowbound/src/settings.rs+4-4
...@@ -307,18 +307,18 @@ impl crate::State {...@@ -307,18 +307,18 @@ impl crate::State {
307 color_scheme: self.color_scheme,307 color_scheme: self.color_scheme,
308 light_pages: self.light_pages,308 light_pages: self.light_pages,
309 hide_spelling: self.hide_spelling,309 hide_spelling: self.hide_spelling,
310 ignore_markdown: self.view.editor.markdown.is_none(),310 ignore_markdown: self.pane.view.editor.markdown.is_none(),
311 search_scope: self.search.default,311 search_scope: self.search.default,
312 tags: (self.tags != canvas::editor::NoteTag::defaults()).then(|| self.tags.clone()),312 tags: (self.tags != canvas::editor::NoteTag::defaults()).then(|| self.tags.clone()),
313 manual_updates: !self.updates.automatic(),313 manual_updates: !self.updates.automatic(),
314 ignore_pen_pressure: !self.pen_pressure,314 ignore_pen_pressure: !self.pen_pressure,
315 ignore_grid: !self.view.snap_to_grid,315 ignore_grid: !self.pane.view.snap_to_grid,
316 default_font: self.view.editor.default_font.clone(),316 default_font: self.pane.view.editor.default_font.clone(),
317 page_tabs_left: self.page_tabs_left,317 page_tabs_left: self.page_tabs_left,
318 navigation_bar_right: self.navigation_bar_right,318 navigation_bar_right: self.navigation_bar_right,
319 servers: self.servers.clone(),319 servers: self.servers.clone(),
320 keys: crate::commands::Keymap::current().saved(),320 keys: crate::commands::Keymap::current().saved(),
321 recent: self.trail.recent.clone(),321 recent: self.recent.clone(),
322 passwords: self.passwords,322 passwords: self.passwords,
323 notebook_theme: NotebookTheme(self.notebook_theme.clone()),323 notebook_theme: NotebookTheme(self.notebook_theme.clone()),
324 live: self.live_options.clone(),324 live: self.live_options.clone(),
crates/snowbound/src/sidebar.rs+4-4
...@@ -941,7 +941,7 @@ impl crate::State {...@@ -941,7 +941,7 @@ impl crate::State {
941 },941 },
942 );942 );
943 crate::name(&mut self.ui, rows_id, "My Notebooks");943 crate::name(&mut self.ui, rows_id, "My Notebooks");
944 let shown = match (&self.session, &self.locked) {944 let shown = match (&self.session, &self.pane.locked) {
945 (Some(session), _) => Some((&session.library, session.tabs[session.tab].path.as_str())),945 (Some(session), _) => Some((&session.library, session.tabs[session.tab].path.as_str())),
946 (None, Some(locked)) => Some((&locked.library, locked.path.as_str())),946 (None, Some(locked)) => Some((&locked.library, locked.path.as_str())),
947 (None, None) => None,947 (None, None) => None,
...@@ -1444,7 +1444,7 @@ impl crate::State {...@@ -1444,7 +1444,7 @@ impl crate::State {
1444 read: impl FnOnce(&str, &std::path::Path) -> Result<Library, String> + Send + 'static,1444 read: impl FnOnce(&str, &std::path::Path) -> Result<Library, String> + Send + 'static,
1445 ) {1445 ) {
1446 let section = section.or_else(|| {1446 let section = section.or_else(|| {
1447 (self.trail.recent.iter())1447 (self.recent.iter())
1448 .find(|place| place.notebook == location)1448 .find(|place| place.notebook == location)
1449 .map(|place| place.section.clone())1449 .map(|place| place.section.clone())
1450 });1450 });
...@@ -1477,8 +1477,8 @@ impl crate::State {...@@ -1477,8 +1477,8 @@ impl crate::State {
1477 return Err("This folder holds no notebook sections.".into());1477 return Err("This folder holds no notebook sections.".into());
1478 };1478 };
1479 let section = library.open_async(&path, notify).await?;1479 let section = library.open_async(&path, notify).await?;
1480 let (session, page) = crate::read_session(section, library, path, left)?;1480 let (session, space, page) = crate::read_session(section, library, path, left)?;
1481 Ok(crate::Loaded::Section(Box::new(session), page))1481 Ok(crate::Loaded::Section(Box::new(session), space, page))
1482 });1482 });
1483 }1483 }
14841484
crates/snowbound/src/task_pane.rs+4-4
...@@ -445,7 +445,7 @@ impl State {...@@ -445,7 +445,7 @@ impl State {
445445
446 /// Shows the next marked word in the Spelling pane; past the last, the check is complete.446 /// Shows the next marked word in the Spelling pane; past the last, the check is complete.
447 fn next_word(&mut self) {447 fn next_word(&mut self) {
448 match self.view.next_correction() {448 match self.pane.view.next_correction() {
449 Ok(Some((response, correction))) => {449 Ok(Some((response, correction))) => {
450 self.respond(response);450 self.respond(response);
451 self.correction = Some(correction);451 self.correction = Some(correction);
...@@ -552,7 +552,7 @@ impl State {...@@ -552,7 +552,7 @@ impl State {
552 else {552 else {
553 return;553 return;
554 };554 };
555 let spelling = self.view.spelling.clone();555 let spelling = self.pane.view.spelling.clone();
556 let text = match (correction.repeated, pressed) {556 let text = match (correction.repeated, pressed) {
557 (_, _) if change => correction.suggestions.get(chosen).map(String::as_str),557 (_, _) if change => correction.suggestions.get(chosen).map(String::as_str),
558 (true, [true, _]) => Some(""),558 (true, [true, _]) => Some(""),
...@@ -566,7 +566,7 @@ impl State {...@@ -566,7 +566,7 @@ impl State {
566 }566 }
567 };567 };
568 if let Some(text) = text {568 if let Some(text) = text {
569 match self.view.correct(&correction, text) {569 match self.pane.view.correct(&correction, text) {
570 Ok(response) => self.respond(response),570 Ok(response) => self.respond(response),
571 Err(error) => eprintln!("{error}"),571 Err(error) => eprintln!("{error}"),
572 }572 }
...@@ -936,7 +936,7 @@ impl State {...@@ -936,7 +936,7 @@ impl State {
936 let pages = &session.pages;936 let pages = &session.pages;
937 let at = pages937 let at = pages
938 .iter()938 .iter()
939 .position(|(space, ..)| *space == session.space)?;939 .position(|(space, ..)| *space == self.pane.showing.space)?;
940 let base = pages.iter().map(|(.., level)| *level).min()?;940 let base = pages.iter().map(|(.., level)| *level).min()?;
941 let start = pages[..=at]941 let start = pages[..=at]
942 .iter()942 .iter()
crates/snowbound/src/templates.rs+4-4
...@@ -387,8 +387,8 @@ impl crate::State {...@@ -387,8 +387,8 @@ impl crate::State {
387 let Some(session) = &self.session else {387 let Some(session) = &self.session else {
388 return;388 return;
389 };389 };
390 let space = session.space;390 let space = self.pane.showing.space;
391 let read_only = session.read_only();391 let read_only = session.read_only(&self.pane.showing);
392 let Some(rect) = self.ui.laid_out(crate::page()) else {392 let Some(rect) = self.ui.laid_out(crate::page()) else {
393 return;393 return;
394 };394 };
...@@ -409,7 +409,7 @@ impl crate::State {...@@ -409,7 +409,7 @@ impl crate::State {
409 ) {409 ) {
410 self.choose(crate::commands::Choice::Art(Some(name)));410 self.choose(crate::commands::Choice::Art(Some(name)));
411 }411 }
412 let editor = &self.view.editor;412 let editor = &self.pane.view.editor;
413 let blank = editor413 let blank = editor
414 .visible_outlines()414 .visible_outlines()
415 .chain(editor.caret_outline())415 .chain(editor.caret_outline())
...@@ -422,7 +422,7 @@ impl crate::State {...@@ -422,7 +422,7 @@ impl crate::State {
422 return;422 return;
423 };423 };
424 let (color, rule_lines) = (editor.page_color(), editor.rule_lines());424 let (color, rule_lines) = (editor.page_color(), editor.rule_lines());
425 let viewport = self.view.viewport;425 let viewport = self.pane.view.viewport;
426 let scale = self.ui.scale();426 let scale = self.ui.scale();
427 let position = [427 let position = [
428 (viewport.origin[0] + start[0] * viewport.scale) / scale,428 (viewport.origin[0] + start[0] * viewport.scale) / scale,
crates/snowbound/src/themes.rs+6-6
...@@ -120,7 +120,7 @@ impl State {...@@ -120,7 +120,7 @@ impl State {
120 let library = &session.library;120 let library = &session.library;
121 library.catalog()?;121 library.catalog()?;
122 let target = match scope {122 let target = match scope {
123 Scope::Page => stored::Scope::page(self.view.editor.identity()?),123 Scope::Page => stored::Scope::page(self.pane.view.editor.identity()?),
124 Scope::Section => {124 Scope::Section => {
125 stored::Scope::section(library.section_identity(&session.tabs[session.tab].path)?)125 stored::Scope::section(library.section_identity(&session.tabs[session.tab].path)?)
126 }126 }
...@@ -144,7 +144,7 @@ impl State {...@@ -144,7 +144,7 @@ impl State {
144 session144 session
145 .library145 .library
146 .themes()146 .themes()
147 .effective(section, self.view.editor.identity())147 .effective(section, self.pane.view.editor.identity())
148 }148 }
149149
150 /// The styles the gallery offers: the page's theme's, else OneNote 2010's.150 /// The styles the gallery offers: the page's theme's, else OneNote 2010's.
...@@ -167,17 +167,17 @@ impl State {...@@ -167,17 +167,17 @@ impl State {
167 let sheet = (theme.as_ref())167 let sheet = (theme.as_ref())
168 .map(|theme| theme.sheet(self.section_color()))168 .map(|theme| theme.sheet(self.section_color()))
169 .unwrap_or_default();169 .unwrap_or_default();
170 if !force && self.view.editor.styles == sheet {170 if !force && self.pane.view.editor.styles == sheet {
171 return Ok(());171 return Ok(());
172 }172 }
173 self.view.editor.styles = sheet.clone();173 self.pane.view.editor.styles = sheet.clone();
174 let Some(session) = &self.session else {174 let Some(session) = &self.session else {
175 return Ok(());175 return Ok(());
176 };176 };
177 if theme.is_none() || session.read_only() {177 if theme.is_none() || session.read_only(&self.pane.showing) {
178 return Ok(());178 return Ok(());
179 }179 }
180 let page = session.section.page(session.space)?;180 let page = session.section.page(self.pane.showing.space)?;
181 let ops = onestore::op::restyle(&page, &sheet)?;181 let ops = onestore::op::restyle(&page, &sheet)?;
182 if ops.is_empty() {182 if ops.is_empty() {
183 return Ok(());183 return Ok(());
crates/snowbound/src/undo.rs+10-9
...@@ -958,13 +958,13 @@ impl State {...@@ -958,13 +958,13 @@ impl State {
958958
959 /// Whether no action, page or command is on its way, which Undo and Redo wait for.959 /// Whether no action, page or command is on its way, which Undo and Redo wait for.
960 pub(crate) fn settled(&self) -> bool {960 pub(crate) fn settled(&self) -> bool {
961 self.undo.busy == 0 && self.switching.is_none() && self.commands.is_empty()961 self.undo.busy == 0 && self.pane.switching.is_none() && self.commands.is_empty()
962 }962 }
963963
964 /// Whether Undo, or `redo` Redo, has something to take, or may once what is on its way964 /// Whether Undo, or `redo` Redo, has something to take, or may once what is on its way
965 /// lands.965 /// lands.
966 pub(crate) fn can_step(&self, redo: bool) -> bool {966 pub(crate) fn can_step(&self, redo: bool) -> bool {
967 let editor = &self.view.editor;967 let editor = &self.pane.view.editor;
968 !self.settled()968 !self.settled()
969 || (if redo {969 || (if redo {
970 editor.can_redo()970 editor.can_redo()
...@@ -998,9 +998,9 @@ impl State {...@@ -998,9 +998,9 @@ impl State {
998 /// user is, whichever came last.998 /// user is, whichever came last.
999 fn take(&mut self, redo: bool) -> Result<(), Box<dyn Error>> {999 fn take(&mut self, redo: bool) -> Result<(), Box<dyn Error>> {
1000 self.persist()?;1000 self.persist()?;
1001 let depth = self.view.editor.history_depth();1001 let depth = self.pane.view.editor.history_depth();
1002 // A page kept in no notebook has only its own history.1002 // A page kept in no notebook has only its own history.
1003 let shown = self.session.as_ref().map(|session| session.space);1003 let shown = self.session.as_ref().map(|_| self.pane.showing.space);
1004 let next = match (self.here(), shown) {1004 let next = match (self.here(), shown) {
1005 (Some(here), Some(page)) => self.undo.next(redo, page, depth, &here),1005 (Some(here), Some(page)) => self.undo.next(redo, page, depth, &here),
1006 _ => Some(Next::Edit),1006 _ => Some(Next::Edit),
...@@ -1008,10 +1008,10 @@ impl State {...@@ -1008,10 +1008,10 @@ impl State {
1008 match next {1008 match next {
1009 None => {}1009 None => {}
1010 Some(Next::Edit) => {1010 Some(Next::Edit) => {
1011 let response = self.view.undo(redo)?;1011 let response = self.pane.view.undo(redo)?;
1012 self.respond(response);1012 self.respond(response);
1013 if let Some(page) = shown1013 if let Some(page) = shown
1014 && self.view.editor.history_depth() != depth1014 && self.pane.view.editor.history_depth() != depth
1015 {1015 {
1016 self.undo.stepped(page, redo);1016 self.undo.stepped(page, redo);
1017 }1017 }
...@@ -1117,11 +1117,11 @@ impl State {...@@ -1117,11 +1117,11 @@ impl State {
1117 author: self.author.clone(),1117 author: self.author.clone(),
1118 fresh: self.dated_page(None, "")?,1118 fresh: self.dated_page(None, "")?,
1119 };1119 };
1120 let shown = session.space;1120 let shown = self.pane.showing.space;
1121 let original = Arc::clone(&site.library);1121 let original = Arc::clone(&site.library);
1122 let proxy = self.proxy.clone();1122 let proxy = self.proxy.clone();
1123 self.undo.busy += 1;1123 self.undo.busy += 1;
1124 self.switching = Some((None, web_time::Instant::now()));1124 self.pane.switching = Some((None, web_time::Instant::now()));
1125 self.load_async(move || async move {1125 self.load_async(move || async move {
1126 let applied = site.change(change, shown).await;1126 let applied = site.change(change, shown).await;
1127 let show = match &applied {1127 let show = match &applied {
...@@ -1150,11 +1150,12 @@ impl State {...@@ -1150,11 +1150,12 @@ impl State {
1150 } else {1150 } else {
1151 state.list(&library);1151 state.list(&library);
1152 if state1152 if state
1153 .pane
1153 .sectionless1154 .sectionless
1154 .as_ref()1155 .as_ref()
1155 .is_some_and(|listed| lineage(listed))1156 .is_some_and(|listed| lineage(listed))
1156 {1157 {
1157 state.sectionless = Some(library);1158 state.pane.sectionless = Some(library);
1158 }1159 }
1159 }1160 }
1160 }1161 }
crates/snowbound/src/unread.rs+8-3
...@@ -274,8 +274,13 @@ impl State {...@@ -274,8 +274,13 @@ impl State {
274 let viewing = self.session.as_ref().and_then(|session| {274 let viewing = self.session.as_ref().and_then(|session| {
275 let path = &session.tabs[session.tab].path;275 let path = &session.tabs[session.tab].path;
276 let section = session.library.section_identity(path)?;276 let section = session.library.section_identity(path)?;
277 (session.version.is_none() && !recycle::binned(path))277 (self.pane.showing.version.is_none() && !recycle::binned(path)).then(|| {
278 .then(|| (session.library.location.clone(), section, session.space))278 (
279 session.library.location.clone(),
280 section,
281 self.pane.showing.space,
282 )
283 })
279 });284 });
280 let same = |(notebook, section, space, _): &(String, [u8; 16], ExGuid, bool)| {285 let same = |(notebook, section, space, _): &(String, [u8; 16], ExGuid, bool)| {
281 viewing.as_ref() == Some(&(notebook.clone(), *section, *space))286 viewing.as_ref() == Some(&(notebook.clone(), *section, *space))
...@@ -418,7 +423,7 @@ impl State {...@@ -418,7 +423,7 @@ impl State {
418 let at = session423 let at = session
419 .pages424 .pages
420 .iter()425 .iter()
421 .position(|(space, ..)| *space == session.space);426 .position(|(space, ..)| *space == self.pane.showing.space);
422 let after = (session.pages.iter().skip(at.map_or(0, |at| at + 1)))427 let after = (session.pages.iter().skip(at.map_or(0, |at| at + 1)))
423 .find(|(space, ..)| unread.contains(space));428 .find(|(space, ..)| unread.contains(space));
424 if let Some((space, ..)) = after {429 if let Some((space, ..)) = after {
crates/snowbound/src/web.rs+10-10
...@@ -999,7 +999,7 @@ pub async fn start(...@@ -999,7 +999,7 @@ pub async fn start(
999 if let Some(resume) = resumed().and_then(|resume| serde_json::from_str::<Resume>(&resume).ok())999 if let Some(resume) = resumed().and_then(|resume| serde_json::from_str::<Resume>(&resume).ok())
1000 {1000 {
1001 for (axis, offset) in resume.offset.into_iter().enumerate() {1001 for (axis, offset) in resume.offset.into_iter().enumerate() {
1002 match state.view.scroll_to(axis, offset) {1002 match state.pane.view.scroll_to(axis, offset) {
1003 Ok(response) => state.respond(response),1003 Ok(response) => state.respond(response),
1004 Err(error) => report(error),1004 Err(error) => report(error),
1005 }1005 }
...@@ -1571,13 +1571,13 @@ fn turn(state: &mut State) -> Result<(), Box<dyn Error>> {...@@ -1571,13 +1571,13 @@ fn turn(state: &mut State) -> Result<(), Box<dyn Error>> {
1571 state.frame()?;1571 state.frame()?;
1572 remember_look(state);1572 remember_look(state);
1573 }1573 }
1574 let space = state.session.as_ref().map(|session| session.space);1574 let space = state.session.as_ref().map(|_| state.pane.showing.space);
1575 if TYPED.take() || LOOKED.replace(space) != space {1575 if TYPED.take() || LOOKED.replace(space) != space {
1576 fetch_fallbacks(&crate::page_text(&state.view.editor));1576 fetch_fallbacks(&crate::page_text(&state.pane.view.editor));
1577 }1577 }
1578 park_input(state);1578 park_input(state);
1579 let now = Instant::now();1579 let now = Instant::now();
1580 let (repaint, blink) = state.view.blink(now);1580 let (repaint, blink) = state.pane.view.blink(now);
1581 let wake = state.ui.wake_at();1581 let wake = state.ui.wake_at();
1582 if repaint || wake.is_some_and(|wake| wake <= now) {1582 if repaint || wake.is_some_and(|wake| wake <= now) {
1583 state.window.request_redraw();1583 state.window.request_redraw();
...@@ -1709,7 +1709,7 @@ pub fn update_now(state: &mut State) {...@@ -1709,7 +1709,7 @@ pub fn update_now(state: &mut State) {
1709 return RELOADING.set(false);1709 return RELOADING.set(false);
1710 };1710 };
1711 let resume = Resume {1711 let resume = Resume {
1712 offset: state.view.viewport.origin.map(|origin| -origin),1712 offset: state.pane.view.viewport.origin.map(|origin| -origin),
1713 build,1713 build,
1714 };1714 };
1715 let reloading = reload_into(&serde_json::to_string(&resume).unwrap_or_default());1715 let reloading = reload_into(&serde_json::to_string(&resume).unwrap_or_default());
...@@ -1737,8 +1737,8 @@ fn follow_updates(state: &mut State, now: Instant) -> Option<Instant> {...@@ -1737,8 +1737,8 @@ fn follow_updates(state: &mut State, now: Instant) -> Option<Instant> {
1737 }1737 }
1738 let idle = LAST_INPUT.get().map_or(now, |last| last + IDLE);1738 let idle = LAST_INPUT.get().map_or(now, |last| last + IDLE);
1739 let quiet = !state.ui.popups_open()1739 let quiet = !state.ui.popups_open()
1740 && state.opening.is_none()1740 && state.pane.opening.is_none()
1741 && state.loading == 01741 && state.pane.loading == 0
1742 && matches!(state.media, crate::recording::Media::Idle);1742 && matches!(state.media, crate::recording::Media::Idle);
1743 if update_ready() && (returned || idle <= now) && quiet {1743 if update_ready() && (returned || idle <= now) && quiet {
1744 update_now(state);1744 update_now(state);
...@@ -1783,7 +1783,7 @@ pub fn font_arrived(name: String, bytes: Vec<u8>) {...@@ -1783,7 +1783,7 @@ pub fn font_arrived(name: String, bytes: Vec<u8>) {
1783 families1783 families
1784 };1784 };
1785 send(UserEvent::Then(Box::new(move |state| {1785 send(UserEvent::Then(Box::new(move |state| {
1786 let families = register(&mut state.view.engine.fonts.collection, face.clone());1786 let families = register(&mut state.pane.view.engine.fonts.collection, face.clone());
1787 if let Ok(mut layouts) = state.layouts.lock() {1787 if let Ok(mut layouts) = state.layouts.lock() {
1788 register(&mut layouts.fonts.collection, face.clone());1788 register(&mut layouts.fonts.collection, face.clone());
1789 }1789 }
...@@ -1792,7 +1792,7 @@ pub fn font_arrived(name: String, bytes: Vec<u8>) {...@@ -1792,7 +1792,7 @@ pub fn font_arrived(name: String, bytes: Vec<u8>) {
1792 }1792 }
1793 state.ui.fall_back_to(&families);1793 state.ui.fall_back_to(&families);
1794 state.renderer.clear_glyph_cache();1794 state.renderer.clear_glyph_cache();
1795 let response = state.view.relayout()?;1795 let response = state.pane.view.relayout()?;
1796 state.respond(response);1796 state.respond(response);
1797 state.window.request_redraw();1797 state.window.request_redraw();
1798 Ok(())1798 Ok(())
...@@ -1816,7 +1816,7 @@ pub fn dictionary_arrived(name: String, affix: String, words: String) {...@@ -1816,7 +1816,7 @@ pub fn dictionary_arrived(name: String, affix: String, words: String) {
1816/// Parks the text area while the page takes no text, keeping it writable for the1816/// Parks the text area while the page takes no text, keeping it writable for the
1817/// interface's fields.1817/// interface's fields.
1818fn park_input(state: &State) {1818fn park_input(state: &State) {
1819 if state.ui.focused() != Some(page()) || !state.view.accepts_text() {1819 if state.ui.focused() != Some(page()) || !state.pane.view.accepts_text() {
1820 place_input(0.0, 0.0, 1.0, state.ui.focused().is_some());1820 place_input(0.0, 0.0, 1.0, state.ui.focused().is_some());
1821 }1821 }
1822}1822}