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

refactor: hold the page view in a Pane

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

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

crates/snowbound/src/attachment.rs+13-6
......@@ -10,10 +10,14 @@ use std::{
1010impl State {
1111 /// Inserts a file at the caret or window point `at`, as a picture when requested.
1212 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 {
1418 return;
1519 }
16 let loading = self.loading;
20 let loading = self.pane.loading;
1721 let reply = self.reply(
1822 move |state, (path, read): (PathBuf, std::io::Result<Vec<u8>>)| {
1923 let bytes = match read {
......@@ -27,7 +31,7 @@ impl State {
2731 return Ok(());
2832 }
2933 };
30 if state.loading != loading {
34 if state.pane.loading != loading {
3135 platform::alert(
3236 "File wasn't inserted",
3337 "Return to the page and insert the file again.",
......@@ -37,7 +41,7 @@ impl State {
3741 if state
3842 .session
3943 .as_ref()
40 .is_some_and(crate::Session::read_only)
44 .is_some_and(|session| session.read_only(&state.pane.showing))
4145 {
4246 return Ok(());
4347 }
......@@ -60,8 +64,11 @@ impl State {
6064 tags: Vec::new(),
6165 };
6266 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)?,
6572 };
6673 state.respond(response);
6774 Ok(())
crates/snowbound/src/commands.rs+44-42
......@@ -1070,7 +1070,7 @@ impl State {
10701070 }
10711071
10721072 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()
10741074 }
10751075
10761076 /// Queues `choice` to run after this frame's input, as a key or a click on it does.
......@@ -1127,7 +1127,7 @@ impl State {
11271127
11281128 pub(crate) fn status(&self, choice: &Choice, format: &FormatState) -> Status {
11291129 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();
11311131 let modal = self.options.is_some()
11321132 || self.password.is_some()
11331133 || self.themes.is_some()
......@@ -1138,13 +1138,14 @@ impl State {
11381138 #[cfg(any(windows, target_os = "linux"))]
11391139 let modal = modal || self.prompt.strong_count() != 0;
11401140 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));
11421143 // 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();
11441145 let text = writable && typing;
11451146 let tagged = writable
11461147 && matches!(
1147 self.view.object_focus(),
1148 self.pane.view.object_focus(),
11481149 Some(
11491150 canvas::interaction::ObjectFocus::Image(_)
11501151 | canvas::interaction::ObjectFocus::File(_)
......@@ -1152,12 +1153,12 @@ impl State {
11521153 );
11531154 // Palette commands act after its search field returns focus to the editor.
11541155 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;
11561157 let selected = page
11571158 && !field
11581159 && (typing && anchor != focus
11591160 || matches!(
1160 self.view.object_focus(),
1161 self.pane.view.object_focus(),
11611162 Some(canvas::interaction::ObjectFocus::Image(_))
11621163 ));
11631164 let enabled = |enabled| Status {
......@@ -1178,7 +1179,7 @@ impl State {
11781179 Choice::Pen(_) => Id::Pen,
11791180 Choice::Symbol(_) => Id::Symbol,
11801181 };
1181 let tool = self.view.tool();
1182 let tool = self.pane.view.tool();
11821183 match id {
11831184 Id::Settings
11841185 | Id::NewNotebook
......@@ -1267,8 +1268,8 @@ impl State {
12671268 checked: Some(false),
12681269 },
12691270 |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)),
12721273 },
12731274 ),
12741275 Id::FullPageView => Status {
......@@ -1312,7 +1313,7 @@ impl State {
13121313 },
13131314 Id::SnapToGrid => Status {
13141315 enabled: !modal,
1315 checked: Some(self.view.snap_to_grid),
1316 checked: Some(self.pane.view.snap_to_grid),
13161317 },
13171318 Id::HideSpelling => Status {
13181319 enabled: !modal && self.spelling.is_some(),
......@@ -1320,9 +1321,9 @@ impl State {
13201321 },
13211322 Id::MarkdownShortcuts => Status {
13221323 enabled: !modal,
1323 checked: Some(self.view.editor.markdown.is_some()),
1324 checked: Some(self.pane.view.editor.markdown.is_some()),
13241325 },
1325 Id::Spelling => enabled(text && self.view.spelling.is_some()),
1326 Id::Spelling => enabled(text && self.pane.view.spelling.is_some()),
13261327 Id::FormatPainter => Status {
13271328 enabled: text,
13281329 checked: Some(self.painter.is_some()),
......@@ -1343,7 +1344,7 @@ impl State {
13431344 Id::Transport(transport) => self.transport_status(transport),
13441345 Id::InsertSpace => Status {
13451346 enabled: writable,
1346 checked: Some(self.view.inserting_space()),
1347 checked: Some(self.pane.view.inserting_space()),
13471348 },
13481349 Id::SelectType => Status {
13491350 enabled: page,
......@@ -1427,7 +1428,7 @@ impl State {
14271428 if matches!(formatting, Formatting::Font(_)) || self.toolbar != kept {
14281429 self.save_settings();
14291430 }
1430 let response = self.view.format(formatting)?;
1431 let response = self.pane.view.format(formatting)?;
14311432 self.respond(response);
14321433 Ok(())
14331434 }
......@@ -1442,7 +1443,7 @@ impl State {
14421443 self.session
14431444 .as_ref()
14441445 .map(|session| &session.library)
1445 .or(self.sectionless.as_ref())
1446 .or(self.pane.sectionless.as_ref())
14461447 }
14471448
14481449 /// Runs `choice` where it applies now.
......@@ -1463,22 +1464,22 @@ impl State {
14631464 Choice::Color(color) => return format(self, Formatting::Color(color)),
14641465 Choice::List(style) => return format(self, Formatting::List(style)),
14651466 Choice::Table([rows, columns]) => {
1466 let response = self.view.insert_table(rows, columns)?;
1467 let response = self.pane.view.insert_table(rows, columns)?;
14671468 self.respond(response);
14681469 return Ok(());
14691470 }
14701471 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);
14721473 }
14731474 Choice::Art(name) => {
14741475 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;
14761477 state.paper_page(editor.page_color(), editor.rule_lines(), Some(art))
14771478 });
14781479 }
14791480 Choice::RuleLines(lines) => {
14801481 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);
14821483 }
14831484 Choice::Pen(place) => {
14841485 self.toolbar.pen = place;
......@@ -1495,7 +1496,7 @@ impl State {
14951496 recent.insert(0, symbol);
14961497 recent.truncate(crate::symbol::GALLERY);
14971498 self.save_settings();
1498 let response = self.view.insert_symbol(symbol)?;
1499 let response = self.pane.view.insert_symbol(symbol)?;
14991500 self.respond(response);
15001501 return Ok(());
15011502 }
......@@ -1544,18 +1545,19 @@ impl State {
15441545 title: String::new(),
15451546 },
15461547 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),
15481549 title: String::new(),
15491550 },
15501551 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")?;
15521553 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),
15551556 }
15561557 }
15571558 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;
15591561 let link = self.page_link(space, None)?;
15601562 self.clipboard.set_text(link)?;
15611563 return Ok(());
......@@ -1604,7 +1606,7 @@ impl State {
16041606 }
16051607 Id::Undo | Id::Redo => return self.step(id == Id::Redo),
16061608 Id::Cut | Id::Copy => {
1607 let response = self.view.copy(id == Id::Cut)?;
1609 let response = self.pane.view.copy(id == Id::Cut)?;
16081610 self.respond(response);
16091611 return Ok(());
16101612 }
......@@ -1624,7 +1626,7 @@ impl State {
16241626 Id::FormatPainter => {
16251627 self.painter = match self.painter {
16261628 Some(_) => None,
1627 None => Some(self.view.editor.painted_format()?),
1629 None => Some(self.pane.view.editor.painted_format()?),
16281630 };
16291631 return Ok(());
16301632 }
......@@ -1632,7 +1634,7 @@ impl State {
16321634 match field {
16331635 Some(field) => self.ui.focus_all(field),
16341636 None => {
1635 let response = self.view.widen_selection()?;
1637 let response = self.pane.view.widen_selection()?;
16361638 self.respond(response);
16371639 }
16381640 }
......@@ -1668,11 +1670,11 @@ impl State {
16681670 }
16691671 Id::ZoomIn | Id::ZoomOut | Id::ActualSize => {
16701672 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,
16731675 _ => 1.0,
16741676 };
1675 let response = self.view.set_zoom(zoom)?;
1677 let response = self.pane.view.set_zoom(zoom)?;
16761678 self.respond(response);
16771679 return Ok(());
16781680 }
......@@ -1725,12 +1727,12 @@ impl State {
17251727 return Ok(());
17261728 }
17271729 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;
17291731 self.save_settings();
17301732 return Ok(());
17311733 }
17321734 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());
17341736 self.save_settings();
17351737 return Ok(());
17361738 }
......@@ -1743,7 +1745,7 @@ impl State {
17431745 Id::RecordAudio | Id::RecordVideo => return self.record(id == Id::RecordVideo),
17441746 Id::Transport(transport) => return self.run_transport(transport),
17451747 Id::InsertSpace => {
1746 let response = self.view.insert_space();
1748 let response = self.pane.view.insert_space();
17471749 self.respond(response);
17481750 return Ok(());
17491751 }
......@@ -1756,7 +1758,7 @@ impl State {
17561758 Id::Shape(kind) => Tool::Shape(kind, pens[0].shape()),
17571759 _ => Tool::Select,
17581760 };
1759 let response = self.view.set_tool(tool);
1761 let response = self.pane.view.set_tool(tool);
17601762 self.respond(response);
17611763 return Ok(());
17621764 }
......@@ -1765,7 +1767,7 @@ impl State {
17651767 return Ok(());
17661768 }
17671769 Id::Equation => {
1768 let response = self.view.insert_equation()?;
1770 let response = self.pane.view.insert_equation()?;
17691771 self.respond(response);
17701772 return Ok(());
17711773 }
......@@ -1779,7 +1781,7 @@ impl State {
17791781 Id::Time => time,
17801782 _ => format!("{date} {time}"),
17811783 };
1782 let response = self.view.insert_text(format!("{text} "))?;
1784 let response = self.pane.view.insert_text(format!("{text} "))?;
17831785 self.respond(response);
17841786 return Ok(());
17851787 }
......@@ -1803,7 +1805,7 @@ impl State {
18031805 // Clear Formatting at a caret makes the paragraph Normal, as OneNote 2010's
18041806 // Ctrl+Shift+N does; over a selection it clears the characters' formatting.
18051807 Id::ClearFormatting => {
1806 let [anchor, focus] = self.view.editor.selection().positions;
1808 let [anchor, focus] = self.pane.view.editor.selection().positions;
18071809 if anchor == focus {
18081810 return format(self, Formatting::Style(self.gallery_style("p")));
18091811 }
......@@ -2096,9 +2098,9 @@ mod tests {
20962098 for effect in [
20972099 "self.respond(",
20982100 "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",
21022104 "self.open_link_dialog(",
21032105 ] {
21042106 assert!(
crates/snowbound/src/conflict_render.rs+21-15
......@@ -10,7 +10,7 @@ const SCALE: f32 = 2.0;
1010
1111/// One view laid out: the bar and the page on the left, the page list on the right; `menu`
1212/// opens the conflict page's menu.
13fn layout(ui: &mut Ui, session: &Session, menu: bool) -> Vec<Command> {
13fn layout(ui: &mut Ui, session: &Session, showing: &Showing, menu: bool) -> Vec<Command> {
1414 let theme = ui.theme.clone();
1515 let section = theme.section(section_color(None));
1616 ui.begin(SIZE, SCALE, Instant::now());
......@@ -36,7 +36,7 @@ fn layout(ui: &mut Ui, session: &Session, menu: bool) -> Vec<Command> {
3636 },
3737 );
3838 let mut commands: Vec<Command> = session
39 .bar()
39 .bar(showing)
4040 .and_then(|bar| conflict_bar(ui, bar, &["synthetic", "Songs"], [false, true]))
4141 .into_iter()
4242 .collect();
......@@ -64,6 +64,7 @@ fn layout(ui: &mut Ui, session: &Session, menu: bool) -> Vec<Command> {
6464 &theme,
6565 &section,
6666 session,
67 showing,
6768 [&HashSet::new(); 2],
6869 &HashMap::new(),
6970 (0.0, false),
......@@ -242,8 +243,7 @@ fn conflict_views_offer_the_versions_and_render_offscreen() {
242243 let library = Arc::new(crate::Library::section(&file, &directory.join("cache")));
243244 let section = library.open(&library.location, || {}).unwrap();
244245 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();
247247 let version = session.versions(page)[0].space;
248248 let output = std::env::var_os("SNOWBOUND_CONFLICT_RENDER").map(PathBuf::from);
249249 let mut gpu = output.as_deref().map(gpu);
......@@ -262,15 +262,18 @@ fn conflict_views_offer_the_versions_and_render_offscreen() {
262262 Theme::dark(),
263263 ),
264264 ] {
265 session.space = space;
266 session.shown = shown;
265 let showing = Showing {
266 space,
267 shown,
268 ..Showing::default()
269 };
267270 let mut ui = Ui::new(appearance, std::time::Duration::from_millis(500));
268271 // Later frames lay out with earlier frames' measurements, and a menu fades in.
269272 for _ in 0..8 {
270 layout(&mut ui, &session, menu);
273 layout(&mut ui, &session, &showing, menu);
271274 std::thread::sleep(std::time::Duration::from_millis(30));
272275 }
273 let bar = session.bar();
276 let bar = session.bar(&showing);
274277 match (name, bar) {
275278 ("page" | "page-dark", Some(Bar::Page { shown: false, .. }))
276279 | ("versions", Some(Bar::Page { shown: true, .. }))
......@@ -326,8 +329,7 @@ fn page_versions_list_open_read_only_and_render_offscreen() {
326329 let library = Arc::new(crate::Library::section(&file, &directory.join("cache")));
327330 let section = library.open(&library.location, || {}).unwrap();
328331 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();
331333 let versions: Vec<_> = session
332334 .page_versions(page)
333335 .iter()
......@@ -374,10 +376,14 @@ fn page_versions_list_open_read_only_and_render_offscreen() {
374376 Theme::dark(),
375377 ),
376378 ] {
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) {
381387 (
382388 Some(Bar::History {
383389 version: open,
......@@ -393,7 +399,7 @@ fn page_versions_list_open_read_only_and_render_offscreen() {
393399 }
394400 let mut ui = Ui::new(appearance, std::time::Duration::from_millis(500));
395401 for _ in 0..8 {
396 layout(&mut ui, &session, menu);
402 layout(&mut ui, &session, &showing, menu);
397403 std::thread::sleep(std::time::Duration::from_millis(30));
398404 }
399405 for context in &versions {
crates/snowbound/src/history.rs+16-14
......@@ -25,9 +25,9 @@ impl Session {
2525
2626 /// Whether the open page takes no edits: a conflict page, a version, or a page of the
2727 /// 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()
3131 || crate::recycle::binned(&self.tabs[self.tab].path)
3232 }
3333
......@@ -54,13 +54,13 @@ impl Session {
5454 }
5555 }
5656
57 pub(crate) fn refresh_history(&mut self) -> Result<(), Box<dyn Error>> {
57 pub(crate) fn refresh_history(&mut self, showing: &mut Showing) -> Result<(), Box<dyn Error>> {
5858 self.history = self.section.versions()?;
59 if self
59 if showing
6060 .shown_history
6161 .is_some_and(|page| self.page_versions(page).is_empty())
6262 {
63 self.shown_history = None;
63 showing.shown_history = None;
6464 }
6565 Ok(())
6666 }
......@@ -142,9 +142,9 @@ pub fn menu(
142142impl State {
143143 /// Shows or hides a page's versions under it; hiding leaves an open version for its page.
144144 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() {
148148 self.commands.push(Command::OpenPage(page));
149149 }
150150 Ok(())
......@@ -174,7 +174,7 @@ impl State {
174174 .restore_version_async(page, version, &self.author);
175175 self.section_edit(operation, move |state, _| {
176176 if let Some(session) = &mut state.session {
177 session.refresh_history()?;
177 session.refresh_history(&mut state.pane.showing)?;
178178 }
179179 state.edited(vec![page]);
180180 state.commands.push(Command::OpenPage(page));
......@@ -193,7 +193,7 @@ impl State {
193193 .delete_versions_async(&[(page, vec![version])]);
194194 self.section_edit(operation, move |state, _| {
195195 if let Some(session) = &mut state.session {
196 session.refresh_history()?;
196 session.refresh_history(&mut state.pane.showing)?;
197197 }
198198 state.commands.push(Command::OpenPage(page));
199199 Ok(())
......@@ -274,9 +274,11 @@ impl State {
274274 };
275275 self.section_edit(operation, move |state, ()| {
276276 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));
280282 }
281283 }
282284 Ok(())
crates/snowbound/src/link.rs+14-13
......@@ -75,7 +75,7 @@ impl State {
7575 /// Ctrl+K and the toolbar's Link: the dialog opens on what the selection links.
7676 pub(crate) fn open_link_dialog(&mut self) {
7777 // 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 {
7979 return;
8080 };
8181 self.link = Some(LinkDialog {
......@@ -239,7 +239,7 @@ impl State {
239239 }
240240 let dialog = self.link.as_ref().unwrap();
241241 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)?;
243243 self.respond(response);
244244 } else if !cancel {
245245 return Ok(());
......@@ -252,7 +252,7 @@ impl State {
252252
253253 /// A secondary press on text, a picture or a file opens its context menu there.
254254 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 {
256256 return Ok(());
257257 };
258258 self.respond(response);
......@@ -338,15 +338,15 @@ impl State {
338338 let correction = context.spelling.unwrap();
339339 let response = match chosen_text.as_str() {
340340 _ if chosen < correction.suggestions.len() => {
341 self.view.correct(&correction, &chosen_text)?
341 self.pane.view.correct(&correction, &chosen_text)?
342342 }
343 "Delete Repeated Word" => self.view.correct(&correction, "")?,
343 "Delete Repeated Word" => self.pane.view.correct(&correction, "")?,
344344 "Spelling" => {
345345 self.open_spelling_pane();
346346 return Ok(());
347347 }
348348 text => {
349 if let Some(spelling) = &self.view.spelling {
349 if let Some(spelling) = &self.pane.view.spelling {
350350 if text == "Ignore" {
351351 spelling.ignore(&correction.word);
352352 } else {
......@@ -407,7 +407,8 @@ impl State {
407407 separated: true,
408408 disabled: disabled(commands::Id::Cut)
409409 || image.size.is_none_or(|size| {
410 self.view
410 self.pane
411 .view
411412 .editor
412413 .image_placement(image.id)
413414 .is_none_or(|(_, shown)| shown == size)
......@@ -554,7 +555,7 @@ impl State {
554555 return Ok(());
555556 }
556557 "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)?;
558559 self.respond(response);
559560 return Ok(());
560561 }
......@@ -581,15 +582,15 @@ impl State {
581582 return Ok(());
582583 }
583584 "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)?;
586587 self.clipboard.set_text(link)?;
587588 }
588589 return Ok(());
589590 }
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")?,
593594 };
594595 self.respond(response);
595596 Ok(())
crates/snowbound/src/live.rs+12-11
......@@ -590,8 +590,9 @@ impl State {
590590 let Some(session) = &self.session else {
591591 return live::Presence::default();
592592 };
593 let outline = self.view.editor.active_outline();
593 let outline = self.pane.view.editor.active_outline();
594594 let [anchor, focus] = self
595 .pane
595596 .view
596597 .editor
597598 .selection()
......@@ -599,7 +600,7 @@ impl State {
599600 .map(|position| spot(outline, position));
600601 live::Presence {
601602 section: section(session),
602 page: Some(session.space.into()),
603 page: Some(self.pane.showing.space.into()),
603604 caret: anchor
604605 .zip(focus)
605606 .map(|(anchor, focus)| Caret { anchor, focus }),
......@@ -815,26 +816,26 @@ impl State {
815816 self.commands
816817 .push(Command::OpenSection(library, tab.path.clone()));
817818 }
818 } else if presence.page != Some(session.space.into()) {
819 } else if presence.page != Some(self.pane.showing.space.into()) {
819820 if asked != Some(place) {
820821 self.commands.push(Command::OpenPage(presence.page?.into()));
821822 }
822823 } else {
823824 self.peers.going = None;
824825 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)?;
826827 let rect = outline
827828 .caret_at(focus, parley::Affinity::Downstream, 0.0)
828829 .ok()?;
829830 let [x, y] = outline.origin();
830 let viewport = self.view.viewport;
831 let viewport = self.pane.view.viewport;
831832 let [width, height] = viewport.size.map(|side| side as f32);
832833 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()?];
834835 let across = (rect.x0 as f32 + x) * viewport.scale + viewport.origin[0];
835836 if !(0.0..width).contains(&across) {
836837 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()?);
838839 }
839840 for response in responses {
840841 self.respond(response);
......@@ -924,9 +925,9 @@ impl State {
924925 let Some([left, top, right, bottom]) = self.ui.laid_out(page()) else {
925926 return;
926927 };
927 let here = (section(session), Some(session.space.into()));
928 let here = (section(session), Some(self.pane.showing.space.into()));
928929 let scale = self.ui.scale();
929 let viewport = self.view.viewport;
930 let viewport = self.pane.view.viewport;
930931 // A point on the page in points from the page box's corner.
931932 let shown = |[x, y]: [f32; 2]| {
932933 [
......@@ -943,7 +944,7 @@ impl State {
943944 else {
944945 continue;
945946 };
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 {
947948 continue;
948949 };
949950 let Ok(rect) = outline.caret_at(focus, parley::Affinity::Downstream, 0.0) else {
......@@ -971,7 +972,7 @@ impl State {
971972 if !(0.0..right - left).contains(&x0) {
972973 continue;
973974 }
974 let selection = find(&self.view.editor, caret.anchor)
975 let selection = find(&self.pane.view.editor, caret.anchor)
975976 .filter(|(other, _)| other.id == outline.id)
976977 .and_then(|(_, anchor)| outline.range_rects([anchor, focus].into()).ok())
977978 .unwrap_or_default()
crates/snowbound/src/main.rs+318-252
......@@ -539,7 +539,44 @@ fn icloud_listed(proxy: EventLoopProxy<UserEvent>) -> impl Fn() + Send + Sync +
539539 }
540540}
541541
542/// The open section and the page the editor shows.
542/// A page view, and which page, locked section or empty notebook it shows.
543struct Pane {
544 view: PageView,
545 showing: Showing,
546 /// The newest read requested; older ones are dropped when they finish.
547 loading: u64,
548 opening: Option<Opening>,
549 /// The section tab, if another, that the newest read opens, and when it was asked for.
550 switching: Option<(Option<usize>, Instant)>,
551 /// Pages visited, for Back and Forward.
552 trail: navigation::Trail,
553 /// The stored page changed elsewhere while marked text was pending; it is shown once
554 /// the composition ends.
555 stale: bool,
556 /// Whether the page last heard it had keyboard focus.
557 page_focused: bool,
558 /// The notebook shown while it has no section open, as one whose last section was
559 /// deleted.
560 sectionless: Option<Arc<Library>>,
561 /// A password-protected section shown locked, in place of an open one.
562 locked: Option<protection::Locked>,
563}
564
565/// Which page of the open section a pane shows, and what the page list shows beneath it.
566#[derive(Default)]
567struct Showing {
568 space: ExGuid,
569 /// The version of page `space` open, by its context.
570 version: Option<ExGuid>,
571 /// The page whose conflict pages the list shows beneath it.
572 shown: Option<ExGuid>,
573 /// Which of the open conflict page's conflicting changes is selected, in page order.
574 change: Option<usize>,
575 /// The page whose versions the list shows beneath it (Show Page Versions).
576 shown_history: Option<ExGuid>,
577}
578
579/// An open section.
543580struct Session {
544581 section: notebook::session::Section,
545582 /// The notebook the section belongs to.
......@@ -549,21 +586,12 @@ struct Session {
549586 tab: usize,
550587 /// Spaces, titles and outline levels in section order.
551588 pages: Vec<(ExGuid, String, u32)>,
552 space: ExGuid,
553589 /// The section's sync status as last read.
554590 sync: notebook::session::SyncStatus,
555591 /// Each page's conflict pages, the versions a merge could not take.
556592 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>,
561593 /// Each page's versions, newest first.
562594 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>,
567595}
568596
569597/// The information bar above a page with conflict pages, or above one of them.
......@@ -602,13 +630,13 @@ impl Session {
602630 Ok(())
603631 }
604632
605 fn title(&self) -> &str {
606 match self.conflict(self.space) {
633 fn title(&self, space: ExGuid) -> &str {
634 match self.conflict(space) {
607635 Some((_, version)) => &version.title,
608636 None => self
609637 .pages
610638 .iter()
611 .find(|(space, ..)| *space == self.space)
639 .find(|(listed, ..)| *listed == space)
612640 .map_or("", |(_, title, _)| title),
613641 }
614642 }
......@@ -630,33 +658,34 @@ impl Session {
630658 })
631659 }
632660
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 {
635664 return Some(Bar::History {
636 page: self.space,
665 page: space,
637666 version,
638667 grouped: self.grouped(),
639668 });
640669 }
641 match self.conflict(self.space) {
670 match self.conflict(space) {
642671 Some((page, _)) => Some(Bar::Version {
643672 page,
644 version: self.space,
673 version: space,
645674 }),
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),
649678 }),
650679 }
651680 }
652681
653 fn refresh_conflicts(&mut self) -> Result<(), Box<dyn Error>> {
682 fn refresh_conflicts(&mut self, showing: &mut Showing) -> Result<(), Box<dyn Error>> {
654683 self.conflicts = self.section.conflicts()?;
655 if self
684 if showing
656685 .shown
657686 .is_some_and(|page| self.versions(page).is_empty())
658687 {
659 self.shown = None;
688 showing.shown = None;
660689 }
661690 Ok(())
662691 }
......@@ -685,9 +714,9 @@ impl Session {
685714/// section, or a notebook read again after a change, with the catalog path the open
686715/// section has in it.
687716enum Loaded {
688 Section(Box<Session>, Page),
717 Section(Box<Session>, ExGuid, Page),
689718 /// 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),
691720 Page(ExGuid, Page),
692721 /// A page's version, by the page's space and the version's context.
693722 Version(ExGuid, ExGuid, Page),
......@@ -697,8 +726,8 @@ enum Loaded {
697726
698727/// What an opening page replaces: the section, or the page of the open one.
699728enum Shown {
700 Section(Box<Session>),
701 Created(Box<Session>),
729 Section(Box<Session>, ExGuid),
730 Created(Box<Session>, ExGuid),
702731 Page(ExGuid),
703732 Version(ExGuid, ExGuid),
704733}
......@@ -822,19 +851,14 @@ struct State {
822851 loads: (mpsc::Sender<Read>, mpsc::Receiver<Read>),
823852 /// Lays out pages the loader threads read, off the frame.
824853 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)>,
830854 prefetch: prefetch::Prefetch,
831855 /// Where each page was left this run, by `Library::key` and page space, as OneNote
832856 /// returns to it until the notebook closes.
833857 places: HashMap<(String, ExGuid), Place>,
834858 /// The page each section showed last this run, by `Library::key`.
835859 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>,
838862 /// What Undo and Redo take back across pages and sections.
839863 undo: undo::Timeline,
840864 swipe: navigation::Swipe,
......@@ -853,9 +877,11 @@ struct State {
853877 /// Backends that failed to start this run, and why.
854878 unavailable: Vec<(settings::Backend, String)>,
855879 ui: Ui,
856 view: PageView,
880 /// The page view the window shows.
881 pane: Pane,
857882 /// The open notebooks in the sidebar's order.
858883 notebooks: Vec<Arc<Library>>,
884 /// The open section, the one the pane shows.
859885 session: Option<Session>,
860886 /// A page not kept in any notebook, from a text file or `--page`.
861887 temporary: bool,
......@@ -938,11 +964,6 @@ struct State {
938964 titlebar: [[f32; 4]; 2],
939965 /// A section or group being renamed in the sidebar.
940966 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>,
946967 /// The password dialog while it is open.
947968 password: Option<protection::Asking>,
948969 /// Options' Passwords.
......@@ -977,11 +998,6 @@ struct State {
977998 changed: bool,
978999 /// Only the view scrolled or zoomed during this frame, to be announced after it.
9791000 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,
9851001 /// The pointer in logical pixels, for window drags from the strip.
9861002 pointer: [f32; 2],
9871003 /// When the strip was last pressed, to zoom on a double click.
......@@ -1180,6 +1196,7 @@ impl State {
11801196 icloud::look_up(icloud_listed(proxy.clone()));
11811197 let mut notebooks = Vec::new();
11821198 let mut session = None;
1199 let mut showing = Showing::default();
11831200 let mut sectionless = None;
11841201 let mut locked = None;
11851202 let (editor, scene) = match input {
......@@ -1213,9 +1230,10 @@ impl State {
12131230 .0;
12141231 notebooks.push(Arc::clone(&library));
12151232 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))?;
12171234 let (scene, editor) = PageScene::from_page(page, &mut engine)?;
12181235 session = Some(opened);
1236 showing.space = space;
12191237 (editor, Some((scene, [0.0; 2])))
12201238 }
12211239 Input::Notebooks { locations, current } => {
......@@ -1249,10 +1267,11 @@ impl State {
12491267 match shown {
12501268 Some((library, path, left)) if !library.locked(&path) => {
12511269 let section = library.open_async(&path, notify(proxy.clone())).await?;
1252 let (opened, page) =
1270 let (opened, space, page) =
12531271 read_session(section, Arc::clone(library), path, left)?;
12541272 let (scene, editor) = PageScene::from_page(page, &mut engine)?;
12551273 session = Some(opened);
1274 showing.space = space;
12561275 (editor, Some((scene, [0.0; 2])))
12571276 }
12581277 shown => {
......@@ -1348,14 +1367,25 @@ impl State {
13481367 drawing,
13491368 unavailable,
13501369 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 },
13591389 notebooks,
13601390 session,
13611391 temporary,
......@@ -1398,8 +1428,6 @@ impl State {
13981428 light_pages: stored.light_pages,
13991429 titlebar,
14001430 renaming: None,
1401 sectionless,
1402 locked,
14031431 password: None,
14041432 passwords: stored.passwords,
14051433 notebook_theme: (stored.notebook_theme.0)
......@@ -1420,8 +1448,6 @@ impl State {
14201448 commands: Vec::new(),
14211449 changed: false,
14221450 moved: false,
1423 stale: false,
1424 page_focused: true,
14251451 pointer: [0.0; 2],
14261452 strip_press: None,
14271453 strip_held: false,
......@@ -1439,18 +1465,11 @@ impl State {
14391465 loads: mpsc::channel(),
14401466 prefetch,
14411467 layouts,
1442 loading: 0,
1443 opening: None,
1444 switching: None,
14451468 places: HashMap::new(),
14461469 last_pages: (stored.recent.iter().rev())
14471470 .map(|place| (library::key(&place.notebook, &place.section), place.page))
14481471 .collect(),
1449 trail: {
1450 let mut trail = navigation::Trail::default();
1451 trail.recent = stored.recent;
1452 trail
1453 },
1472 recent: stored.recent,
14541473 undo: undo::Timeline::default(),
14551474 swipe: navigation::Swipe::default(),
14561475 access_adapter,
......@@ -1464,9 +1483,9 @@ impl State {
14641483 servers: stored.servers,
14651484 correction: None,
14661485 };
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);
14701489 // A notebook opened from its server that couldn't sign in asks to, as the Finder does.
14711490 let unsigned = state.notebooks.iter().find(|library| {
14721491 library.notebook.is_err() && library::server_address(&library.location).is_some()
......@@ -1557,7 +1576,7 @@ impl State {
15571576 let scale = self.scale();
15581577 if scale != self.ui.scale() {
15591578 self.renderer.clear_glyph_cache();
1560 let response = self.view.scale_factor_changed(scale)?;
1579 let response = self.pane.view.scale_factor_changed(scale)?;
15611580 self.respond(response);
15621581 }
15631582 #[cfg(feature = "live")]
......@@ -1572,10 +1591,10 @@ impl State {
15721591 platform::resize_direction(&self.window, self.pointer)
15731592 .map(CursorIcon::from)
15741593 .or_else(|| self.ui.cursor())
1575 .unwrap_or_else(|| cursor_icon(self.view.cursor())),
1594 .unwrap_or_else(|| cursor_icon(self.pane.view.cursor())),
15761595 );
15771596 let ime = if self.ui.focused() == Some(page()) {
1578 self.view.accepts_text()
1597 self.pane.view.accepts_text()
15791598 } else {
15801599 search::takes_text(self.ui.focused())
15811600 };
......@@ -1590,8 +1609,8 @@ impl State {
15901609 let commands = std::mem::take(&mut self.commands);
15911610 let follow = !commands.is_empty()
15921611 || 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();
15951614 for command in commands {
15961615 self.apply(command)?;
15971616 }
......@@ -1601,7 +1620,7 @@ impl State {
16011620 self.after_move()?;
16021621 }
16031622 self.moved = false;
1604 if self.stale {
1623 if self.pane.stale {
16051624 self.refresh()?;
16061625 }
16071626 // A covered window shows nothing, so animations wait for it to be uncovered.
......@@ -1669,8 +1688,8 @@ impl State {
16691688 ((rect[2] - rect[0]) * scale).round() as u32,
16701689 ((rect[3] - rect[1]) * scale).round() as u32,
16711690 ];
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)?;
16741693 self.respond(response);
16751694 }
16761695 }
......@@ -1743,8 +1762,8 @@ impl State {
17431762 platform::update_menu(|| self.statuses());
17441763 let welcome = self.session.is_none()
17451764 && !self.temporary
1746 && self.sectionless.is_none()
1747 && self.locked.is_none();
1765 && self.pane.sectionless.is_none()
1766 && self.pane.locked.is_none();
17481767 self.toolbar(&theme, !welcome)?;
17491768 if welcome {
17501769 self.welcome();
......@@ -1916,7 +1935,7 @@ impl State {
19161935 }
19171936 (clicked, open_tab)
19181937 }
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),
19201939 None => {
19211940 let tabs = [("Temporary page", section_color(None), false)];
19221941 let shown = if self.temporary { &tabs[..] } else { &[] };
......@@ -1994,13 +2013,16 @@ impl State {
19942013 },
19952014 );
19962015 if let Some(session) = &self.session
1997 && let Some(bar) = session.bar()
2016 && let Some(bar) = session.bar(&self.pane.showing)
19982017 {
19992018 let changes = self.changes().len();
20002019 let sections: Vec<&str> = session.tabs.iter().map(|tab| tab.name.as_str()).collect();
20012020 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),
20042026 ];
20052027 self.commands
20062028 .extend(conflict_bar(&mut self.ui, bar, &sections, steps));
......@@ -2008,7 +2030,7 @@ impl State {
20082030 if self.in_recycle_bin() {
20092031 recycle::bar(&mut self.ui);
20102032 }
2011 if self.session.is_none() && self.locked.is_some() {
2033 if self.session.is_none() && self.pane.locked.is_some() {
20122034 self.locked_page();
20132035 self.ui.close();
20142036 self.ui.close();
......@@ -2021,7 +2043,12 @@ impl State {
20212043 self.context_menu();
20222044 return Ok((section, open_tab, None));
20232045 }
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 {
20252052 self.no_sections(library);
20262053 self.ui.close();
20272054 self.ui.close();
......@@ -2053,7 +2080,7 @@ impl State {
20532080 self.template_strip(&theme);
20542081 }
20552082 self.transport(&theme)?;
2056 let scroll = self.view.scroll();
2083 let scroll = self.pane.view.scroll();
20572084 for (index, axis) in [Axis::X, Axis::Y]
20582085 .into_iter()
20592086 .filter(|_| !opening)
......@@ -2063,13 +2090,13 @@ impl State {
20632090 &mut self.ui,
20642091 index,
20652092 axis,
2066 -self.view.viewport.origin[index],
2093 -self.pane.view.viewport.origin[index],
20672094 [scroll.min[index], scroll.max[index]],
2068 self.view.viewport.size[index] as f32,
2095 self.pane.view.viewport.size[index] as f32,
20692096 scroll.max[1 - index] > scroll.min[1 - index],
20702097 [0.18, 0.18, 0.18, 0.45],
20712098 ) {
2072 let response = self.view.scroll_to(index, offset)?;
2099 let response = self.pane.view.scroll_to(index, offset)?;
20732100 self.respond(response);
20742101 }
20752102 }
......@@ -2077,7 +2104,7 @@ impl State {
20772104 if !opening {
20782105 self.peer_carets();
20792106 }
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]| {
20812108 let scale = self.ui.scale();
20822109 self.ui.leaf(
20832110 "task",
......@@ -2101,9 +2128,9 @@ impl State {
21012128 }
21022129 let signal = self.ui.signal(page());
21032130 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)?;
21072134 self.respond(response);
21082135 }
21092136 self.page_events(signal.events)?;
......@@ -2427,7 +2454,7 @@ impl State {
24272454 let fonts = &self.fonts;
24282455 let recent_fonts = &self.recent_fonts;
24292456 let pens = &self.toolbar;
2430 let engine = &self.view.engine;
2457 let engine = &self.pane.view.engine;
24312458 let session = self.session.as_ref();
24322459 let update = self.updates.status();
24332460 let sheet = self.gallery_sheet();
......@@ -3126,8 +3153,8 @@ impl State {
31263153 color: ui.theme.paper,
31273154 ink: ui.theme.paper_ink,
31283155 },
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(),
31313158 false,
31323159 &mut self.page_color_preview,
31333160 )
......@@ -3187,7 +3214,7 @@ impl State {
31873214 let format = self.format_state();
31883215 let [out, into] =
31893216 [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);
31913218 let field = self.ui.id("zoom field");
31923219 if self.ui.focused() != Some(field) {
31933220 self.zoom_typed = None;
......@@ -3303,7 +3330,7 @@ impl State {
33033330 self.choose(choice);
33043331 }
33053332 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)?;
33073334 self.respond(response);
33083335 }
33093336 Ok(())
......@@ -3402,7 +3429,7 @@ impl State {
34023429 node.set_orientation(accesskit::Orientation::Vertical);
34033430 }
34043431 // 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()) {
34063433 self.ui.close();
34073434 return None;
34083435 }
......@@ -3419,6 +3446,7 @@ impl State {
34193446 theme,
34203447 section,
34213448 session,
3449 &self.pane.showing,
34223450 [&found, &unread],
34233451 &peers,
34243452 (rounding, self.page_tabs_left),
......@@ -3461,7 +3489,10 @@ impl State {
34613489 if self.loading().is_some() {
34623490 return Ok(());
34633491 }
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));
34653496 let scale = self.ui.scale();
34663497 let corner = self.ui.laid_out(page()).unwrap_or_default();
34673498 let device = |point: [f32; 2]| {
......@@ -3472,30 +3503,32 @@ impl State {
34723503 };
34733504 for event in events {
34743505 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))?,
34763507 ui::Event::Pressure(pressure) => {
3477 self.view
3508 self.pane
3509 .view
34783510 .set_pressure(pressure.filter(|_| self.pen_pressure));
34793511 continue;
34803512 }
3481 ui::Event::PointerLeft => self.view.pointer_left(),
3513 ui::Event::PointerLeft => self.pane.view.pointer_left(),
34823514 ui::Event::Button {
34833515 button: MouseButton::Left,
34843516 pressed,
34853517 at,
34863518 } => {
34873519 if pressed {
3488 let response = self.view.pointer_pressed(at)?;
3520 let response = self.pane.view.pointer_pressed(at)?;
34893521 // 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()
34923525 {
3493 self.dismissed.insert(session.space);
3526 self.dismissed.insert(self.pane.showing.space);
34943527 }
34953528 response
34963529 } 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;
34993532 if anchor != focus
35003533 && let Some(painted) = self.painter.take()
35013534 {
......@@ -3521,7 +3554,7 @@ impl State {
35213554 ui::Event::Ime(Ime::Preedit(..) | Ime::Commit(_)) if read_only => continue,
35223555 ui::Event::Key { ref key, .. }
35233556 if read_only
3524 && !self.view.modifiers().command
3557 && !self.pane.view.modifiers().command
35253558 && !matches!(
35263559 key,
35273560 Key::Named(
......@@ -3538,20 +3571,25 @@ impl State {
35383571 {
35393572 continue;
35403573 }
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 }
35423577 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())?
35443579 }
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)?
35503582 }
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))?,
35513589 };
35523590 self.respond(response);
35533591 }
3554 if self.view.editor.marked_range().is_none() {
3592 if self.pane.view.editor.marked_range().is_none() {
35553593 platform::clear_marked_text(&self.window);
35563594 }
35573595 Ok(())
......@@ -3579,7 +3617,7 @@ impl State {
35793617 let tab = match &self.session {
35803618 // Back to the open section before another one opened.
35813619 Some(session)
3582 if self.switching.is_some()
3620 if self.pane.switching.is_some()
35833621 && Arc::ptr_eq(&session.library, &library)
35843622 && session.tabs[session.tab].path == path =>
35853623 {
......@@ -3591,34 +3629,37 @@ impl State {
35913629 }
35923630 _ => None,
35933631 };
3594 self.switching = Some((tab, Instant::now()));
3632 self.pane.switching = Some((tab, Instant::now()));
35953633 let notify = notify(self.proxy.clone());
35963634 let last = self.last_pages.get(&library.key(&path)).copied();
35973635 self.load_async(move || async move {
35983636 let start = Instant::now();
35993637 let section = library.open_async(&path, notify).await?;
36003638 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))
36033641 });
36043642 }
36053643 Command::OpenPage(space) => {
36063644 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 {
36083649 self.stop_loading();
36093650 return Ok(());
36103651 }
3611 self.switching = Some((None, Instant::now()));
3652 self.pane.switching = Some((None, Instant::now()));
36123653 let read = session.reader(space);
36133654 self.load(move || Ok(Loaded::Page(space, read()?)));
36143655 }
36153656 Command::Versions { page, show } => {
36163657 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);
36183659 let open = if show {
36193660 session.versions(page).first().map(|version| version.space)
36203661 } else {
3621 (session.space != page).then_some(page)
3662 (self.pane.showing.space != page).then_some(page)
36223663 };
36233664 self.commands.extend(open.map(Command::OpenPage));
36243665 }
......@@ -3627,7 +3668,7 @@ impl State {
36273668 let operation = session.section.delete_pages_async(&[version]);
36283669 self.section_edit(operation, move |state, _| {
36293670 if let Some(session) = &mut state.session {
3630 session.refresh_conflicts()?;
3671 session.refresh_conflicts(&mut state.pane.showing)?;
36313672 }
36323673 state.commands.push(Command::OpenPage(page));
36333674 Ok(())
......@@ -3635,9 +3676,9 @@ impl State {
36353676 }
36363677 Command::CopyVersion { version, tab } => {
36373678 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 {
36393680 Some(open) if open == version => {
3640 session.section.version(session.space, open)?
3681 session.section.version(self.pane.showing.space, open)?
36413682 }
36423683 _ => session.section.page(version)?,
36433684 };
......@@ -3729,7 +3770,7 @@ impl State {
37293770 Command::Choose(choice) => self.run(choice)?,
37303771 Command::TagPicture => self.pick_tag_picture(),
37313772 Command::Text(text) => {
3732 if let Some(context) = self.view.caret_context() {
3773 if let Some(context) = self.pane.view.caret_context() {
37333774 self.text_command(context, text)?;
37343775 }
37353776 }
......@@ -3747,8 +3788,8 @@ impl State {
37473788 &mut self,
37483789 read: impl FnOnce() -> F + Send + 'static,
37493790 ) {
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());
37523793 let layouts = Arc::clone(&self.layouts);
37533794 let requested = Instant::now();
37543795 let scenes = Arc::clone(&self.prefetch.scenes);
......@@ -3757,8 +3798,8 @@ impl State {
37573798 let laid = read().await.and_then(|loaded| {
37583799 lap("switch read", requested);
37593800 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),
37623803 Loaded::Page(space, page) => (Shown::Page(space), page),
37633804 Loaded::Version(space, version, page) => (Shown::Version(space, version), page),
37643805 Loaded::Library(library, path) => return Ok(Laid::Library(library, path)),
......@@ -3766,8 +3807,8 @@ impl State {
37663807 let mut engine = layouts.lock().map_err(|_| "Page layout failed")?;
37673808 let start = Instant::now();
37683809 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))
37713812 }
37723813 Shown::Page(space) => open.map(|open| (open, *space)),
37733814 Shown::Version(..) => None,
......@@ -3846,12 +3887,12 @@ impl State {
38463887 for (id, loaded) in reads {
38473888 match loaded {
38483889 // 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),
38513892 Ok(Laid::Library(library, path)) => self.adopt(library, path.as_deref())?,
38523893 Err(error) => {
3853 if id == self.loading {
3854 self.switching = None;
3894 if id == self.pane.loading {
3895 self.pane.switching = None;
38553896 }
38563897 platform::alert("Couldn't open", &error);
38573898 }
......@@ -3861,17 +3902,18 @@ impl State {
38613902 color: self.ui.theme.paper,
38623903 ink: self.ui.theme.paper_ink,
38633904 };
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 {
38663907 return Ok(());
38673908 };
38683909 let paper = paper.colored(opening.editor.page_color());
38693910 if !self
3911 .pane
38703912 .view
38713913 .prepare(&mut opening.scene, &opening.editor, paper, &self.redraw)
38723914 && opening.since.elapsed() < HOLD
38733915 {
3874 self.opening = Some(opening);
3916 self.pane.opening = Some(opening);
38753917 return Ok(());
38763918 }
38773919 lap("switch pictures", opening.since);
......@@ -3880,7 +3922,7 @@ impl State {
38803922
38813923 fn show_page(&mut self, mut opening: Opening) -> Result<(), Box<dyn Error>> {
38823924 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
38843926 && let Some(library) = self
38853927 .notebooks
38863928 .iter()
......@@ -3905,7 +3947,7 @@ impl State {
39053947 } else if let Some(path) = library.first_section() {
39063948 return self.apply(Command::OpenSection(library, path));
39073949 } else {
3908 self.switching = None;
3950 self.pane.switching = None;
39093951 return self.adopt(library, None);
39103952 }
39113953 }
......@@ -3916,22 +3958,24 @@ impl State {
39163958 let mut leaving = self
39173959 .session
39183960 .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);
39213963 if let Some(session) = &self.session {
39223964 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()
39283972 {
3929 self.prefetch.keep(key, session.space, scene);
3973 self.prefetch.keep(key, self.pane.showing.space, scene);
39303974 }
39313975 }
3932 let created = matches!(opening.loaded, Shown::Created(_));
3976 let created = matches!(opening.loaded, Shown::Created(..));
39333977 match opening.loaded {
3934 Shown::Section(session) | Shown::Created(session) => {
3978 Shown::Section(session, space) | Shown::Created(session, space) => {
39353979 // The settings keep the notebook shown, not the section.
39363980 let other_notebook = self
39373981 .session
......@@ -3940,7 +3984,7 @@ impl State {
39403984 self.list(&session.library);
39413985 // The section left stays open a while for coming back to; the notebook's
39423986 // background sync takes it over once it is closed.
3943 self.locked = None;
3987 self.pane.locked = None;
39443988 if let Some(previous) = self.session.replace(*session) {
39453989 let path = &previous.tabs[previous.tab].path;
39463990 let locked = self.leave(&previous.library, path);
......@@ -3956,7 +4000,11 @@ impl State {
39564000 });
39574001 }
39584002 }
3959 self.sectionless = None;
4003 self.pane.showing = Showing {
4004 space,
4005 ..Showing::default()
4006 };
4007 self.pane.sectionless = None;
39604008 if other_notebook {
39614009 self.save_settings();
39624010 }
......@@ -3964,12 +4012,12 @@ impl State {
39644012 Shown::Page(space) | Shown::Version(space, _) => {
39654013 let session = self.session.as_mut().ok_or("No section is open")?;
39664014 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 {
39694017 Shown::Version(_, version) => Some(version),
39704018 _ => None,
39714019 };
3972 session.change = None;
4020 self.pane.showing.change = None;
39734021 // Pages created, moved or deleted since the list was read.
39744022 session.pages = pages;
39754023 leaving = leaving.filter(|left| session.pages.iter().any(|(s, ..)| s == left));
......@@ -3977,9 +4025,12 @@ impl State {
39774025 }
39784026 let place = self.session.as_ref().and_then(|session| {
39794027 let key = session.key();
3980 self.places.get(&(key, session.space)).copied()
4028 self.places.get(&(key, self.pane.showing.space)).copied()
39814029 });
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);
39834034 // Each page takes up the history it was left with.
39844035 if let Some(leaving) = leaving {
39854036 self.undo.park(leaving, left);
......@@ -3987,19 +4038,19 @@ impl State {
39874038 let shown = self
39884039 .session
39894040 .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);
39924043 if let Some(parked) = shown.and_then(|space| self.undo.resume(space)) {
3993 self.view.resume(parked)?;
4044 self.pane.view.resume(parked)?;
39944045 }
39954046 lap("switch shown", requested);
3996 self.switching = None;
4047 self.pane.switching = None;
39974048 self.visited();
39984049 if let Some((space, action)) = self.after_open.take()
39994050 && let Some(session) = self
40004051 .session
40014052 .as_ref()
4002 .filter(|session| session.space == space)
4053 .filter(|_| self.pane.showing.space == space)
40034054 {
40044055 let target = menus::Target::Page {
40054056 library: Arc::clone(&session.library),
......@@ -4029,7 +4080,7 @@ impl State {
40294080 self.list(&library);
40304081 let shown = |shown: &Library| shown.location == library.location;
40314082 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)
40334084 || self
40344085 .session
40354086 .as_ref()
......@@ -4043,7 +4094,7 @@ impl State {
40434094 }
40444095 });
40454096 }
4046 self.sectionless = Some(Arc::clone(&library));
4097 self.pane.sectionless = Some(Arc::clone(&library));
40474098 self.title();
40484099 }
40494100 self.show_arrived(&library);
......@@ -4104,7 +4155,7 @@ impl State {
41044155 fn after_move(&mut self) -> Result<(), Box<dyn Error>> {
41054156 let scale = self.ui.scale();
41064157 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()?;
41084159 self.window.set_ime_cursor_area(
41094160 LogicalPosition::new(x0 / scale + corner[0], y0 / scale + corner[1]),
41104161 LogicalSize::new((x1 - x0) / scale, (y1 - y0) / scale),
......@@ -4132,11 +4183,11 @@ impl State {
41324183 } else {
41334184 ""
41344185 };
4135 format!("{}{bin} · {file}", session.title())
4186 format!("{}{bin} · {file}", session.title(self.pane.showing.space))
41364187 }
41374188 None if !self.temporary => "Snowbound".to_owned(),
41384189 None => {
4139 let editor = &self.view.editor;
4190 let editor = &self.pane.view.editor;
41404191 if !editor.active_outline().title {
41414192 return;
41424193 }
......@@ -4169,27 +4220,27 @@ impl State {
41694220 }
41704221
41714222 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 {
41734224 return;
41744225 };
41754226 let timestamp = date.timestamp();
4176 self.view.editor.finish_composition();
4227 self.pane.view.editor.finish_composition();
41774228 platform::clear_marked_text(&self.window);
41784229 let title = match field {
41794230 DateField::Date => "Change Page Date",
41804231 DateField::Time => "Change Page Time",
41814232 };
4182 let page = self.session.as_ref().map(|session| session.space);
4233 let page = self.session.as_ref().map(|_| self.pane.showing.space);
41834234 let reply = self.reply(
41844235 move |state, chosen: Result<(u64, [String; 2]), &'static str>| {
41854236 let (changed, text) = chosen?;
41864237 // 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)
41894240 {
41904241 return Ok(());
41914242 }
4192 let response = state.view.change_date(changed, text)?;
4243 let response = state.pane.view.change_date(changed, text)?;
41934244 state.respond(response);
41944245 Ok(())
41954246 },
......@@ -4211,13 +4262,13 @@ impl State {
42114262 let Some((_, version)) = self
42124263 .session
42134264 .as_ref()
4214 .and_then(|session| session.conflict(session.space))
4265 .and_then(|session| session.conflict(self.pane.showing.space))
42154266 else {
42164267 return Vec::new();
42174268 };
42184269 let marked = |id: &ExGuid| version.objects.contains(id);
42194270 let mut changes = Vec::new();
4220 for outline in self.view.editor.outlines() {
4271 for outline in self.pane.view.editor.outlines() {
42214272 for (at, node) in outline.document().text_nodes().enumerate() {
42224273 let Some(text) = node.text() else {
42234274 continue;
......@@ -4234,10 +4285,10 @@ impl State {
42344285 /// Selects the next or previous conflicting change, as OneNote's conflict menu does.
42354286 fn select_change(&mut self, forward: bool) -> Result<(), Box<dyn Error>> {
42364287 let changes = self.changes();
4237 let Some(session) = &mut self.session else {
4288 if self.session.is_none() {
42384289 return Ok(());
4239 };
4240 let next = match (session.change, forward) {
4290 }
4291 let next = match (self.pane.showing.change, forward) {
42414292 (None, true) => 0,
42424293 (Some(at), true) => at + 1,
42434294 (Some(at), false) if at > 0 => at - 1,
......@@ -4246,12 +4297,12 @@ impl State {
42464297 let Some(&(outline, paragraph, length)) = changes.get(next) else {
42474298 return Ok(());
42484299 };
4249 session.change = Some(next);
4300 self.pane.showing.change = Some(next);
42504301 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())?;
42534304 self.ui.set_focus(Some(page()));
4254 let response = self.view.focus_text()?;
4305 let response = self.pane.view.focus_text()?;
42554306 self.respond(response);
42564307 self.window.request_redraw();
42574308 Ok(())
......@@ -4281,7 +4332,7 @@ impl State {
42814332 /// Hands the section the ops the editor recorded since the last call, as one edit; the
42824333 /// section thread stores it.
42834334 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() {
42854336 Ok(ops) => ops,
42864337 Err(error) => return self.refused(&error.to_string()),
42874338 };
......@@ -4291,12 +4342,12 @@ impl State {
42914342 if ops.is_empty() {
42924343 return Ok(());
42934344 }
4294 if session.read_only() {
4345 if session.read_only(&self.pane.showing) {
42954346 // A conflict page or a version is read-only: it returns to what is stored.
4296 self.stale = true;
4347 self.pane.stale = true;
42974348 return Ok(());
42984349 }
4299 let space = session.space;
4350 let space = self.pane.showing.space;
43004351 session.section.apply(
43014352 &self.author,
43024353 onestore::op::Edit {
......@@ -4308,18 +4359,20 @@ impl State {
43084359 },
43094360 )?;
43104361 // 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;
43124363 let title = (editor.outlines().iter())
43134364 .find(|outline| outline.title)
43144365 .and_then(|outline| outline.document().paragraphs().last())
43154366 .map(|paragraph| onestore::op::title_line(paragraph.text()));
43164367 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 })
43184371 {
43194372 session.pages = session.section.pages()?;
43204373 }
43214374 self.edited(vec![space]);
4322 let editor = &self.view.editor;
4375 let editor = &self.pane.view.editor;
43234376 self.undo
43244377 .edited(space, editor.history_depth(), editor.typing().is_some());
43254378 Ok(())
......@@ -4328,17 +4381,17 @@ impl State {
43284381 /// Shows the page as stored after a change made elsewhere, in place: the caret,
43294382 /// selection, scroll and drawn pictures stay. Waits for marked text to end.
43304383 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;
43334386 return Ok(());
43344387 }
4335 self.stale = false;
4388 self.pane.stale = false;
43364389 self.persist()?;
43374390 let Some(session) = &self.session else {
43384391 return Ok(());
43394392 };
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)?;
43424395 self.respond(response);
43434396 self.title();
43444397 self.window.request_redraw();
......@@ -4349,10 +4402,13 @@ impl State {
43494402 /// text the editor showed goes on the clipboard so nothing typed is lost unannounced.
43504403 fn refused(&mut self, error: &str) -> Result<(), Box<dyn Error>> {
43514404 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))?;
43534406 // 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)?;
43564412 platform::clear_marked_text(&self.window);
43574413 self.refresh()?;
43584414 platform::alert(
......@@ -4413,13 +4469,13 @@ impl State {
44134469 match event {
44144470 Event::Changed(spaces) => {
44154471 listed = true;
4416 changed |= spaces.contains(&session.space);
4472 changed |= spaces.contains(&self.pane.showing.space);
44174473 elsewhere.extend_from_slice(&spaces);
44184474 self.search
44194475 .changed(session.key(), session.section.replica(), spaces);
44204476 }
44214477 Event::Rejected { spaces, error } => {
4422 if spaces.contains(&session.space) {
4478 if spaces.contains(&self.pane.showing.space) {
44234479 rejected = Some(error);
44244480 } else {
44254481 eprintln!("Saving failed: {error}");
......@@ -4447,23 +4503,26 @@ impl State {
44474503 return Ok(());
44484504 };
44494505 if listed {
4450 let back = session.shown;
4506 let back = self.pane.showing.shown;
44514507 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)?;
44544510 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| {
44564512 !session
4457 .page_versions(session.space)
4513 .page_versions(self.pane.showing.space)
44584514 .iter()
44594515 .any(|listed| listed.context == version)
44604516 });
4461 if gone && listed(session.space) {
4517 if gone && listed(self.pane.showing.space) {
44624518 // 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));
44644521 return Ok(());
44654522 }
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 {
44674526 // A page removed elsewhere, or a conflict page deleted elsewhere, which
44684527 // returns to its page.
44694528 let open = back
......@@ -4511,7 +4570,7 @@ impl State {
45114570 }
45124571 let mut error = None;
45134572 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);
45154574 self.access_adapter.update_if_active(|| {
45164575 sent = true;
45174576 page_tree(view, ui, window, &mut self.accessibility).unwrap_or_else(|failure| {
......@@ -4592,7 +4651,7 @@ impl State {
45924651 let mut tree = Tree::new(chrome, true);
45934652 if self.ui.laid_out(page()).is_some() {
45944653 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)?;
45964655 tree.update_and_process_changes(page, &mut Unwatched);
45974656 }
45984657 if focus != graft || self.ui.laid_out(page()).is_some() {
......@@ -4638,7 +4697,7 @@ impl State {
46384697 if request.action == Action::Focus {
46394698 self.window.focus_window();
46404699 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)?;
46424701 self.respond(response);
46434702 self.window.request_redraw();
46444703 }
......@@ -4647,7 +4706,7 @@ impl State {
46474706 let Some(outline) = self.accessibility.outline_for_node(request.target_node) else {
46484707 return Ok(());
46494708 };
4650 let view = &mut self.view;
4709 let view = &mut self.pane.view;
46514710 match (request.action, request.data) {
46524711 (Action::Focus, _) => {
46534712 view.editor.focus_outline(outline)?;
......@@ -4671,7 +4730,7 @@ impl State {
46714730 _ => return Ok(()),
46724731 }
46734732 self.ui.set_focus(Some(page()));
4674 let response = self.view.focus_text()?;
4733 let response = self.pane.view.focus_text()?;
46754734 self.respond(response);
46764735 self.window.request_redraw();
46774736 Ok(())
......@@ -4682,8 +4741,12 @@ impl State {
46824741 if let Some(path) = self.snapshot.take() {
46834742 // A hidden window never paints, so nothing polls the pictures between snapshots.
46844743 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 );
46874750 }
46884751 self.snapshot(&path)?;
46894752 }
......@@ -4724,7 +4787,7 @@ impl State {
47244787
47254788 /// Marks misspelled words on the page unless Hide Spelling Errors is on.
47264789 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);
47284791 self.window.request_redraw();
47294792 }
47304793
......@@ -4736,7 +4799,7 @@ impl State {
47364799 }
47374800 .colored(
47384801 self.page_color_preview
4739 .unwrap_or_else(|| self.view.editor.page_color()),
4802 .unwrap_or_else(|| self.pane.view.editor.page_color()),
47404803 )
47414804 }
47424805
......@@ -4758,9 +4821,9 @@ impl State {
47584821 fn paint(&mut self, target: &draw::Target) -> Result<(), Box<dyn Error>> {
47594822 let start = Instant::now();
47604823 let paper = self.paper();
4761 self.view.update_pictures(paper, &self.redraw);
4824 self.pane.view.update_pictures(paper, &self.redraw);
47624825 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();
47644827 }
47654828 let theme = &self.ui.theme;
47664829 let scale = self.ui.scale();
......@@ -4772,20 +4835,20 @@ impl State {
47724835 canvas::gpu::Viewport {
47734836 scale,
47744837 origin: [0.0; 2],
4775 ..self.view.viewport
4838 ..self.pane.view.viewport
47764839 },
47774840 ),
47784841 None => (
4779 self.view.primitives(TextColors {
4842 self.pane.view.primitives(TextColors {
47804843 caret: theme.caret,
4781 selection: if self.page_focused {
4844 selection: if self.pane.page_focused {
47824845 theme.selection
47834846 } else {
47844847 theme.inactive_selection
47854848 },
47864849 paper,
47874850 })?,
4788 self.view.viewport,
4851 self.pane.view.viewport,
47894852 ),
47904853 };
47914854 lap("page primitives", start);
......@@ -4853,14 +4916,17 @@ impl State {
48534916 fn pinch(&mut self, factor: f32) -> Result<(), Box<dyn Error>> {
48544917 if self.over_page() {
48554918 // 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))?;
48574923 self.respond(response);
48584924 }
48594925 Ok(())
48604926 }
48614927
48624928 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();
48644930 let armed = scroll.max[0] <= scroll.min[0] && self.over_page();
48654931 if let Some(forward) = self.swipe.scroll(phase, delta, armed, Instant::now()) {
48664932 self.travel(forward);
......@@ -5209,6 +5275,7 @@ fn page_rows(
52095275 theme: &Theme,
52105276 section: &ui::Section,
52115277 session: &Session,
5278 showing: &Showing,
52125279 marked: [&HashSet<ExGuid>; 2],
52135280 peers: &HashMap<ExGuid, Vec<[f32; 4]>>,
52145281 shape: (f32, bool),
......@@ -5255,6 +5322,7 @@ fn page_rows(
52555322 theme,
52565323 section,
52575324 session,
5325 showing,
52585326 marked,
52595327 peers,
52605328 shape,
......@@ -5283,6 +5351,7 @@ fn page_rows(
52835351 theme,
52845352 section,
52855353 session,
5354 showing,
52865355 marked,
52875356 peers,
52885357 shape,
......@@ -5305,6 +5374,7 @@ fn page_row(
53055374 theme: &Theme,
53065375 section: &ui::Section,
53075376 session: &Session,
5377 showing: &Showing,
53085378 [found, unread]: [&HashSet<ExGuid>; 2],
53095379 peers: &HashMap<ExGuid, Vec<[f32; 4]>>,
53105380 shape: (f32, bool),
......@@ -5334,7 +5404,7 @@ fn page_row(
53345404 shift,
53355405 lifted,
53365406 };
5337 let selected = *space == session.space && session.version.is_none();
5407 let selected = *space == showing.space && showing.version.is_none();
53385408 if selected && !lifted {
53395409 rows.open = Some(ui.id(space));
53405410 }
......@@ -5366,19 +5436,19 @@ fn page_row(
53665436 };
53675437 // Each row: its id, its label, whether it is open, and whether it is a page version.
53685438 let history = (session.page_versions(*space).iter())
5369 .filter(|_| session.shown_history == Some(*space))
5439 .filter(|_| showing.shown_history == Some(*space))
53705440 .map(|version| {
5371 let open = session.version == Some(version.context);
5441 let open = showing.version == Some(version.context);
53725442 (version.context, history::label(version), open, true)
53735443 });
53745444 let conflicts = (versions.iter())
5375 .filter(|_| session.shown == Some(*space))
5445 .filter(|_| showing.shown == Some(*space))
53765446 .map(|version| {
53775447 let label = match version.created {
53785448 Some(created) => format!("{} {}", platform::short_date(created), version.user),
53795449 None => version.user.clone(),
53805450 };
5381 (version.space, label, version.space == session.space, false)
5451 (version.space, label, version.space == showing.space, false)
53825452 });
53835453 for (id, label, selected, version) in history.chain(conflicts) {
53845454 let tab = PageTab {
......@@ -6236,14 +6306,14 @@ pub fn plain(error: &(dyn Error + 'static), thing: &str) -> String {
62366306 }
62376307}
62386308
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.
62416311fn read_session(
62426312 section: notebook::session::Section,
62436313 library: Arc<Library>,
62446314 path: String,
62456315 space: Option<ExGuid>,
6246) -> Result<(Session, Page), Box<dyn Error>> {
6316) -> Result<(Session, ExGuid, Page), Box<dyn Error>> {
62476317 let folder = path.rsplit_once('/').map_or("", |(folder, _)| folder);
62486318 let tabs = library.tabs(folder);
62496319 let tab = tabs
......@@ -6266,15 +6336,11 @@ fn read_session(
62666336 tabs,
62676337 tab,
62686338 pages,
6269 space,
62706339 sync,
62716340 conflicts,
6272 shown: None,
6273 change: None,
62746341 history,
6275 shown_history: None,
6276 version: None,
62776342 },
6343 space,
62786344 page,
62796345 ))
62806346}
......@@ -6371,7 +6437,7 @@ impl State {
63716437 }
63726438 UserEvent::InsertText(text) => {
63736439 if self.ui.focused() == Some(page()) {
6374 match self.view.insert_text(text) {
6440 match self.pane.view.insert_text(text) {
63756441 Ok(response) => self.respond(response),
63766442 Err(error) => eprintln!("{error}"),
63776443 }
......@@ -6488,7 +6554,7 @@ impl State {
64886554 // Quiet still when the mark sent on finding it quiet arrives, what a thread sent
64896555 // as it ended has been taken, however many frames wait in the queue before it.
64906556 let quiet = self.settled()
6491 && self.opening.is_none()
6557 && self.pane.opening.is_none()
64926558 && RUNNING.load(Ordering::Acquire) == 0
64936559 && self.search.pending.load(Ordering::Relaxed) == 0;
64946560 if let Some((path, settled, sent)) = self.replay_settle.take() {
......@@ -6522,7 +6588,7 @@ impl State {
65226588 if event.window_id != self.window.id() {
65236589 return;
65246590 }
6525 let was_marked = self.view.editor.marked_range().is_some();
6591 let was_marked = self.pane.view.editor.marked_range().is_some();
65266592 let result = match event.window_event {
65276593 accesskit_winit::WindowEvent::InitialTreeRequested => {
65286594 self.deactivate_accessibility();
......@@ -6534,7 +6600,7 @@ impl State {
65346600 Ok(())
65356601 }
65366602 };
6537 if was_marked && self.view.editor.marked_range().is_none() {
6603 if was_marked && self.pane.view.editor.marked_range().is_none() {
65386604 platform::clear_marked_text(&self.window);
65396605 }
65406606 if let Err(error) = result {
......@@ -6547,7 +6613,7 @@ impl State {
65476613impl State {
65486614 /// Quits, first asking whether to discard a temporary page holding edits.
65496615 fn quit(&self) {
6550 let editor = &self.view.editor;
6616 let editor = &self.pane.view.editor;
65516617 let unchanged = editor.caret_outline().is_none_or(TextOutline::is_empty)
65526618 && self.initial_date == editor.date().map(|date| date.timestamp())
65536619 && (self.initial_layouts.iter())
......@@ -6804,7 +6870,7 @@ impl ApplicationHandler<UserEvent> for App {
68046870 WindowEvent::PinchGesture { delta, .. } => state.pinch(1.0 + delta as f32)?,
68056871 // Smart zoom: to 200% about the fingers, or back to 100%.
68066872 WindowEvent::DoubleTapGesture { .. } => {
6807 let zoom = state.view.zoom();
6873 let zoom = state.pane.view.zoom();
68086874 let target = if (zoom - 1.0).abs() < 0.01 { 2.0 } else { 1.0 };
68096875 state.pinch(target / zoom)?
68106876 }
......@@ -6826,7 +6892,7 @@ impl ApplicationHandler<UserEvent> for App {
68266892 return;
68276893 }
68286894 let now = Instant::now();
6829 let (repaint, blink) = state.view.blink(now);
6895 let (repaint, blink) = state.pane.view.blink(now);
68306896 // A due interface change waits on the frame that shows it, which sets the next.
68316897 let wake = state.ui.wake_at();
68326898 if repaint || wake.is_some_and(|wake| wake <= now) {
......@@ -7562,7 +7628,7 @@ mod tests {
75627628 let section = library.open(&tab.path, || {}).unwrap();
75637629 let opened = ms(start);
75647630 let start = Instant::now();
7565 let (session, page) =
7631 let (session, _, page) =
75667632 read_session(section, Arc::clone(&library), tab.path.clone(), None).unwrap();
75677633 let read = ms(start);
75687634 let start = Instant::now();
crates/snowbound/src/manage.rs+50-41
......@@ -177,8 +177,8 @@ impl State {
177177 title: &str,
178178 ) -> Result<PageCreation, Box<dyn Error>> {
179179 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,
182182 )?;
183183 let [date, time] = platform::date_text(creation.created());
184184 Ok(creation.dated(&date, &time)?)
......@@ -228,7 +228,7 @@ impl State {
228228 session.section.replica().apply(&self.author, edit)?;
229229 #[cfg(target_arch = "wasm32")]
230230 session.section.apply(&self.author, edit)?;
231 let shown = session.space;
231 let shown = self.pane.showing.space;
232232 self.made(Change::Delete {
233233 pages: vec![space],
234234 show: Some(shown),
......@@ -236,7 +236,8 @@ impl State {
236236 });
237237 let session = self.session.as_ref().ok_or("No section is open")?;
238238 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)?;
240241 self.stop_loading();
241242 let now = crate::Instant::now();
242243 self.show_page(crate::Opening {
......@@ -248,14 +249,15 @@ impl State {
248249 })?;
249250 self.ui.set_focus(Some(crate::page()));
250251 if let Some(title) = self
252 .pane
251253 .view
252254 .editor
253255 .outlines()
254256 .iter()
255257 .find(|outline| outline.title)
256258 {
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()?;
259261 self.respond(response);
260262 }
261263 Ok(())
......@@ -267,7 +269,7 @@ impl State {
267269 self.with_art(Some(template), move |state, art| {
268270 state.persist()?;
269271 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)?;
271273 state.edit_page(ops)
272274 })
273275 }
......@@ -283,16 +285,13 @@ impl State {
283285 let Some(template) = template else {
284286 return then(self, Vec::new());
285287 };
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;
287290 let proxy = self.proxy.clone();
288291 crate::spawn(move || {
289292 let art = template.pictures().map_err(|error| error.to_string());
290293 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 {
296295 then(state, art?)
297296 } else {
298297 Ok(())
......@@ -310,7 +309,7 @@ impl State {
310309 rule_lines: Option<onestore::page::RuleLines>,
311310 art: Option<Vec<Image>>,
312311 ) -> 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)?;
314313 self.respond(response);
315314 Ok(())
316315 }
......@@ -321,7 +320,7 @@ impl State {
321320 return Ok(());
322321 }
323322 let session = self.session.as_ref().ok_or("No section is open")?;
324 let space = session.space;
323 let space = self.pane.showing.space;
325324 session.section.apply(
326325 &self.author,
327326 Edit {
......@@ -469,6 +468,7 @@ impl State {
469468 pub(crate) fn show_arrived(&mut self, library: &Arc<Library>) {
470469 if self.session.is_some()
471470 || self
471 .pane
472472 .sectionless
473473 .as_ref()
474474 .is_some_and(|shown| shown.location != library.location)
......@@ -476,7 +476,7 @@ impl State {
476476 return;
477477 }
478478 let first = library.first_section();
479 self.sectionless = Some(Arc::clone(library));
479 self.pane.sectionless = Some(Arc::clone(library));
480480 self.title();
481481 if let Some(path) = first {
482482 self.commands
......@@ -572,8 +572,8 @@ impl State {
572572 .first_section()
573573 .ok_or("The new notebook has no section")?;
574574 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))
577577 });
578578 Ok(())
579579 }
......@@ -623,7 +623,7 @@ impl State {
623623 background.discard();
624624 }
625625 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)
627627 || self
628628 .session
629629 .as_ref()
......@@ -631,7 +631,7 @@ impl State {
631631 {
632632 self.persist().unwrap_or_else(|error| eprintln!("{error}"));
633633 self.session = None;
634 self.sectionless = None;
634 self.pane.sectionless = None;
635635 match self
636636 .notebooks
637637 .iter()
......@@ -639,7 +639,7 @@ impl State {
639639 {
640640 Some((open, path)) => self.commands.push(Command::OpenSection(open, path)),
641641 None => {
642 self.sectionless = self.notebooks.first().cloned();
642 self.pane.sectionless = self.notebooks.first().cloned();
643643 self.title();
644644 }
645645 }
......@@ -695,15 +695,16 @@ impl State {
695695 (self.session).take_if(|session| Arc::ptr_eq(&session.library, &library))
696696 {
697697 // 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();
699699 if let Err(error) = session.section.close() {
700700 eprintln!("{location}: {error}");
701701 }
702 self.sectionless = Some(Arc::clone(&library));
702 self.pane.sectionless = Some(Arc::clone(&library));
703703 }
704704 self.trashing.insert(location.clone());
705705 self.close_notebook(&library);
706 self.trail.forget(&location);
706 self.pane.trail.forget(&location);
707 self.recent.retain(|place| place.notebook != location);
707708 self.reads.forget(&location);
708709 let ours = crate::library::key(&location, "");
709710 self.folded.retain(|key| !key.starts_with(&ours));
......@@ -754,13 +755,13 @@ impl State {
754755 .as_ref()
755756 .and_then(|_| self.session.take())
756757 .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);
758759 if showing {
759 self.sectionless = Some(Arc::clone(&library));
760 self.pane.sectionless = Some(Arc::clone(&library));
760761 self.title();
761762 }
762763 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);
764765 crate::spawn_async(move || async move {
765766 let structure = Arc::clone(&library.structure);
766767 let _structure = structure.lock().await;
......@@ -805,7 +806,7 @@ impl State {
805806 state.folder_rename = None;
806807 }
807808 let restore = showing
808 && requested == state.loading
809 && requested == state.pane.loading
809810 && state.notebook().is_some_and(|current| {
810811 current.location == library.location
811812 && Arc::ptr_eq(&current.structure, &library.structure)
......@@ -839,7 +840,14 @@ impl State {
839840 .retain(|open| open.location != to || !std::mem::replace(&mut listed, true));
840841 if from != to {
841842 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 }
843851 self.reads.moved(&from, &to);
844852 let (from, to) = (crate::library::key(&from, ""), crate::library::key(&to, ""));
845853 let rekey = |key: &String| match key.strip_prefix(&from) {
......@@ -852,7 +860,7 @@ impl State {
852860 .collect();
853861 }
854862 if let Some(shown) = shown {
855 self.sectionless = Some(Arc::clone(&reopened));
863 self.pane.sectionless = Some(Arc::clone(&reopened));
856864 self.title();
857865 if let Some(path) = shown
858866 .filter(|path| reopened.contains(path))
......@@ -885,8 +893,8 @@ impl State {
885893 .filter(|session| Arc::ptr_eq(&session.library, &library))
886894 .map(|session| session.tabs[session.tab].path.clone());
887895 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();
890898 let structure = Arc::clone(&library.structure);
891899 let pending = match &change {
892900 Structure::Rename { path, name } => library.entry_identity(path).map(|identity| {
......@@ -991,8 +999,8 @@ impl State {
991999 library.keep_key(&path, key);
9921000 }
9931001 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));
9961004 }
9971005 Structure::Properties { name, color } => {
9981006 if name != library.name {
......@@ -1018,11 +1026,11 @@ impl State {
10181026 return Ok(Loaded::Library(library, Some(path)));
10191027 }
10201028 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)?;
10221030 Ok(if fresh {
1023 Loaded::Created(Box::new(session), page)
1031 Loaded::Created(Box::new(session), space, page)
10241032 } else {
1025 Loaded::Section(Box::new(session), page)
1033 Loaded::Section(Box::new(session), space, page)
10261034 })
10271035 };
10281036 crate::spawn_async(move || async move {
......@@ -1040,7 +1048,7 @@ impl State {
10401048 };
10411049 let library = match &loaded {
10421050 Loaded::Library(library, _) => Arc::clone(library),
1043 Loaded::Section(session, _) | Loaded::Created(session, _) => {
1051 Loaded::Section(session, ..) | Loaded::Created(session, ..) => {
10441052 Arc::clone(&session.library)
10451053 }
10461054 _ => unreachable!("A structure change returns its notebook"),
......@@ -1071,7 +1079,7 @@ impl State {
10711079 });
10721080 if let Some(path) = path {
10731081 state.adopt(library, Some(&path))?;
1074 } else if requested != state.loading || !showing {
1082 } else if requested != state.pane.loading || !showing {
10751083 if let Some(path) = library.first_section() {
10761084 state.commands.push(Command::OpenSection(library, path));
10771085 } else {
......@@ -1079,13 +1087,14 @@ impl State {
10791087 }
10801088 }
10811089 } else if state
1090 .pane
10821091 .sectionless
10831092 .as_ref()
10841093 .is_some_and(|shown| shown.location == library.location)
10851094 {
1086 state.sectionless = Some(library);
1095 state.pane.sectionless = Some(library);
10871096 }
1088 if showing && requested == state.loading {
1097 if showing && requested == state.pane.loading {
10891098 match loaded {
10901099 Loaded::Library(library, path) => state.adopt(library, path.as_deref())?,
10911100 loaded => state.load(move || Ok(loaded)),
crates/snowbound/src/menus.rs+2-2
......@@ -489,7 +489,7 @@ impl State {
489489 let above = at
490490 .and_then(|at| at.checked_sub(1))
491491 .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);
493493 let versions = if shown {
494494 "Hide Page Versions"
495495 } else {
......@@ -1039,7 +1039,7 @@ impl State {
10391039 new(&action, String::new()).map(|structure| Command::Structure(library, structure))
10401040 }
10411041 (Target::Closed(location), Action::Delete) => {
1042 self.trail.recent.retain(|place| place.notebook != location);
1042 self.recent.retain(|place| place.notebook != location);
10431043 self.save_settings();
10441044 None
10451045 }
crates/snowbound/src/navigation.rs+46-50
......@@ -39,19 +39,17 @@ impl Visit {
3939 }
4040}
4141
42/// Pages visited before and after the one shown, and those shown lately.
42/// Pages visited before and after the one shown.
4343#[derive(Default)]
4444pub struct Trail {
4545 back: Vec<Visit>,
4646 here: Option<Visit>,
4747 forward: Vec<Visit>,
48 /// Each page shown lately once, latest first, kept between launches.
49 pub recent: Vec<Place>,
5048}
5149
5250/// Places Back keeps, at most.
5351const KEPT: usize = 100;
54/// Pages `Trail::recent` keeps, at most.
52/// Pages `State::recent` keeps, at most.
5553const RECENT: usize = 8;
5654
5755impl Trail {
......@@ -61,8 +59,7 @@ impl Trail {
6159 .chain(&mut self.here)
6260 .chain(&mut self.forward)
6361 .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) {
6663 *notebook = to.to_owned();
6764 }
6865 }
......@@ -73,14 +70,6 @@ impl Trail {
7370 self.back.retain(elsewhere);
7471 self.forward.retain(elsewhere);
7572 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);
8473 }
8574
8675 /// Notes `visit` shown. Arriving anywhere but where Back or Forward went drops the pages
......@@ -122,23 +111,30 @@ impl Trail {
122111 pub fn open(&self, exists: impl Fn(&Visit) -> bool) -> [bool; 2] {
123112 [&self.back, &self.forward].map(|side| side.iter().rev().any(&exists))
124113 }
114}
125115
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.
117pub 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.
125pub 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
142138}
143139
144140/// Where `visit`'s page is in `notebooks`, as `index` has read them and `open` tells whether
......@@ -189,19 +185,19 @@ impl State {
189185 let place = Place {
190186 notebook: session.library.location.clone(),
191187 section: path.clone(),
192 page: session.space,
188 page: self.pane.showing.space,
193189 };
194190 if let Ok(section) = session.section.identity() {
195 self.trail.visit(Visit {
191 self.pane.trail.visit(Visit {
196192 notebook: place.notebook.clone(),
197193 section,
198 space: session.space,
199 page: self.view.editor.identity(),
194 space: self.pane.showing.space,
195 page: self.pane.view.editor.identity(),
200196 binned: crate::recycle::binned(path),
201197 });
202198 }
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);
205201 if moved {
206202 self.save_settings();
207203 }
......@@ -222,7 +218,7 @@ impl State {
222218
223219 /// Whether Back and Forward have a page to go to.
224220 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())
226222 }
227223
228224 /// Whether `library`'s section at `path` is the one open.
......@@ -246,9 +242,9 @@ impl State {
246242 /// Shows the page visited before the one shown, or after it going `forward`, wherever it
247243 /// went since. Pages since deleted, and those of notebooks since closed, are passed over.
248244 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);
250246 let visit = trail.step(forward, |visit| self.find(visit).is_some());
251 self.trail = trail;
247 self.pane.trail = trail;
252248 if let Some((library, path, space)) = visit.and_then(|visit| self.find(&visit)) {
253249 self.go(library, path, space);
254250 }
......@@ -418,16 +414,16 @@ mod tests {
418414
419415 #[test]
420416 fn recent_pages_are_kept_once_latest_first_and_bounded() {
421 let mut trail = Trail::default();
417 let mut recent = Vec::new();
422418 for page in [1, 2, 1] {
423 trail.remember(place("A.one", page));
419 remember(&mut recent, place("A.one", page));
424420 }
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)]);
426422 for page in 0..20 {
427 trail.remember(place("B.one", page));
423 remember(&mut recent, place("B.one", page));
428424 }
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));
431427 }
432428
433429 #[test]
......@@ -452,7 +448,7 @@ mod tests {
452448 &page("Kept"),
453449 0,
454450 ));
455 let mut trail = Trail::default();
451 let mut recent = Vec::new();
456452 // Visited oldest first: the index has read A without page 2, not yet B, and C is gone.
457453 for gone in [
458454 place("C.one", 1),
......@@ -460,12 +456,12 @@ mod tests {
460456 place("A.one", 2),
461457 kept.clone(),
462458 ] {
463 trail.remember(gone);
459 remember(&mut recent, gone);
464460 }
465461 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");
469465 }
470466
471467 #[test]
crates/snowbound/src/options.rs+4-4
......@@ -390,10 +390,10 @@ impl State {
390390 color_scheme: self.color_scheme,
391391 pages_match: !self.light_pages,
392392 hide_spelling: self.hide_spelling,
393 markdown: self.view.editor.markdown.is_some(),
393 markdown: self.pane.view.editor.markdown.is_some(),
394394 automatic_updates: self.updates.automatic(),
395395 pen_pressure: self.pen_pressure,
396 default_font: self.view.editor.default_font.clone(),
396 default_font: self.pane.view.editor.default_font.clone(),
397397 page_tabs_left: self.page_tabs_left,
398398 navigation_bar_left: !self.navigation_bar_right,
399399 lock_after: self.passwords.lock_after,
......@@ -692,9 +692,9 @@ impl State {
692692 self.color_scheme = options.color_scheme;
693693 self.light_pages = !options.pages_match;
694694 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);
696696 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;
698698 self.page_tabs_left = options.page_tabs_left;
699699 self.navigation_bar_right = !options.navigation_bar_left;
700700 self.passwords = crate::settings::Passwords {
crates/snowbound/src/palette.rs+5-5
......@@ -114,7 +114,7 @@ impl State {
114114 }
115115 }
116116 // 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();
118118 for (item, title) in text.iter().flat_map(|context| self.text_items(context)) {
119119 if !(commands::COMMANDS.iter()).any(|command| command.title == title) {
120120 commands.push(Row {
......@@ -334,7 +334,7 @@ impl State {
334334 Target::Page {
335335 library: Arc::clone(library),
336336 path: path.clone(),
337 space: session.space,
337 space: self.pane.showing.space,
338338 },
339339 Target::Section {
340340 library: Arc::clone(library),
......@@ -367,7 +367,7 @@ impl State {
367367 .map(Arc::clone)
368368 };
369369 // 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| {
371371 library(&place.notebook)
372372 .is_none_or(|library| library.notebook.is_err() || library.contains(&place.section))
373373 });
......@@ -375,7 +375,7 @@ impl State {
375375 (
376376 session.library.location.as_str(),
377377 session.tabs[session.tab].path.as_str(),
378 session.space,
378 self.pane.showing.space,
379379 )
380380 });
381381 let theme = &self.ui.theme;
......@@ -393,7 +393,7 @@ impl State {
393393 _ => false,
394394 })
395395 };
396 for place in &self.trail.recent {
396 for place in &self.recent {
397397 let Some(library) = library(&place.notebook) else {
398398 let target = Target::Closed(place.notebook.clone());
399399 if !listed(&closed, &target) {
crates/snowbound/src/paste.rs+7-6
......@@ -124,7 +124,7 @@ impl State {
124124 }
125125 }
126126 Some(Pasted::Clip(clip)) => {
127 let response = self.view.paste_clip(clip)?;
127 let response = self.pane.view.paste_clip(clip)?;
128128 self.respond(response);
129129 }
130130 Some(Pasted::Page(html, text)) => {
......@@ -142,12 +142,12 @@ impl State {
142142 });
143143 if pieces.is_empty() {
144144 if let Some(text) = text {
145 let response = self.view.paste(&text, language)?;
145 let response = self.pane.view.paste(&text, language)?;
146146 self.respond(response);
147147 }
148148 return Ok(());
149149 }
150 let (places, response) = self.view.paste_pieces(pieces)?;
150 let (places, response) = self.pane.view.paste_pieces(pieces)?;
151151 self.respond(response);
152152 for (place, (source, css)) in places.into_iter().zip(fetched) {
153153 let proxy = self.proxy.clone();
......@@ -159,7 +159,7 @@ impl State {
159159 return;
160160 };
161161 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)?;
163163 state.respond(response);
164164 Ok(())
165165 })));
......@@ -168,7 +168,7 @@ impl State {
168168 }
169169 Some(Pasted::Text(text)) => {
170170 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)?;
172172 self.respond(response);
173173 }
174174 Some(Pasted::Picture(bytes)) => self.insert_picture(bytes, None)?,
......@@ -194,9 +194,10 @@ impl State {
194194 let size = picture_size(&bytes, pixels);
195195 let response = match at {
196196 Some(point) => self
197 .pane
197198 .view
198199 .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)?,
200201 };
201202 self.respond(response);
202203 Ok(())
crates/snowbound/src/prefetch.rs+7-7
......@@ -277,7 +277,7 @@ impl State {
277277 /// The section tab shown open: one being opened, from the moment it is asked for.
278278 pub fn open_tab(&self) -> Option<usize> {
279279 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);
281281 Some(
282282 opening
283283 .filter(|tab| *tab < session.tabs.len())
......@@ -287,15 +287,15 @@ impl State {
287287
288288 /// Since when the page area shows that a page is opening, once it has taken a while.
289289 pub fn loading(&self) -> Option<Instant> {
290 let (_, since) = self.switching?;
290 let (_, since) = self.pane.switching?;
291291 (since.elapsed() >= PATIENCE).then_some(since)
292292 }
293293
294294 /// Leaves the page shown as it is, dropping whatever was opening in its place.
295295 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;
299299 }
300300
301301 /// Readies the pages beside the open one and the sections beside the open section.
......@@ -306,7 +306,7 @@ impl State {
306306 let at = session
307307 .pages
308308 .iter()
309 .position(|(space, ..)| *space == session.space);
309 .position(|(space, ..)| *space == self.pane.showing.space);
310310 let pages: Vec<ExGuid> = at
311311 .into_iter()
312312 .flat_map(|at| [at + 1, at.wrapping_sub(1)])
......@@ -330,7 +330,7 @@ impl State {
330330 return;
331331 };
332332 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))) {
334334 return;
335335 }
336336 let read = session.reader(space);
crates/snowbound/src/print.rs+3-3
......@@ -281,11 +281,11 @@ impl State {
281281 let session = self.session.as_ref().ok_or("No section is open")?;
282282 let tab = &session.tabs[session.tab];
283283 // 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()?);
285285 let at = session
286286 .pages
287287 .iter()
288 .position(|(space, ..)| *space == session.space);
288 .position(|(space, ..)| *space == self.pane.showing.space);
289289 let spaces: Vec<ExGuid> = match (setup.scope, at) {
290290 (Scope::PageGroup, Some(at)) => {
291291 let levels: Vec<u32> = session.pages.iter().map(|(.., level)| *level).collect();
......@@ -297,7 +297,7 @@ impl State {
297297 (Scope::Section | Scope::Notebook, _) => {
298298 session.pages.iter().map(|(space, ..)| *space).collect()
299299 }
300 _ => vec![session.space],
300 _ => vec![self.pane.showing.space],
301301 };
302302 let title = match setup.scope {
303303 Scope::Page | Scope::PageGroup => open.1.title.clone(),
crates/snowbound/src/protection.rs+5-5
......@@ -89,8 +89,8 @@ impl State {
8989 previous.library.keep(path, previous.section);
9090 }
9191 }
92 self.sectionless = None;
93 self.locked = Some(Locked { library, path });
92 self.pane.sectionless = None;
93 self.pane.locked = Some(Locked { library, path });
9494 self.ui.set_focus(Some(notice()));
9595 self.title();
9696 Ok(())
......@@ -113,7 +113,7 @@ impl State {
113113 section: &ui::Section,
114114 strip: [f32; 4],
115115 ) -> (Option<Command>, Id) {
116 let Some(locked) = &self.locked else {
116 let Some(locked) = &self.pane.locked else {
117117 return (None, row);
118118 };
119119 let library = Arc::clone(&locked.library);
......@@ -157,7 +157,7 @@ impl State {
157157
158158 /// The locked section's page, as OneNote 2010 draws it: a click or Enter unlocks.
159159 pub(crate) fn locked_page(&mut self) {
160 let Some(locked) = &self.locked else {
160 let Some(locked) = &self.pane.locked else {
161161 return;
162162 };
163163 let (library, path) = (Arc::clone(&locked.library), locked.path.clone());
......@@ -266,7 +266,7 @@ impl State {
266266
267267 /// The section shown, open or locked, with its notebook.
268268 pub(crate) fn shown_section(&self) -> Option<(Arc<Library>, String)> {
269 match (&self.session, &self.locked) {
269 match (&self.session, &self.pane.locked) {
270270 (Some(session), _) if session.library.catalog().is_some() => Some((
271271 Arc::clone(&session.library),
272272 session.tabs[session.tab].path.clone(),
crates/snowbound/src/recording.rs+15-15
......@@ -93,7 +93,7 @@ impl State {
9393 };
9494 let [date, time] = platform::date_text(crate::filetime());
9595 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)?;
9797 self.respond(response);
9898 if id.is_none() {
9999 recorder.stop()?()?;
......@@ -117,7 +117,7 @@ impl State {
117117 path,
118118 } => {
119119 // 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);
121121 let finish = match recorder.stop() {
122122 Ok(finish) => finish,
123123 Err(detail) => {
......@@ -126,8 +126,8 @@ impl State {
126126 }
127127 };
128128 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;
131131 let job = std::thread::spawn(move || {
132132 finish()?;
133133 let recorded =
......@@ -150,7 +150,7 @@ impl State {
150150 let saved = job
151151 .join()
152152 .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() {
154154 return Ok(());
155155 }
156156 let mut file = match saved {
......@@ -162,7 +162,7 @@ impl State {
162162 };
163163 file.preview = platform::file_icon(&path).map(Into::into);
164164 notebook::fs::remove_file(&path)?;
165 let response = self.view.finish_recording(file)?;
165 let response = self.pane.view.finish_recording(file)?;
166166 self.respond(response);
167167 Ok(())
168168 }
......@@ -225,7 +225,7 @@ impl State {
225225 let (enabled, checked) = match transport {
226226 Transport::Pause => (
227227 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)),
229229 ),
230230 Transport::Stop => (recording || playing.is_some(), None),
231231 Transport::Skip(_) | Transport::SeekTo => (playing.is_some(), None),
......@@ -241,9 +241,9 @@ impl State {
241241 (Transport::Stop, Media::Recording { .. }) => self.stop_recording(false)?,
242242 (Transport::Stop, Media::Playing(_)) => self.media = Media::Idle,
243243 (Transport::Pause, Media::Recording { recorder, .. }) => {
244 let paused = !self.view.editor.recording_paused();
244 let paused = !self.pane.view.editor.recording_paused();
245245 recorder.pause(paused);
246 self.view.editor.pause_recording(paused);
246 self.pane.view.editor.pause_recording(paused);
247247 }
248248 (Transport::Pause, Media::Playing(playback)) => {
249249 let player = &mut playback.player;
......@@ -281,13 +281,13 @@ impl State {
281281 let mut played = None;
282282 let (clock_text, picture) = match &mut self.media {
283283 Media::Idle => {
284 let response = self.view.set_played(None)?;
284 let response = self.pane.view.set_played(None)?;
285285 self.respond(response);
286286 return Ok(());
287287 }
288288 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() {
291291 "Paused"
292292 } else if *video {
293293 "Recording video"
......@@ -311,8 +311,8 @@ impl State {
311311 played = playback
312312 .recording
313313 .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));
316316 let mut picture = None;
317317 if let Some(video) = &mut playback.video {
318318 let frame = video.movie.frame(at);
......@@ -344,7 +344,7 @@ impl State {
344344 )
345345 }
346346 };
347 let response = self.view.set_played(played)?;
347 let response = self.pane.view.set_played(played)?;
348348 self.respond(response);
349349 let picture_height = picture
350350 .as_ref()
crates/snowbound/src/recycle.rs+5-5
......@@ -67,8 +67,8 @@ impl State {
6767 section.close_async().await?;
6868 continue;
6969 }
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));
7272 }
7373 Err("The Recycle Bin is empty.".into())
7474 });
......@@ -127,7 +127,7 @@ impl State {
127127 let session = self.session.as_ref().ok_or("No section is open")?;
128128 let replica = Arc::clone(session.section.replica());
129129 let library = Arc::clone(&session.library);
130 let shown = session.space;
130 let shown = self.pane.showing.space;
131131 let at = (session.pages.iter()).position(|(space, ..)| gone.contains(space));
132132 let kept = |space: &ExGuid| !gone.contains(space);
133133 let after = at.and_then(|at| {
......@@ -149,8 +149,8 @@ impl State {
149149 .first_section()
150150 .ok_or("The notebook has no sections")?;
151151 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))
154154 });
155155 Ok(())
156156 }
crates/snowbound/src/save_as.rs+6-2
......@@ -166,11 +166,15 @@ impl State {
166166 .filter(|session| Arc::ptr_eq(&session.library, &library))
167167 .map(|session| {
168168 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 )
170174 });
171175 let section = dialog.section.unwrap_or_default();
172176 let title = match dialog.scope {
173 Scope::Page => self.view.editor.page()?.title,
177 Scope::Page => self.pane.view.editor.page()?.title,
174178 Scope::Section => crate::library::section_name(&section, &None),
175179 Scope::Notebook => library.name.clone(),
176180 };
crates/snowbound/src/screenshot.rs+9-4
......@@ -59,17 +59,18 @@ impl State {
5959 };
6060 self.surface.size = size.map(|side| (side * scale).max(1.0) as u32);
6161 self.renderer.clear_glyph_cache();
62 let response = self.view.scale_factor_changed(scale)?;
62 let response = self.pane.view.scale_factor_changed(scale)?;
6363 self.respond(response);
6464 self.ui.set_focus(None);
6565 if let Some(title) = self
66 .pane
6667 .view
6768 .editor
6869 .outlines()
6970 .iter()
7071 .find(|outline| outline.title)
7172 {
72 self.view.editor.focus_outline(title.id)?;
73 self.pane.view.editor.focus_outline(title.id)?;
7374 }
7475 #[cfg(target_os = "macos")]
7576 if snow_leopard() {
......@@ -99,8 +100,12 @@ impl State {
99100 // The page's box sizes the view whose pictures settle.
100101 self.layout(size, scale)?;
101102 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 );
104109 }
105110 // Later frames lay out with earlier frames' measurements while colours ease.
106111 let deadline = Instant::now() + std::time::Duration::from_secs(2);
crates/snowbound/src/search.rs+15-15
......@@ -614,26 +614,26 @@ impl State {
614614 /// Marks the query's matches on the page; with `select`, selects the match Find on
615615 /// This Page is at, or the first, or the paragraph, when a result opened the page.
616616 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());
618618 if !select {
619619 return Ok(());
620620 }
621 let space = self.session.as_ref().map(|session| session.space);
621 let space = self.session.as_ref().map(|_| self.pane.showing.space);
622622 let target = if self.search.finding {
623623 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),
626626 };
627627 self.search
628628 .current
629 .and_then(|at| self.view.found.get(at).copied())
629 .and_then(|at| self.pane.view.found.get(at).copied())
630630 } else if let Some((_, paragraph)) =
631631 self.search.reveal.filter(|(page, _)| Some(*page) == space)
632632 {
633633 self.search.reveal = None;
634634 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(),
637637 }
638638 } else {
639639 None
......@@ -645,9 +645,9 @@ impl State {
645645 let Some((outline, selection)) = target else {
646646 return Ok(());
647647 };
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()?;
651651 self.respond(response);
652652 Ok(())
653653 }
......@@ -686,7 +686,7 @@ impl State {
686686 if !matches!(self.search.task_pane, Some(TaskPane::Search { .. })) {
687687 self.search.query.clear();
688688 self.search.scope = self.search.default;
689 self.view.found.clear();
689 self.pane.view.found.clear();
690690 }
691691 }
692692
......@@ -711,7 +711,7 @@ impl State {
711711 let open = self
712712 .session
713713 .as_ref()
714 .map(|session| (session.key(), session.space));
714 .map(|session| (session.key(), self.pane.showing.space));
715715 match open {
716716 Some((key, shown)) if key == section && shown == space => self.refind(true)?,
717717 Some((key, _)) if key == section => self.commands.push(Command::OpenPage(space)),
......@@ -1130,7 +1130,7 @@ impl State {
11301130 &[NamedKey::Enter, NamedKey::F3, NamedKey::Escape],
11311131 );
11321132 let before = self.search.query.clone();
1133 let count = self.view.found.len();
1133 let count = self.pane.view.found.len();
11341134 let label = if self.search.query.trim().is_empty() {
11351135 "Find on page".to_owned()
11361136 } else if count == 0 {
......@@ -1211,7 +1211,7 @@ impl State {
12111211 self.end_search();
12121212 return Ok(());
12131213 }
1214 let shift = self.view.modifiers().shift;
1214 let shift = self.pane.view.modifiers().shift;
12151215 for key in keys {
12161216 match key {
12171217 NamedKey::Escape => {
......@@ -1234,7 +1234,7 @@ impl State {
12341234 (at + count - 1) % count
12351235 };
12361236 self.search.current = Some(next);
1237 self.select(self.view.found.get(next).copied())?;
1237 self.select(self.pane.view.found.get(next).copied())?;
12381238 }
12391239 Ok(())
12401240 }
crates/snowbound/src/settings.rs+4-4
......@@ -307,18 +307,18 @@ impl crate::State {
307307 color_scheme: self.color_scheme,
308308 light_pages: self.light_pages,
309309 hide_spelling: self.hide_spelling,
310 ignore_markdown: self.view.editor.markdown.is_none(),
310 ignore_markdown: self.pane.view.editor.markdown.is_none(),
311311 search_scope: self.search.default,
312312 tags: (self.tags != canvas::editor::NoteTag::defaults()).then(|| self.tags.clone()),
313313 manual_updates: !self.updates.automatic(),
314314 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(),
317317 page_tabs_left: self.page_tabs_left,
318318 navigation_bar_right: self.navigation_bar_right,
319319 servers: self.servers.clone(),
320320 keys: crate::commands::Keymap::current().saved(),
321 recent: self.trail.recent.clone(),
321 recent: self.recent.clone(),
322322 passwords: self.passwords,
323323 notebook_theme: NotebookTheme(self.notebook_theme.clone()),
324324 live: self.live_options.clone(),
crates/snowbound/src/sidebar.rs+4-4
......@@ -941,7 +941,7 @@ impl crate::State {
941941 },
942942 );
943943 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) {
945945 (Some(session), _) => Some((&session.library, session.tabs[session.tab].path.as_str())),
946946 (None, Some(locked)) => Some((&locked.library, locked.path.as_str())),
947947 (None, None) => None,
......@@ -1444,7 +1444,7 @@ impl crate::State {
14441444 read: impl FnOnce(&str, &std::path::Path) -> Result<Library, String> + Send + 'static,
14451445 ) {
14461446 let section = section.or_else(|| {
1447 (self.trail.recent.iter())
1447 (self.recent.iter())
14481448 .find(|place| place.notebook == location)
14491449 .map(|place| place.section.clone())
14501450 });
......@@ -1477,8 +1477,8 @@ impl crate::State {
14771477 return Err("This folder holds no notebook sections.".into());
14781478 };
14791479 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))
14821482 });
14831483 }
14841484
crates/snowbound/src/task_pane.rs+4-4
......@@ -445,7 +445,7 @@ impl State {
445445
446446 /// Shows the next marked word in the Spelling pane; past the last, the check is complete.
447447 fn next_word(&mut self) {
448 match self.view.next_correction() {
448 match self.pane.view.next_correction() {
449449 Ok(Some((response, correction))) => {
450450 self.respond(response);
451451 self.correction = Some(correction);
......@@ -552,7 +552,7 @@ impl State {
552552 else {
553553 return;
554554 };
555 let spelling = self.view.spelling.clone();
555 let spelling = self.pane.view.spelling.clone();
556556 let text = match (correction.repeated, pressed) {
557557 (_, _) if change => correction.suggestions.get(chosen).map(String::as_str),
558558 (true, [true, _]) => Some(""),
......@@ -566,7 +566,7 @@ impl State {
566566 }
567567 };
568568 if let Some(text) = text {
569 match self.view.correct(&correction, text) {
569 match self.pane.view.correct(&correction, text) {
570570 Ok(response) => self.respond(response),
571571 Err(error) => eprintln!("{error}"),
572572 }
......@@ -936,7 +936,7 @@ impl State {
936936 let pages = &session.pages;
937937 let at = pages
938938 .iter()
939 .position(|(space, ..)| *space == session.space)?;
939 .position(|(space, ..)| *space == self.pane.showing.space)?;
940940 let base = pages.iter().map(|(.., level)| *level).min()?;
941941 let start = pages[..=at]
942942 .iter()
crates/snowbound/src/templates.rs+4-4
......@@ -387,8 +387,8 @@ impl crate::State {
387387 let Some(session) = &self.session else {
388388 return;
389389 };
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);
392392 let Some(rect) = self.ui.laid_out(crate::page()) else {
393393 return;
394394 };
......@@ -409,7 +409,7 @@ impl crate::State {
409409 ) {
410410 self.choose(crate::commands::Choice::Art(Some(name)));
411411 }
412 let editor = &self.view.editor;
412 let editor = &self.pane.view.editor;
413413 let blank = editor
414414 .visible_outlines()
415415 .chain(editor.caret_outline())
......@@ -422,7 +422,7 @@ impl crate::State {
422422 return;
423423 };
424424 let (color, rule_lines) = (editor.page_color(), editor.rule_lines());
425 let viewport = self.view.viewport;
425 let viewport = self.pane.view.viewport;
426426 let scale = self.ui.scale();
427427 let position = [
428428 (viewport.origin[0] + start[0] * viewport.scale) / scale,
crates/snowbound/src/themes.rs+6-6
......@@ -120,7 +120,7 @@ impl State {
120120 let library = &session.library;
121121 library.catalog()?;
122122 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()?),
124124 Scope::Section => {
125125 stored::Scope::section(library.section_identity(&session.tabs[session.tab].path)?)
126126 }
......@@ -144,7 +144,7 @@ impl State {
144144 session
145145 .library
146146 .themes()
147 .effective(section, self.view.editor.identity())
147 .effective(section, self.pane.view.editor.identity())
148148 }
149149
150150 /// The styles the gallery offers: the page's theme's, else OneNote 2010's.
......@@ -167,17 +167,17 @@ impl State {
167167 let sheet = (theme.as_ref())
168168 .map(|theme| theme.sheet(self.section_color()))
169169 .unwrap_or_default();
170 if !force && self.view.editor.styles == sheet {
170 if !force && self.pane.view.editor.styles == sheet {
171171 return Ok(());
172172 }
173 self.view.editor.styles = sheet.clone();
173 self.pane.view.editor.styles = sheet.clone();
174174 let Some(session) = &self.session else {
175175 return Ok(());
176176 };
177 if theme.is_none() || session.read_only() {
177 if theme.is_none() || session.read_only(&self.pane.showing) {
178178 return Ok(());
179179 }
180 let page = session.section.page(session.space)?;
180 let page = session.section.page(self.pane.showing.space)?;
181181 let ops = onestore::op::restyle(&page, &sheet)?;
182182 if ops.is_empty() {
183183 return Ok(());
crates/snowbound/src/undo.rs+10-9
......@@ -958,13 +958,13 @@ impl State {
958958
959959 /// Whether no action, page or command is on its way, which Undo and Redo wait for.
960960 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()
962962 }
963963
964964 /// Whether Undo, or `redo` Redo, has something to take, or may once what is on its way
965965 /// lands.
966966 pub(crate) fn can_step(&self, redo: bool) -> bool {
967 let editor = &self.view.editor;
967 let editor = &self.pane.view.editor;
968968 !self.settled()
969969 || (if redo {
970970 editor.can_redo()
......@@ -998,9 +998,9 @@ impl State {
998998 /// user is, whichever came last.
999999 fn take(&mut self, redo: bool) -> Result<(), Box<dyn Error>> {
10001000 self.persist()?;
1001 let depth = self.view.editor.history_depth();
1001 let depth = self.pane.view.editor.history_depth();
10021002 // 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);
10041004 let next = match (self.here(), shown) {
10051005 (Some(here), Some(page)) => self.undo.next(redo, page, depth, &here),
10061006 _ => Some(Next::Edit),
......@@ -1008,10 +1008,10 @@ impl State {
10081008 match next {
10091009 None => {}
10101010 Some(Next::Edit) => {
1011 let response = self.view.undo(redo)?;
1011 let response = self.pane.view.undo(redo)?;
10121012 self.respond(response);
10131013 if let Some(page) = shown
1014 && self.view.editor.history_depth() != depth
1014 && self.pane.view.editor.history_depth() != depth
10151015 {
10161016 self.undo.stepped(page, redo);
10171017 }
......@@ -1117,11 +1117,11 @@ impl State {
11171117 author: self.author.clone(),
11181118 fresh: self.dated_page(None, "")?,
11191119 };
1120 let shown = session.space;
1120 let shown = self.pane.showing.space;
11211121 let original = Arc::clone(&site.library);
11221122 let proxy = self.proxy.clone();
11231123 self.undo.busy += 1;
1124 self.switching = Some((None, web_time::Instant::now()));
1124 self.pane.switching = Some((None, web_time::Instant::now()));
11251125 self.load_async(move || async move {
11261126 let applied = site.change(change, shown).await;
11271127 let show = match &applied {
......@@ -1150,11 +1150,12 @@ impl State {
11501150 } else {
11511151 state.list(&library);
11521152 if state
1153 .pane
11531154 .sectionless
11541155 .as_ref()
11551156 .is_some_and(|listed| lineage(listed))
11561157 {
1157 state.sectionless = Some(library);
1158 state.pane.sectionless = Some(library);
11581159 }
11591160 }
11601161 }
crates/snowbound/src/unread.rs+8-3
......@@ -274,8 +274,13 @@ impl State {
274274 let viewing = self.session.as_ref().and_then(|session| {
275275 let path = &session.tabs[session.tab].path;
276276 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 })
279284 });
280285 let same = |(notebook, section, space, _): &(String, [u8; 16], ExGuid, bool)| {
281286 viewing.as_ref() == Some(&(notebook.clone(), *section, *space))
......@@ -418,7 +423,7 @@ impl State {
418423 let at = session
419424 .pages
420425 .iter()
421 .position(|(space, ..)| *space == session.space);
426 .position(|(space, ..)| *space == self.pane.showing.space);
422427 let after = (session.pages.iter().skip(at.map_or(0, |at| at + 1)))
423428 .find(|(space, ..)| unread.contains(space));
424429 if let Some((space, ..)) = after {
crates/snowbound/src/web.rs+10-10
......@@ -999,7 +999,7 @@ pub async fn start(
999999 if let Some(resume) = resumed().and_then(|resume| serde_json::from_str::<Resume>(&resume).ok())
10001000 {
10011001 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) {
10031003 Ok(response) => state.respond(response),
10041004 Err(error) => report(error),
10051005 }
......@@ -1571,13 +1571,13 @@ fn turn(state: &mut State) -> Result<(), Box<dyn Error>> {
15711571 state.frame()?;
15721572 remember_look(state);
15731573 }
1574 let space = state.session.as_ref().map(|session| session.space);
1574 let space = state.session.as_ref().map(|_| state.pane.showing.space);
15751575 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));
15771577 }
15781578 park_input(state);
15791579 let now = Instant::now();
1580 let (repaint, blink) = state.view.blink(now);
1580 let (repaint, blink) = state.pane.view.blink(now);
15811581 let wake = state.ui.wake_at();
15821582 if repaint || wake.is_some_and(|wake| wake <= now) {
15831583 state.window.request_redraw();
......@@ -1709,7 +1709,7 @@ pub fn update_now(state: &mut State) {
17091709 return RELOADING.set(false);
17101710 };
17111711 let resume = Resume {
1712 offset: state.view.viewport.origin.map(|origin| -origin),
1712 offset: state.pane.view.viewport.origin.map(|origin| -origin),
17131713 build,
17141714 };
17151715 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> {
17371737 }
17381738 let idle = LAST_INPUT.get().map_or(now, |last| last + IDLE);
17391739 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
17421742 && matches!(state.media, crate::recording::Media::Idle);
17431743 if update_ready() && (returned || idle <= now) && quiet {
17441744 update_now(state);
......@@ -1783,7 +1783,7 @@ pub fn font_arrived(name: String, bytes: Vec<u8>) {
17831783 families
17841784 };
17851785 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());
17871787 if let Ok(mut layouts) = state.layouts.lock() {
17881788 register(&mut layouts.fonts.collection, face.clone());
17891789 }
......@@ -1792,7 +1792,7 @@ pub fn font_arrived(name: String, bytes: Vec<u8>) {
17921792 }
17931793 state.ui.fall_back_to(&families);
17941794 state.renderer.clear_glyph_cache();
1795 let response = state.view.relayout()?;
1795 let response = state.pane.view.relayout()?;
17961796 state.respond(response);
17971797 state.window.request_redraw();
17981798 Ok(())
......@@ -1816,7 +1816,7 @@ pub fn dictionary_arrived(name: String, affix: String, words: String) {
18161816/// Parks the text area while the page takes no text, keeping it writable for the
18171817/// interface's fields.
18181818fn 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() {
18201820 place_input(0.0, 0.0, 1.0, state.ui.focused().is_some());
18211821 }
18221822}