authorgravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-10-02 15:55:34-07:00
committergravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-10-02 16:11:25-07:00
logf3b369e572b4a689a2e3c0337a0ea4be5da31fd6
treed0d0009e5599beb778a54a6f4aab19cc7a37cbff
parent1a9bd5592f8f9bd6781eb91b4b2c383af90a4f9b
signature Signed by SSH key SHA256:52mNGHRsVFBDED9IAX5pe+LRWUefqTbxEReunq21QvU

feat: a notebook's colour recolours its binder art on the desktop and in iOS's list

The binder is three layers, its back and tabs, the cover currentColor paints and what lies on the cover, which iOS's asset catalog links to and composes with the cover as a template. The desktop shades the colour as it shades a section's, for each appearance. Assisted-by: claude-opus-5.5

20 files changed, 138 insertions(+), 36 deletions(-)

apps/ios/Snowbound/Assets.xcassets/Notebook.imageset/Contents.json created+16
......@@ -0,0 +1,16 @@
1{
2 "images" : [
3 {
4 "filename" : "notebook.svg",
5 "idiom" : "universal"
6 }
7 ],
8 "info" : {
9 "author" : "xcode",
10 "version" : 1
11 },
12 "properties" : {
13 "preserves-vector-representation" : true,
14 "template-rendering-intent" : "original"
15 }
16}
apps/ios/Snowbound/Assets.xcassets/Notebook.imageset/notebook.svg created+1
......@@ -0,0 +1 @@
1../../../../../crates/snowbound/assets/icons/notebook.svg
\ No newline at end of file
apps/ios/Snowbound/Assets.xcassets/NotebookBack.imageset/Contents.json created+16
......@@ -0,0 +1,16 @@
1{
2 "images" : [
3 {
4 "filename" : "notebook-back.svg",
5 "idiom" : "universal"
6 }
7 ],
8 "info" : {
9 "author" : "xcode",
10 "version" : 1
11 },
12 "properties" : {
13 "preserves-vector-representation" : true,
14 "template-rendering-intent" : "original"
15 }
16}
apps/ios/Snowbound/Assets.xcassets/NotebookBack.imageset/notebook-back.svg created+1
......@@ -0,0 +1 @@
1../../../../../crates/snowbound/assets/icons/notebook-back.svg
\ No newline at end of file
apps/ios/Snowbound/Assets.xcassets/NotebookCover.imageset/Contents.json created+16
......@@ -0,0 +1,16 @@
1{
2 "images" : [
3 {
4 "filename" : "notebook-cover.svg",
5 "idiom" : "universal"
6 }
7 ],
8 "info" : {
9 "author" : "xcode",
10 "version" : 1
11 },
12 "properties" : {
13 "preserves-vector-representation" : true,
14 "template-rendering-intent" : "template"
15 }
16}
apps/ios/Snowbound/Assets.xcassets/NotebookCover.imageset/notebook-cover.svg created+1
......@@ -0,0 +1 @@
1../../../../../crates/snowbound/assets/icons/notebook-cover.svg
\ No newline at end of file
apps/ios/Snowbound/Lists.swift+1-2
......@@ -307,8 +307,7 @@ final class NotebooksViewController: UICollectionViewController, UIDocumentPicke
307307 case .notebook(let id):
308308 guard let notebook = notebook(id) else { break }
309309 content.text = notebook.name
310 content.image = UIImage(systemName: "book.closed.fill")
311 content.imageProperties.tintColor = notebook.color
310 content.image = .notebook(notebook.color)
312311 var details: [String] = []
313312 if case .server(let server) = notebook.source { details.append(server.host) }
314313 if let state = attention[id]?.state {
apps/ios/Snowbound/Notebooks.swift+15-1
......@@ -140,6 +140,20 @@ struct Tab: Decodable, Equatable {
140140 var uiColor: UIColor { UIColor(rgb: color) }
141141}
142142
143extension UIImage {
144 /// A notebook's binder, the desktop's art, `side` points square: its cover in `color`, or
145 /// the art's own orange.
146 static func notebook(_ color: UIColor?, side: CGFloat = 28) -> UIImage {
147 let frame = CGRect(x: 0, y: 0, width: side, height: side)
148 let cover = color ?? UIColor(rgb: [0xf3, 0x9c, 0x28])
149 return UIGraphicsImageRenderer(size: frame.size).image { _ in
150 UIImage(named: "NotebookBack")?.draw(in: frame)
151 UIImage(named: "NotebookCover")?.withTintColor(cover, renderingMode: .alwaysOriginal).draw(in: frame)
152 UIImage(named: "Notebook")?.draw(in: frame)
153 }.withRenderingMode(.alwaysOriginal)
154 }
155}
156
143157extension UIViewController {
144158 /// Tells the reader `title` and `message` in an alert they dismiss with OK.
145159 func alert(_ title: String, _ message: String) {
......@@ -175,7 +189,7 @@ final class Notebook {
175189 private(set) var name: String
176190 private(set) var handle: OpaquePointer?
177191 private(set) var tabs: [Tab] = []
178 /// The notebook's colour, which tints its book in the list; none where it has none.
192 /// The notebook's colour, which its binder's cover takes in the list; none where it has none.
179193 private(set) var color: UIColor?
180194 /// Why the notebook could not be opened, while it cannot.
181195 private(set) var problem: String?
apps/ios/Snowbound/Prototypes.swift+2-2
......@@ -38,7 +38,7 @@ extension Prototype {
3838 ) -> UIContentUnavailableConfiguration? {
3939 guard Notebooks.all.isEmpty, !ICloud.signedIn || ICloud.documents != nil else { return nil }
4040 var welcome = UIContentUnavailableConfiguration.empty()
41 welcome.image = UIImage(systemName: "book.closed")
41 welcome.image = .notebook(nil, side: 64)
4242 welcome.text = "No Notebooks Yet"
4343 welcome.secondaryText =
4444 ICloud.documents != nil
......@@ -286,7 +286,7 @@ enum QuickNote {
286286 let notebooks = Notebooks.all.filter { $0.tabs.contains(where: \.readable) }
287287 let choices = notebooks.map { notebook in
288288 UIMenu(
289 title: notebook.name, image: UIImage(systemName: "book.closed"),
289 title: notebook.name, image: .notebook(notebook.color),
290290 children: notebook.tabs.filter(\.readable).map { tab in
291291 let chosen = Place(notebook: notebook.id, section: tab.path)
292292 let action = UIAction(
crates/draw/src/render/icon.rs+22-3
......@@ -33,7 +33,7 @@ impl Palette {
3333/// Rasterizes 16×16 SVG sources over each other at a whole `size` of device pixels. Paths are filled and stroked with `#rrggbb`, `currentColor`,
3434/// which paints the linear `ink`, or two-stop `linearGradient`s with SVG's defaults
3535/// (`objectBoundingBox` unless `userSpaceOnUse`, `x1`..`y2`, stop offsets, pad spread; no
36/// `gradientTransform`). `fill-opacity` and `stroke-opacity` apply, and strokes have round
36/// `gradientTransform`). `fill-opacity`, `stroke-opacity` and `stop-opacity` apply, and strokes have round
3737/// caps and joins. A slot's paths keep the shading among their colours: the hue turn,
3838/// saturation scale and lightness shift that carry the mean of the slot's colours in a
3939/// source onto the palette's colour apply to each of them. A source's `image`s of PNG
......@@ -141,7 +141,8 @@ pub(crate) fn rasterize(sources: &[&str], size: u32, ink: [f32; 3], palette: &Pa
141141 let [x, y] = [index % side as usize, index / side as usize]
142142 .map(|device| device as f32 + 0.5)
143143 .map(|device| device * 16.0 / size);
144 over(pixel, color([x, y]), alpha);
144 let color = color([x, y]);
145 over(pixel, color, alpha * color[3]);
145146 }
146147 }
147148 }
......@@ -287,8 +288,12 @@ impl Paint {
287288 .children()
288289 .filter(|node| node.has_tag_name("stop"))
289290 .map(|node| {
291 let [red, green, blue, _] = hex(node.attribute("stop-color").unwrap());
292 let opacity = node
293 .attribute("stop-opacity")
294 .map_or(1.0, |value| value.parse().unwrap());
290295 (
291 hex(node.attribute("stop-color").unwrap()),
296 [red, green, blue, opacity],
292297 node.attribute("offset")
293298 .map_or(0.0, |offset| number(offset, 1.0)),
294299 )
......@@ -567,6 +572,20 @@ mod tests {
567572 assert_eq!(alpha(1, 8), 64);
568573 }
569574
575 #[test]
576 fn stop_opacity_fades_a_gradient() {
577 const FADE: &str = r##"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><linearGradient id="g" x2="0" y2="1"><stop offset="0" stop-color="#ffffff"/><stop offset="1" stop-color="#ffffff" stop-opacity="0"/></linearGradient></defs><path d="M0 0H16V16H0Z" fill="url(#g)"/></svg>"##;
578 let image = rasterize(&[FADE], 16, [1.0; 3], &Palette::default());
579 let alpha = |y: usize| image.data[(y * 16 + 8) * 4 + 3];
580 assert!(
581 alpha(0) > 240 && alpha(15) < 15,
582 "{} {}",
583 alpha(0),
584 alpha(15)
585 );
586 assert!((i32::from(alpha(8)) - 120).abs() < 10, "{}", alpha(8));
587 }
588
570589 /// A 2×1 PNG, red then half-transparent blue, fits the icon's width, centred, and paints
571590 /// beneath the paths of later sources.
572591 #[test]
crates/snowbound/assets/icons/notebook-back.svg created+14
......@@ -0,0 +1,14 @@
1<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
2 <defs>
3 <linearGradient id="g0" x2="0" y2="1"><stop offset="0" stop-color="#ff8f80"/><stop offset="1" stop-color="#f03b30"/></linearGradient>
4 <linearGradient id="g1" x2="0" y2="1"><stop offset="0" stop-color="#86e88a"/><stop offset="1" stop-color="#28b04a"/></linearGradient>
5 <linearGradient id="g2" x2="0" y2="1"><stop offset="0" stop-color="#79c2ff"/><stop offset="1" stop-color="#1f7cf0"/></linearGradient>
6 </defs>
7 <path d="M12.1 3.75L14.4 3.75A0.6 0.6 0 0 1 15 4.35L15 5.65A0.6 0.6 0 0 1 14.4 6.25L12.1 6.25A0.6 0.6 0 0 1 11.5 5.65L11.5 4.35A0.6 0.6 0 0 1 12.1 3.75Z" fill="#000000" fill-opacity="0.16"/>
8 <path d="M12.1 3H14.4A0.6 0.6 0 0 1 15 3.6V4.9A0.6 0.6 0 0 1 14.4 5.5H12.1A0.6 0.6 0 0 1 11.5 4.9V3.6A0.6 0.6 0 0 1 12.1 3Z" fill="url(#g0)" stroke="#c42418" stroke-width="0.8" stroke-linecap="round" stroke-linejoin="round"/>
9 <path d="M12.1 7.25L14.4 7.25A0.6 0.6 0 0 1 15 7.85L15 9.15A0.6 0.6 0 0 1 14.4 9.75L12.1 9.75A0.6 0.6 0 0 1 11.5 9.15L11.5 7.85A0.6 0.6 0 0 1 12.1 7.25Z" fill="#000000" fill-opacity="0.16"/>
10 <path d="M12.1 6.5H14.4A0.6 0.6 0 0 1 15 7.1V8.4A0.6 0.6 0 0 1 14.4 9H12.1A0.6 0.6 0 0 1 11.5 8.4V7.1A0.6 0.6 0 0 1 12.1 6.5Z" fill="url(#g1)" stroke="#1b8a36" stroke-width="0.8" stroke-linecap="round" stroke-linejoin="round"/>
11 <path d="M12.1 10.75L14.4 10.75A0.6 0.6 0 0 1 15 11.35L15 12.65A0.6 0.6 0 0 1 14.4 13.25L12.1 13.25A0.6 0.6 0 0 1 11.5 12.65L11.5 11.35A0.6 0.6 0 0 1 12.1 10.75Z" fill="#000000" fill-opacity="0.16"/>
12 <path class="accent" d="M12.1 10H14.4A0.6 0.6 0 0 1 15 10.6V11.9A0.6 0.6 0 0 1 14.4 12.5H12.1A0.6 0.6 0 0 1 11.5 11.9V10.6A0.6 0.6 0 0 1 12.1 10Z" fill="url(#g2)" stroke="#1560c4" stroke-width="0.8" stroke-linecap="round" stroke-linejoin="round"/>
13 <path d="M3.5 2.25L11.5 2.25A1 1 0 0 1 12.5 3.25L12.5 14.25A1 1 0 0 1 11.5 15.25L3.5 15.25A1 1 0 0 1 2.5 14.25L2.5 3.25A1 1 0 0 1 3.5 2.25Z" fill="#000000" fill-opacity="0.16"/>
14</svg>
crates/snowbound/assets/icons/notebook-cover.svg created+3
......@@ -0,0 +1,3 @@
1<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
2 <path d="M3.5 1.5H11.5A1 1 0 0 1 12.5 2.5V13.5A1 1 0 0 1 11.5 14.5H3.5A1 1 0 0 1 2.5 13.5V2.5A1 1 0 0 1 3.5 1.5Z" fill="currentColor" stroke="currentColor" stroke-width="0.8" stroke-linecap="round" stroke-linejoin="round"/>
3</svg>
crates/snowbound/assets/icons/notebook.svg+5-16
......@@ -1,21 +1,10 @@
11<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
22 <defs>
3 <linearGradient id="g0" x2="0" y2="1"><stop offset="0" stop-color="#ff8f80"/><stop offset="1" stop-color="#f03b30"/></linearGradient>
4 <linearGradient id="g1" x2="0" y2="1"><stop offset="0" stop-color="#86e88a"/><stop offset="1" stop-color="#28b04a"/></linearGradient>
5 <linearGradient id="g2" x2="0" y2="1"><stop offset="0" stop-color="#79c2ff"/><stop offset="1" stop-color="#1f7cf0"/></linearGradient>
6 <linearGradient id="g3" x2="0" y2="1"><stop offset="0" stop-color="#ffffff"/><stop offset="1" stop-color="#000000"/></linearGradient>
7 <linearGradient id="g4" x2="0" y2="1"><stop offset="0" stop-color="#fffdf3"/><stop offset="1" stop-color="#fbeec7"/></linearGradient>
3 <linearGradient id="g0" x2="0" y2="1"><stop offset="0" stop-color="#ffffff" stop-opacity="0.4"/><stop offset="1" stop-color="#ffffff" stop-opacity="0"/></linearGradient>
4 <linearGradient id="g1" x2="0" y2="1"><stop offset="0" stop-color="#fffdf3"/><stop offset="1" stop-color="#fbeec7"/></linearGradient>
85 </defs>
9 <path d="M12.1 3.75L14.4 3.75A0.6 0.6 0 0 1 15 4.35L15 5.65A0.6 0.6 0 0 1 14.4 6.25L12.1 6.25A0.6 0.6 0 0 1 11.5 5.65L11.5 4.35A0.6 0.6 0 0 1 12.1 3.75Z" fill="#000000" fill-opacity="0.16"/>
10 <path d="M12.1 3H14.4A0.6 0.6 0 0 1 15 3.6V4.9A0.6 0.6 0 0 1 14.4 5.5H12.1A0.6 0.6 0 0 1 11.5 4.9V3.6A0.6 0.6 0 0 1 12.1 3Z" fill="url(#g0)" stroke="#c42418" stroke-width="0.8" stroke-linecap="round" stroke-linejoin="round"/>
11 <path d="M12.1 7.25L14.4 7.25A0.6 0.6 0 0 1 15 7.85L15 9.15A0.6 0.6 0 0 1 14.4 9.75L12.1 9.75A0.6 0.6 0 0 1 11.5 9.15L11.5 7.85A0.6 0.6 0 0 1 12.1 7.25Z" fill="#000000" fill-opacity="0.16"/>
12 <path d="M12.1 6.5H14.4A0.6 0.6 0 0 1 15 7.1V8.4A0.6 0.6 0 0 1 14.4 9H12.1A0.6 0.6 0 0 1 11.5 8.4V7.1A0.6 0.6 0 0 1 12.1 6.5Z" fill="url(#g1)" stroke="#1b8a36" stroke-width="0.8" stroke-linecap="round" stroke-linejoin="round"/>
13 <path d="M12.1 10.75L14.4 10.75A0.6 0.6 0 0 1 15 11.35L15 12.65A0.6 0.6 0 0 1 14.4 13.25L12.1 13.25A0.6 0.6 0 0 1 11.5 12.65L11.5 11.35A0.6 0.6 0 0 1 12.1 10.75Z" fill="#000000" fill-opacity="0.16"/>
14 <path class="accent" d="M12.1 10H14.4A0.6 0.6 0 0 1 15 10.6V11.9A0.6 0.6 0 0 1 14.4 12.5H12.1A0.6 0.6 0 0 1 11.5 11.9V10.6A0.6 0.6 0 0 1 12.1 10Z" fill="url(#g2)" stroke="#1560c4" stroke-width="0.8" stroke-linecap="round" stroke-linejoin="round"/>
15 <path d="M3.5 2.25L11.5 2.25A1 1 0 0 1 12.5 3.25L12.5 14.25A1 1 0 0 1 11.5 15.25L3.5 15.25A1 1 0 0 1 2.5 14.25L2.5 3.25A1 1 0 0 1 3.5 2.25Z" fill="#000000" fill-opacity="0.16"/>
16 <path d="M3.5 1.5H11.5A1 1 0 0 1 12.5 2.5V13.5A1 1 0 0 1 11.5 14.5H3.5A1 1 0 0 1 2.5 13.5V2.5A1 1 0 0 1 3.5 1.5Z" fill="currentColor" stroke="currentColor" stroke-width="0.8" stroke-linecap="round" stroke-linejoin="round"/>
17 <path d="M3.5 1.5H11.5A1 1 0 0 1 12.5 2.5V13.5A1 1 0 0 1 11.5 14.5H3.5A1 1 0 0 1 2.5 13.5V2.5A1 1 0 0 1 3.5 1.5Z" fill="url(#g3)" fill-opacity="0.3" stroke="#000000" stroke-opacity="0.3" stroke-width="0.8" stroke-linecap="round" stroke-linejoin="round"/>
18 <path d="M6 4H10A0.5 0.5 0 0 1 10.5 4.5V6.5A0.5 0.5 0 0 1 10 7H6A0.5 0.5 0 0 1 5.5 6.5V4.5A0.5 0.5 0 0 1 6 4Z" fill="url(#g4)"/>
19 <path d="M3.5 13.5V3A0.5 0.5 0 0 1 4 2.5H11" fill="none" stroke="#ffffff" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="0.85"/>
6 <path d="M3.5 1.5H11.5A1 1 0 0 1 12.5 2.5V13.5A1 1 0 0 1 11.5 14.5H3.5A1 1 0 0 1 2.5 13.5V2.5A1 1 0 0 1 3.5 1.5Z" fill="url(#g0)" stroke="#000000" stroke-opacity="0.22" stroke-width="0.8" stroke-linecap="round" stroke-linejoin="round"/>
7 <path d="M6 4H10A0.5 0.5 0 0 1 10.5 4.5V6.5A0.5 0.5 0 0 1 10 7H6A0.5 0.5 0 0 1 5.5 6.5V4.5A0.5 0.5 0 0 1 6 4Z" fill="url(#g1)"/>
8 <path d="M3.5 13.5V3A0.5 0.5 0 0 1 4 2.5H11" fill="none" stroke="#ffffff" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="0.6"/>
209 <path d="M1.5 3.75H4.5M1.5 6.25H4.5M1.5 8.75H4.5M1.5 11.25H4.5M1.5 13.5H4.5" fill="none" stroke="#3b4350" stroke-width="1.1" stroke-linecap="round" stroke-linejoin="round"/>
2110</svg>
crates/snowbound/src/art.rs+9-1
......@@ -27,10 +27,18 @@ pub const INDENT: &[&str] = art!("icons/indent");
2727pub const INFO: &[&str] = art!("icons/info");
2828pub const ITALIC: &[&str] = art!("icons/italic");
2929pub const LINK: &[&str] = art!("icons/link");
30pub const NOTEBOOK: &[&str] = art!("icons/notebook");
30/// A notebook: its back and tabs, the cover `currentColor` paints, and what lies on the cover.
31/// iOS layers the same files.
32pub const NOTEBOOK: &[&str] = &[
33 include_str!("../assets/icons/notebook-back.svg"),
34 include_str!("../assets/icons/notebook-cover.svg"),
35 include_str!("../assets/icons/notebook.svg"),
36];
3137/// A notebook iCloud Drive keeps: its glyph with a cloud at the corner.
3238pub const NOTEBOOK_ICLOUD: &[&str] = &[
3339 NOTEBOOK[0],
40 NOTEBOOK[1],
41 NOTEBOOK[2],
3442 include_str!("../assets/icons/icloud-badge.svg"),
3543];
3644pub const NUMBERING: &[&str] = art!("icons/numbering");
crates/snowbound/src/main.rs+6-4
......@@ -4474,10 +4474,12 @@ fn section_color(color: Option<u32>) -> [f32; 4] {
44744474 color.map_or(draw::srgb(0x8a, 0xa8, 0xe4), canvas::gpu::colorref)
44754475}
44764476
4477/// The colour a notebook's glyph takes from its COLORREF, as OneNote's navigation bar tints
4478/// it; the glyph's own orange without one.
4479fn notebook_color(color: Option<u32>) -> [f32; 4] {
4480 color.map_or(draw::srgb(0xf3, 0x9c, 0x28), canvas::gpu::colorref)
4477/// The colour a notebook's cover takes in `theme`: its COLORREF's hue at the shade a section's
4478/// takes, or the glyph's own orange without one.
4479fn notebook_color(theme: &Theme, color: Option<u32>) -> [f32; 4] {
4480 color.map_or(draw::srgb(0xf3, 0x9c, 0x28), |color| {
4481 theme.section(canvas::gpu::colorref(color)).accent
4482 })
44814483}
44824484
44834485/// OneNote's information bar for conflicting changes, clicked anywhere: above a page with
crates/snowbound/src/menus.rs+1-1
......@@ -578,7 +578,7 @@ impl State {
578578 tint: place
579579 .path
580580 .is_empty()
581 .then(|| crate::notebook_color(library.color())),
581 .then(|| crate::notebook_color(theme, library.color())),
582582 })
583583 .collect()
584584 }
crates/snowbound/src/palette.rs+2-2
......@@ -97,7 +97,7 @@ impl State {
9797 .map(|library| Row {
9898 text: library.name.clone(),
9999 icon: Some(art::NOTEBOOK),
100 tint: Some(crate::notebook_color(library.color())),
100 tint: Some(crate::notebook_color(&self.ui.theme, library.color())),
101101 target: Some(Target::Notebook(Arc::clone(library))),
102102 ..Row::default()
103103 }),
......@@ -288,7 +288,7 @@ impl State {
288288 .to_owned(),
289289 after: "Closed".to_owned(),
290290 icon: Some(art::NOTEBOOK),
291 tint: Some(crate::notebook_color(None)),
291 tint: Some(crate::notebook_color(&self.ui.theme, None)),
292292 target: Some(target),
293293 ..Row::default()
294294 });
crates/snowbound/src/properties.rs+1-1
......@@ -169,7 +169,7 @@ impl State {
169169 .map(|&(color, text)| Item {
170170 text,
171171 icon: Some(art::SWATCH),
172 tint: Some(crate::notebook_color(Some(color))),
172 tint: Some(crate::notebook_color(&theme, Some(color))),
173173 current: Some(color) == shown,
174174 ..Item::default()
175175 })
crates/snowbound/src/sidebar.rs+5-2
......@@ -613,7 +613,7 @@ impl Leading {
613613 fn art(self, theme: &Theme) -> (&'static [&'static str], [f32; 4]) {
614614 match self {
615615 Self::Icon(icon) => (icon, theme.text),
616 Self::Notebook(icon, color) => (icon, crate::notebook_color(color)),
616 Self::Notebook(icon, color) => (icon, crate::notebook_color(theme, color)),
617617 Self::Section(section) => (art::SECTION, theme.section(section).accent),
618618 }
619619 }
......@@ -867,7 +867,10 @@ impl crate::State {
867867 ..Spec::default()
868868 },
869869 );
870 let tint = crate::notebook_color(self.notebook().and_then(|library| library.color()));
870 let tint = crate::notebook_color(
871 &self.ui.theme,
872 self.notebook().and_then(|library| library.color()),
873 );
871874 if ui::shell::tool_button(&mut self.ui, "button", art::NOTEBOOK, tint, None).clicked {
872875 self.sidebar = !self.sidebar;
873876 self.save_settings();
crates/snowbound/src/sync.rs+1-1
......@@ -681,7 +681,7 @@ fn build(ui: &mut Ui, facts: &Facts) -> Picked {
681681 size: [fill(), px(line)],
682682 text: Some(facts.name),
683683 icon: Some(art::NOTEBOOK),
684 tint: Some(crate::notebook_color(facts.color)),
684 tint: Some(crate::notebook_color(&theme, facts.color)),
685685 bold: true,
686686 overflow: Overflow::Ellipsis,
687687 ..Spec::default()