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