| author | |
| committer | |
| log | 91ad7c52e4867559ba209810469eea16612dcca8 |
| tree | 0b015255736a1ee993dfef721c0a5122fbc524ce |
| parent | 2bfd2c325dbad48b099155c8aee21fcd5b3a5ee5 |
| signature | Signed by SSH key SHA256:52mNGHRsVFBDED9IAX5pe+LRWUefqTbxEReunq21QvU |
Section shading took only a colour's hue at the theme's saturation, which
turned OneNote's Silver (#9595AA) indigo and its mists and Cyan vivid. A colour
greyer than OneNote's bright ones now takes the saturation in proportion, in the
tabs, the frame, sidebar icons, notebook covers and the section's pen.
Assisted-by: claude-opus-5.53 files changed, 45 insertions(+), 4 deletions(-)
crates/canvas/src/interaction/ink.rs+5-1| ... | ... | @@ -20,7 +20,11 @@ use onestore::{ |
| 20 | 20 | pub fn pens(section: [f32; 4]) -> [Pen; 15] { |
| 21 | 21 | let [red, green, blue] = draw::srgb_bytes({ |
| 22 | 22 | let [saturation, lightness] = draw::LIGHT_ACCENT; |
| 23 | draw::hsl(draw::hue(section), saturation, lightness) | |
| 23 | draw::hsl( | |
| 24 | draw::hue(section), | |
| 25 | saturation * draw::vividness(section), | |
| 26 | lightness, | |
| 27 | ) | |
| 24 | 28 | }); |
| 25 | 29 | let accent = Pen::new(35.0, Some(u32::from_le_bytes([red, green, blue, 0]))); |
| 26 | 30 | std::array::from_fn(|place| place.checked_sub(1).map_or(accent, |at| FAVORITES[at])) |
crates/draw/src/render.rs+9| ... | ... | @@ -1762,6 +1762,15 @@ pub fn hue(color: [f32; 4]) -> f32 { |
| 1762 | 1762 | to_hsl([color[0], color[1], color[2]].map(crate::encode))[0] |
| 1763 | 1763 | } |
| 1764 | 1764 | |
| 1765 | /// How much of a shade's saturation a linear colour's hue takes, from 0 to 1: all of it from | |
| 1766 | /// a saturation of `VIVID` up, as OneNote 2010's bright section colours have, less as the | |
| 1767 | /// colour greys, so its Silver and mists stay grey rather than turn their faint hue vivid. | |
| 1768 | pub fn vividness(color: [f32; 4]) -> f32 { | |
| 1769 | const VIVID: f32 = 0.35; | |
| 1770 | let [_, saturation, _] = to_hsl([color[0], color[1], color[2]].map(crate::encode)); | |
| 1771 | (saturation / VIVID).min(1.0) | |
| 1772 | } | |
| 1773 | ||
| 1765 | 1774 | /// Hue in degrees, saturation and lightness of an encoded sRGB colour. |
| 1766 | 1775 | fn to_hsl([r, g, b]: [f32; 3]) -> [f32; 3] { |
| 1767 | 1776 | let [max, min] = [r.max(g).max(b), r.min(g).min(b)]; |
crates/ui/src/theme.rs+31-3| ... | ... | @@ -290,10 +290,12 @@ impl Theme { |
| 290 | 290 | } |
| 291 | 291 | } |
| 292 | 292 | |
| 293 | /// The colours a section takes from the hue of `color`, linear RGBA. | |
| 293 | /// The colours a section takes from the hue of `color`, linear RGBA, as grey as it is. | |
| 294 | 294 | pub fn section(&self, color: [f32; 4]) -> Section { |
| 295 | let hue = hue(color); | |
| 296 | let shade = |hue: f32, [saturation, lightness]: [f32; 2]| hsl(hue, saturation, lightness); | |
| 295 | let (hue, vividness) = (hue(color), draw::vividness(color)); | |
| 296 | let shade = |hue: f32, [saturation, lightness]: [f32; 2]| { | |
| 297 | hsl(hue, saturation * vividness, lightness) | |
| 298 | }; | |
| 297 | 299 | let Shades { |
| 298 | 300 | frame, |
| 299 | 301 | tab, |
| ... | ... | @@ -326,6 +328,32 @@ mod tests { |
| 326 | 328 | assert_eq!(hsl(240.0, 0.0, 1.0), srgb(0xff, 0xff, 0xff)); |
| 327 | 329 | } |
| 328 | 330 | |
| 331 | /// OneNote 2010's Silver (`corpus/section-color/native/colors.txt`) shades grey, as | |
| 332 | /// faint in hue as it is, while its Blue takes the theme's full saturation. | |
| 333 | #[test] | |
| 334 | fn grey_sections_stay_grey() { | |
| 335 | // HSL saturation, as sRGB shows it. | |
| 336 | let saturation = |color: [f32; 4]| { | |
| 337 | let [r, g, b] = draw::srgb_bytes(color).map(|byte| f32::from(byte) / 255.0); | |
| 338 | let [max, min] = [r.max(g).max(b), r.min(g).min(b)]; | |
| 339 | (max - min) / (1.0 - (max + min - 1.0).abs()) | |
| 340 | }; | |
| 341 | for theme in [Theme::light(), Theme::dark()] { | |
| 342 | let silver = theme.section(srgb(0x95, 0x95, 0xaa)); | |
| 343 | let blue = theme.section(srgb(0x8a, 0xa8, 0xe4)); | |
| 344 | for (silver, blue) in [ | |
| 345 | (silver.tab, blue.tab), | |
| 346 | (silver.accent, blue.accent), | |
| 347 | (silver.frame[0], blue.frame[0]), | |
| 348 | ] { | |
| 349 | assert!( | |
| 350 | saturation(silver) < saturation(blue) * 0.4, | |
| 351 | "{silver:?} {blue:?}" | |
| 352 | ); | |
| 353 | } | |
| 354 | } | |
| 355 | } | |
| 356 | ||
| 329 | 357 | #[test] |
| 330 | 358 | fn icon_badges_stand_clear_of_the_accent() { |
| 331 | 359 | let badge = |accent_hue: f32| { |