| author | |
| committer | |
| log | f3b369e572b4a689a2e3c0337a0ea4be5da31fd6 |
| tree | d0d0009e5599beb778a54a6f4aab19cc7a37cbff |
| parent | 1a9bd5592f8f9bd6781eb91b4b2c383af90a4f9b |
| signature | Signed by SSH key SHA256:52mNGHRsVFBDED9IAX5pe+LRWUefqTbxEReunq21QvU |
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.520 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 | ... | @@ -307,8 +307,7 @@ final class NotebooksViewController: UICollectionViewController, UIDocumentPicke |
| 307 | case .notebook(let id): | 307 | case .notebook(let id): |
| 308 | guard let notebook = notebook(id) else { break } | 308 | guard let notebook = notebook(id) else { break } |
| 309 | content.text = notebook.name | 309 | content.text = notebook.name |
| 310 | content.image = UIImage(systemName: "book.closed.fill") | 310 | content.image = .notebook(notebook.color) |
| 311 | content.imageProperties.tintColor = notebook.color | ||
| 312 | var details: [String] = [] | 311 | var details: [String] = [] |
| 313 | if case .server(let server) = notebook.source { details.append(server.host) } | 312 | if case .server(let server) = notebook.source { details.append(server.host) } |
| 314 | if let state = attention[id]?.state { | 313 | if let state = attention[id]?.state { |
apps/ios/Snowbound/Notebooks.swift+15-1| ... | @@ -140,6 +140,20 @@ struct Tab: Decodable, Equatable { | ... | @@ -140,6 +140,20 @@ struct Tab: Decodable, Equatable { |
| 140 | var uiColor: UIColor { UIColor(rgb: color) } | 140 | var uiColor: UIColor { UIColor(rgb: color) } |
| 141 | } | 141 | } |
| 142 | 142 | ||
| 143 | extension 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 | |||
| 143 | extension UIViewController { | 157 | extension UIViewController { |
| 144 | /// Tells the reader `title` and `message` in an alert they dismiss with OK. | 158 | /// Tells the reader `title` and `message` in an alert they dismiss with OK. |
| 145 | func alert(_ title: String, _ message: String) { | 159 | func alert(_ title: String, _ message: String) { |
| ... | @@ -175,7 +189,7 @@ final class Notebook { | ... | @@ -175,7 +189,7 @@ final class Notebook { |
| 175 | private(set) var name: String | 189 | private(set) var name: String |
| 176 | private(set) var handle: OpaquePointer? | 190 | private(set) var handle: OpaquePointer? |
| 177 | private(set) var tabs: [Tab] = [] | 191 | 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. |
| 179 | private(set) var color: UIColor? | 193 | private(set) var color: UIColor? |
| 180 | /// Why the notebook could not be opened, while it cannot. | 194 | /// Why the notebook could not be opened, while it cannot. |
| 181 | private(set) var problem: String? | 195 | private(set) var problem: String? |
apps/ios/Snowbound/Prototypes.swift+2-2| ... | @@ -38,7 +38,7 @@ extension Prototype { | ... | @@ -38,7 +38,7 @@ extension Prototype { |
| 38 | ) -> UIContentUnavailableConfiguration? { | 38 | ) -> UIContentUnavailableConfiguration? { |
| 39 | guard Notebooks.all.isEmpty, !ICloud.signedIn || ICloud.documents != nil else { return nil } | 39 | guard Notebooks.all.isEmpty, !ICloud.signedIn || ICloud.documents != nil else { return nil } |
| 40 | var welcome = UIContentUnavailableConfiguration.empty() | 40 | var welcome = UIContentUnavailableConfiguration.empty() |
| 41 | welcome.image = UIImage(systemName: "book.closed") | 41 | welcome.image = .notebook(nil, side: 64) |
| 42 | welcome.text = "No Notebooks Yet" | 42 | welcome.text = "No Notebooks Yet" |
| 43 | welcome.secondaryText = | 43 | welcome.secondaryText = |
| 44 | ICloud.documents != nil | 44 | ICloud.documents != nil |
| ... | @@ -286,7 +286,7 @@ enum QuickNote { | ... | @@ -286,7 +286,7 @@ enum QuickNote { |
| 286 | let notebooks = Notebooks.all.filter { $0.tabs.contains(where: \.readable) } | 286 | let notebooks = Notebooks.all.filter { $0.tabs.contains(where: \.readable) } |
| 287 | let choices = notebooks.map { notebook in | 287 | let choices = notebooks.map { notebook in |
| 288 | UIMenu( | 288 | UIMenu( |
| 289 | title: notebook.name, image: UIImage(systemName: "book.closed"), | 289 | title: notebook.name, image: .notebook(notebook.color), |
| 290 | children: notebook.tabs.filter(\.readable).map { tab in | 290 | children: notebook.tabs.filter(\.readable).map { tab in |
| 291 | let chosen = Place(notebook: notebook.id, section: tab.path) | 291 | let chosen = Place(notebook: notebook.id, section: tab.path) |
| 292 | let action = UIAction( | 292 | let action = UIAction( |
crates/draw/src/render/icon.rs+22-3| ... | @@ -33,7 +33,7 @@ impl Palette { | ... | @@ -33,7 +33,7 @@ impl Palette { |
| 33 | /// Rasterizes 16×16 SVG sources over each other at a whole `size` of device pixels. Paths are filled and stroked with `#rrggbb`, `currentColor`, | 33 | /// Rasterizes 16×16 SVG sources over each other at a whole `size` of device pixels. Paths are filled and stroked with `#rrggbb`, `currentColor`, |
| 34 | /// which paints the linear `ink`, or two-stop `linearGradient`s with SVG's defaults | 34 | /// which paints the linear `ink`, or two-stop `linearGradient`s with SVG's defaults |
| 35 | /// (`objectBoundingBox` unless `userSpaceOnUse`, `x1`..`y2`, stop offsets, pad spread; no | 35 | /// (`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 |
| 37 | /// caps and joins. A slot's paths keep the shading among their colours: the hue turn, | 37 | /// caps and joins. A slot's paths keep the shading among their colours: the hue turn, |
| 38 | /// saturation scale and lightness shift that carry the mean of the slot's colours in a | 38 | /// saturation scale and lightness shift that carry the mean of the slot's colours in a |
| 39 | /// source onto the palette's colour apply to each of them. A source's `image`s of PNG | 39 | /// 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 | ... | @@ -141,7 +141,8 @@ pub(crate) fn rasterize(sources: &[&str], size: u32, ink: [f32; 3], palette: &Pa |
| 141 | let [x, y] = [index % side as usize, index / side as usize] | 141 | let [x, y] = [index % side as usize, index / side as usize] |
| 142 | .map(|device| device as f32 + 0.5) | 142 | .map(|device| device as f32 + 0.5) |
| 143 | .map(|device| device * 16.0 / size); | 143 | .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]); | ||
| 145 | } | 146 | } |
| 146 | } | 147 | } |
| 147 | } | 148 | } |
| ... | @@ -287,8 +288,12 @@ impl Paint { | ... | @@ -287,8 +288,12 @@ impl Paint { |
| 287 | .children() | 288 | .children() |
| 288 | .filter(|node| node.has_tag_name("stop")) | 289 | .filter(|node| node.has_tag_name("stop")) |
| 289 | .map(|node| { | 290 | .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()); | ||
| 290 | ( | 295 | ( |
| 291 | hex(node.attribute("stop-color").unwrap()), | 296 | [red, green, blue, opacity], |
| 292 | node.attribute("offset") | 297 | node.attribute("offset") |
| 293 | .map_or(0.0, |offset| number(offset, 1.0)), | 298 | .map_or(0.0, |offset| number(offset, 1.0)), |
| 294 | ) | 299 | ) |
| ... | @@ -567,6 +572,20 @@ mod tests { | ... | @@ -567,6 +572,20 @@ mod tests { |
| 567 | assert_eq!(alpha(1, 8), 64); | 572 | assert_eq!(alpha(1, 8), 64); |
| 568 | } | 573 | } |
| 569 | 574 | ||
| 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 | |||
| 570 | /// A 2×1 PNG, red then half-transparent blue, fits the icon's width, centred, and paints | 589 | /// A 2×1 PNG, red then half-transparent blue, fits the icon's width, centred, and paints |
| 571 | /// beneath the paths of later sources. | 590 | /// beneath the paths of later sources. |
| 572 | #[test] | 591 | #[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 @@ | ... | @@ -1,21 +1,10 @@ |
| 1 | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"> | 1 | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"> |
| 2 | <defs> | 2 | <defs> |
| 3 | <linearGradient id="g0" x2="0" y2="1"><stop offset="0" stop-color="#ff8f80"/><stop offset="1" stop-color="#f03b30"/></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="#86e88a"/><stop offset="1" stop-color="#28b04a"/></linearGradient> | 4 | <linearGradient id="g1" x2="0" y2="1"><stop offset="0" stop-color="#fffdf3"/><stop offset="1" stop-color="#fbeec7"/></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> | ||
| 8 | </defs> | 5 | </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"/> | 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"/> |
| 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"/> | 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)"/> |
| 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"/> | 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"/> |
| 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"/> | ||
| 20 | <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"/> | 9 | <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"/> |
| 21 | </svg> | 10 | </svg> |
crates/snowbound/src/art.rs+9-1| ... | @@ -27,10 +27,18 @@ pub const INDENT: &[&str] = art!("icons/indent"); | ... | @@ -27,10 +27,18 @@ pub const INDENT: &[&str] = art!("icons/indent"); |
| 27 | pub const INFO: &[&str] = art!("icons/info"); | 27 | pub const INFO: &[&str] = art!("icons/info"); |
| 28 | pub const ITALIC: &[&str] = art!("icons/italic"); | 28 | pub const ITALIC: &[&str] = art!("icons/italic"); |
| 29 | pub const LINK: &[&str] = art!("icons/link"); | 29 | pub const LINK: &[&str] = art!("icons/link"); |
| 30 | pub 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. | ||
| 32 | pub 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 | ]; | ||
| 31 | /// A notebook iCloud Drive keeps: its glyph with a cloud at the corner. | 37 | /// A notebook iCloud Drive keeps: its glyph with a cloud at the corner. |
| 32 | pub const NOTEBOOK_ICLOUD: &[&str] = &[ | 38 | pub const NOTEBOOK_ICLOUD: &[&str] = &[ |
| 33 | NOTEBOOK[0], | 39 | NOTEBOOK[0], |
| 40 | NOTEBOOK[1], | ||
| 41 | NOTEBOOK[2], | ||
| 34 | include_str!("../assets/icons/icloud-badge.svg"), | 42 | include_str!("../assets/icons/icloud-badge.svg"), |
| 35 | ]; | 43 | ]; |
| 36 | pub const NUMBERING: &[&str] = art!("icons/numbering"); | 44 | pub 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] { | ... | @@ -4474,10 +4474,12 @@ fn section_color(color: Option<u32>) -> [f32; 4] { |
| 4474 | color.map_or(draw::srgb(0x8a, 0xa8, 0xe4), canvas::gpu::colorref) | 4474 | color.map_or(draw::srgb(0x8a, 0xa8, 0xe4), canvas::gpu::colorref) |
| 4475 | } | 4475 | } |
| 4476 | 4476 | ||
| 4477 | /// The colour a notebook's glyph takes from its COLORREF, as OneNote's navigation bar tints | 4477 | /// The colour a notebook's cover takes in `theme`: its COLORREF's hue at the shade a section's |
| 4478 | /// it; the glyph's own orange without one. | 4478 | /// takes, or the glyph's own orange without one. |
| 4479 | fn notebook_color(color: Option<u32>) -> [f32; 4] { | 4479 | fn notebook_color(theme: &Theme, color: Option<u32>) -> [f32; 4] { |
| 4480 | color.map_or(draw::srgb(0xf3, 0x9c, 0x28), canvas::gpu::colorref) | 4480 | color.map_or(draw::srgb(0xf3, 0x9c, 0x28), |color| { |
| 4481 | theme.section(canvas::gpu::colorref(color)).accent | ||
| 4482 | }) | ||
| 4481 | } | 4483 | } |
| 4482 | 4484 | ||
| 4483 | /// OneNote's information bar for conflicting changes, clicked anywhere: above a page with | 4485 | /// 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 { | ... | @@ -578,7 +578,7 @@ impl State { |
| 578 | tint: place | 578 | tint: place |
| 579 | .path | 579 | .path |
| 580 | .is_empty() | 580 | .is_empty() |
| 581 | .then(|| crate::notebook_color(library.color())), | 581 | .then(|| crate::notebook_color(theme, library.color())), |
| 582 | }) | 582 | }) |
| 583 | .collect() | 583 | .collect() |
| 584 | } | 584 | } |
crates/snowbound/src/palette.rs+2-2| ... | @@ -97,7 +97,7 @@ impl State { | ... | @@ -97,7 +97,7 @@ impl State { |
| 97 | .map(|library| Row { | 97 | .map(|library| Row { |
| 98 | text: library.name.clone(), | 98 | text: library.name.clone(), |
| 99 | icon: Some(art::NOTEBOOK), | 99 | icon: Some(art::NOTEBOOK), |
| 100 | tint: Some(crate::notebook_color(library.color())), | 100 | tint: Some(crate::notebook_color(&self.ui.theme, library.color())), |
| 101 | target: Some(Target::Notebook(Arc::clone(library))), | 101 | target: Some(Target::Notebook(Arc::clone(library))), |
| 102 | ..Row::default() | 102 | ..Row::default() |
| 103 | }), | 103 | }), |
| ... | @@ -288,7 +288,7 @@ impl State { | ... | @@ -288,7 +288,7 @@ impl State { |
| 288 | .to_owned(), | 288 | .to_owned(), |
| 289 | after: "Closed".to_owned(), | 289 | after: "Closed".to_owned(), |
| 290 | icon: Some(art::NOTEBOOK), | 290 | icon: Some(art::NOTEBOOK), |
| 291 | tint: Some(crate::notebook_color(None)), | 291 | tint: Some(crate::notebook_color(&self.ui.theme, None)), |
| 292 | target: Some(target), | 292 | target: Some(target), |
| 293 | ..Row::default() | 293 | ..Row::default() |
| 294 | }); | 294 | }); |
crates/snowbound/src/properties.rs+1-1| ... | @@ -169,7 +169,7 @@ impl State { | ... | @@ -169,7 +169,7 @@ impl State { |
| 169 | .map(|&(color, text)| Item { | 169 | .map(|&(color, text)| Item { |
| 170 | text, | 170 | text, |
| 171 | icon: Some(art::SWATCH), | 171 | icon: Some(art::SWATCH), |
| 172 | tint: Some(crate::notebook_color(Some(color))), | 172 | tint: Some(crate::notebook_color(&theme, Some(color))), |
| 173 | current: Some(color) == shown, | 173 | current: Some(color) == shown, |
| 174 | ..Item::default() | 174 | ..Item::default() |
| 175 | }) | 175 | }) |
crates/snowbound/src/sidebar.rs+5-2| ... | @@ -613,7 +613,7 @@ impl Leading { | ... | @@ -613,7 +613,7 @@ impl Leading { |
| 613 | fn art(self, theme: &Theme) -> (&'static [&'static str], [f32; 4]) { | 613 | fn art(self, theme: &Theme) -> (&'static [&'static str], [f32; 4]) { |
| 614 | match self { | 614 | match self { |
| 615 | Self::Icon(icon) => (icon, theme.text), | 615 | 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)), |
| 617 | Self::Section(section) => (art::SECTION, theme.section(section).accent), | 617 | Self::Section(section) => (art::SECTION, theme.section(section).accent), |
| 618 | } | 618 | } |
| 619 | } | 619 | } |
| ... | @@ -867,7 +867,10 @@ impl crate::State { | ... | @@ -867,7 +867,10 @@ impl crate::State { |
| 867 | ..Spec::default() | 867 | ..Spec::default() |
| 868 | }, | 868 | }, |
| 869 | ); | 869 | ); |
| 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 | ); | ||
| 871 | if ui::shell::tool_button(&mut self.ui, "button", art::NOTEBOOK, tint, None).clicked { | 874 | if ui::shell::tool_button(&mut self.ui, "button", art::NOTEBOOK, tint, None).clicked { |
| 872 | self.sidebar = !self.sidebar; | 875 | self.sidebar = !self.sidebar; |
| 873 | self.save_settings(); | 876 | self.save_settings(); |
crates/snowbound/src/sync.rs+1-1| ... | @@ -681,7 +681,7 @@ fn build(ui: &mut Ui, facts: &Facts) -> Picked { | ... | @@ -681,7 +681,7 @@ fn build(ui: &mut Ui, facts: &Facts) -> Picked { |
| 681 | size: [fill(), px(line)], | 681 | size: [fill(), px(line)], |
| 682 | text: Some(facts.name), | 682 | text: Some(facts.name), |
| 683 | icon: Some(art::NOTEBOOK), | 683 | icon: Some(art::NOTEBOOK), |
| 684 | tint: Some(crate::notebook_color(facts.color)), | 684 | tint: Some(crate::notebook_color(&theme, facts.color)), |
| 685 | bold: true, | 685 | bold: true, |
| 686 | overflow: Overflow::Ellipsis, | 686 | overflow: Overflow::Ellipsis, |
| 687 | ..Spec::default() | 687 | ..Spec::default() |