authorgravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-10-02 11:45:09-07:00
committergravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-10-02 12:53:12-07:00
log678a46da0816b22d6b37cd361abf395870595796
tree103a141a38b8f394a055b5e20f7009207597b104
parentf4abeb10328178d4685cc9788c296516e6b186a6
signature Signed by SSH key SHA256:52mNGHRsVFBDED9IAX5pe+LRWUefqTbxEReunq21QvU

fix: what the page area shows in place of a page takes the page's colours

In a dark appearance with Pages Match UI Theme off, the locked section's notice drew the dark theme's light text on the light page, and the welcome and empty notebook notices sat on the chrome's surfaces, which a translucent backdrop lets the desktop show through. They now fill with the page's colour and build their text and buttons in the theme matching it: the light theme's on white pages. Fixes #67. Assisted-by: claude-opus-5.5

3 files changed, 30 insertions(+), 14 deletions(-)

crates/snowbound/src/main.rs+17-3
...@@ -1474,7 +1474,7 @@ impl State {...@@ -1474,7 +1474,7 @@ impl State {
1474 && self.locked.is_none();1474 && self.locked.is_none();
1475 self.toolbar(&theme, !welcome)?;1475 self.toolbar(&theme, !welcome)?;
1476 if welcome {1476 if welcome {
1477 self.welcome(&theme);1477 self.welcome();
1478 return Ok((section, Id::ROOT, None));1478 return Ok((section, Id::ROOT, None));
1479 }1479 }
1480 self.ui.open(1480 self.ui.open(
...@@ -1704,7 +1704,7 @@ impl State {...@@ -1704,7 +1704,7 @@ impl State {
1704 recycle::bar(&mut self.ui);1704 recycle::bar(&mut self.ui);
1705 }1705 }
1706 if self.session.is_none() && self.locked.is_some() {1706 if self.session.is_none() && self.locked.is_some() {
1707 self.locked_page(&theme);1707 self.locked_page();
1708 self.ui.close();1708 self.ui.close();
1709 self.ui.close();1709 self.ui.close();
1710 self.ui.close();1710 self.ui.close();
...@@ -1717,7 +1717,7 @@ impl State {...@@ -1717,7 +1717,7 @@ impl State {
1717 return Ok((section, open_tab, None));1717 return Ok((section, open_tab, None));
1718 }1718 }
1719 if let Some(library) = self.sectionless.clone().filter(|_| self.session.is_none()) {1719 if let Some(library) = self.sectionless.clone().filter(|_| self.session.is_none()) {
1720 self.no_sections(&theme, library);1720 self.no_sections(library);
1721 self.ui.close();1721 self.ui.close();
1722 self.ui.close();1722 self.ui.close();
1723 self.ui.close();1723 self.ui.close();
...@@ -4193,6 +4193,20 @@ impl State {...@@ -4193,6 +4193,20 @@ impl State {
4193 .colored(self.view.editor.page_color())4193 .colored(self.view.editor.page_color())
4194 }4194 }
41954195
4196 /// The colours of what the page area shows in place of a page, which follow the page's:
4197 /// the light theme's on white pages, as Pages Match UI Theme off keeps them in a dark
4198 /// appearance.
4199 pub(crate) fn page_area_theme(&self) -> Theme {
4200 if self.light_pages {
4201 Theme {
4202 accent: self.ui.theme.accent,
4203 ..Theme::light()
4204 }
4205 } else {
4206 self.ui.theme.clone()
4207 }
4208 }
4209
4196 /// Paints the interface with the page in its box.4210 /// Paints the interface with the page in its box.
4197 fn paint(&mut self, target: &draw::Target) -> Result<(), Box<dyn Error>> {4211 fn paint(&mut self, target: &draw::Target) -> Result<(), Box<dyn Error>> {
4198 let start = Instant::now();4212 let start = Instant::now();
crates/snowbound/src/protection.rs+5-2
...@@ -2,7 +2,7 @@...@@ -2,7 +2,7 @@
2//! dialogs that unlock one and set, change or remove its password, Lock All, and locking a2//! dialogs that unlock one and set, change or remove its password, Lock All, and locking a
3//! section after a while unused or as it is left. Keys live in memory only (`Library`).3//! section after a while unused or as it is left. Keys live in memory only (`Library`).
44
5use crate::{Command, Library, State, Theme, art, manage::Structure};5use crate::{Command, Library, State, art, manage::Structure};
6use accesskit::Role;6use accesskit::Role;
7use std::{error::Error, sync::Arc, time::Duration};7use std::{error::Error, sync::Arc, time::Duration};
8use ui::{Anchor, Axis, Flags, Id, Spec, Ui, children, fill, px};8use ui::{Anchor, Axis, Flags, Id, Spec, Ui, children, fill, px};
...@@ -155,11 +155,13 @@ impl State {...@@ -155,11 +155,13 @@ impl State {
155 }155 }
156156
157 /// The locked section's page, as OneNote 2010 draws it: a click or Enter unlocks.157 /// The locked section's page, as OneNote 2010 draws it: a click or Enter unlocks.
158 pub(crate) fn locked_page(&mut self, theme: &Theme) {158 pub(crate) fn locked_page(&mut self) {
159 let Some(locked) = &self.locked else {159 let Some(locked) = &self.locked else {
160 return;160 return;
161 };161 };
162 let (library, path) = (Arc::clone(&locked.library), locked.path.clone());162 let (library, path) = (Arc::clone(&locked.library), locked.path.clone());
163 let theme = &self.page_area_theme();
164 let chrome = std::mem::replace(&mut self.ui.theme, theme.clone());
163 let ui = &mut self.ui;165 let ui = &mut self.ui;
164 let entered =166 let entered =
165 ui::popup::navigation(ui, &[notice()], &[NamedKey::Enter]).contains(&NamedKey::Enter);167 ui::popup::navigation(ui, &[notice()], &[NamedKey::Enter]).contains(&NamedKey::Enter);
...@@ -229,6 +231,7 @@ impl State {...@@ -229,6 +231,7 @@ impl State {
229 ui.close();231 ui.close();
230 ui.close();232 ui.close();
231 ui.close();233 ui.close();
234 ui.theme = chrome;
232 if ui.signal(open).clicked || entered {235 if ui.signal(open).clicked || entered {
233 self.ask_password(library, path, Dialog::Unlock(Zeroizing::default(), false));236 self.ask_password(library, path, Dialog::Unlock(Zeroizing::default(), false));
234 }237 }
crates/snowbound/src/sidebar.rs+8-9
...@@ -1150,7 +1150,7 @@ impl crate::State {...@@ -1150,7 +1150,7 @@ impl crate::State {
11501150
1151 /// The window below the title bar on the first run, or once every notebook is closed:1151 /// The window below the title bar on the first run, or once every notebook is closed:
1152 /// what is missing, and the ways to start.1152 /// what is missing, and the ways to start.
1153 pub(crate) fn welcome(&mut self, theme: &Theme) {1153 pub(crate) fn welcome(&mut self) {
1154 let id = self.ui.id("welcome");1154 let id = self.ui.id("welcome");
1155 #[cfg(target_os = "linux")]1155 #[cfg(target_os = "linux")]
1156 let install = crate::desktop::installable().then_some((1156 let install = crate::desktop::installable().then_some((
...@@ -1163,9 +1163,7 @@ impl crate::State {...@@ -1163,9 +1163,7 @@ impl crate::State {
1163 let install = None;1163 let install = None;
1164 let servers = self.servers.clone();1164 let servers = self.servers.clone();
1165 self.notice(1165 self.notice(
1166 theme,
1167 id,1166 id,
1168 theme.base,
1169 "No notebooks open",1167 "No notebooks open",
1170 &servers,1168 &servers,
1171 [1169 [
...@@ -1202,7 +1200,7 @@ impl crate::State {...@@ -1202,7 +1200,7 @@ impl crate::State {
12021200
1203 /// The page's place while notebook `library` has no sections, as OneNote 2010 shows1201 /// The page's place while notebook `library` has no sections, as OneNote 2010 shows
1204 /// it, with a button to add one; or while none is on this computer yet.1202 /// it, with a button to add one; or while none is on this computer yet.
1205 pub(crate) fn no_sections(&mut self, theme: &Theme, library: Arc<Library>) {1203 pub(crate) fn no_sections(&mut self, library: Arc<Library>) {
1206 let downloading = library.downloading();1204 let downloading = library.downloading();
1207 let new = crate::Command::Structure(1205 let new = crate::Command::Structure(
1208 library,1206 library,
...@@ -1219,27 +1217,27 @@ impl crate::State {...@@ -1219,27 +1217,27 @@ impl crate::State {
1219 vec![("new", art::PLUS, "New Section", new)],1217 vec![("new", art::PLUS, "New Section", new)],
1220 )1218 )
1221 };1219 };
1222 self.notice(theme, id, theme.strip, title, &[], buttons);1220 self.notice(id, title, &[], buttons);
1223 }1221 }
12241222
1225 /// A box `id` filled with `background`, showing `title` above `buttons` in its middle: each1223 /// A box `id` in the page's colours, showing `title` above `buttons` in its middle: each
1226 /// a part, an icon, a label and what it does; then the saved `servers` to reconnect to.1224 /// a part, an icon, a label and what it does; then the saved `servers` to reconnect to.
1227 fn notice(1225 fn notice(
1228 &mut self,1226 &mut self,
1229 theme: &Theme,
1230 id: ui::Id,1227 id: ui::Id,
1231 background: [f32; 4],
1232 title: &str,1228 title: &str,
1233 servers: &[String],1229 servers: &[String],
1234 buttons: Vec<(&str, &'static [&'static str], &str, crate::Command)>,1230 buttons: Vec<(&str, &'static [&'static str], &str, crate::Command)>,
1235 ) {1231 ) {
1236 const BUTTON: [f32; 2] = [240.0, 32.0];1232 const BUTTON: [f32; 2] = [240.0, 32.0];
1233 let theme = self.page_area_theme();
1234 let chrome = std::mem::replace(&mut self.ui.theme, theme.clone());
1237 let [left, top, right, bottom] = self.ui.rect(id).unwrap_or_default();1235 let [left, top, right, bottom] = self.ui.rect(id).unwrap_or_default();
1238 self.ui.open_as(1236 self.ui.open_as(
1239 id,1237 id,
1240 Spec {1238 Spec {
1241 size: [fill(), fill()],1239 size: [fill(), fill()],
1242 fill: Some(background),1240 fill: Some(theme.paper),
1243 ..Spec::default()1241 ..Spec::default()
1244 },1242 },
1245 );1243 );
...@@ -1292,6 +1290,7 @@ impl crate::State {...@@ -1292,6 +1290,7 @@ impl crate::State {
1292 let saved = crate::server::saved_servers(&mut self.ui, servers);1290 let saved = crate::server::saved_servers(&mut self.ui, servers);
1293 self.ui.close();1291 self.ui.close();
1294 self.ui.close();1292 self.ui.close();
1293 self.ui.theme = chrome;
1295 if let Some(saved) = saved {1294 if let Some(saved) = saved {
1296 self.saved_server(saved);1295 self.saved_server(saved);
1297 }1296 }