| 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,7 +20,11 @@ use onestore::{ |
| 20 | pub fn pens(section: [f32; 4]) -> [Pen; 15] { | 20 | pub fn pens(section: [f32; 4]) -> [Pen; 15] { |
| 21 | let [red, green, blue] = draw::srgb_bytes({ | 21 | let [red, green, blue] = draw::srgb_bytes({ |
| 22 | let [saturation, lightness] = draw::LIGHT_ACCENT; | 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 | let accent = Pen::new(35.0, Some(u32::from_le_bytes([red, green, blue, 0]))); | 29 | let accent = Pen::new(35.0, Some(u32::from_le_bytes([red, green, blue, 0]))); |
| 26 | std::array::from_fn(|place| place.checked_sub(1).map_or(accent, |at| FAVORITES[at])) | 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,6 +1762,15 @@ pub fn hue(color: [f32; 4]) -> f32 { |
| 1762 | to_hsl([color[0], color[1], color[2]].map(crate::encode))[0] | 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 | /// Hue in degrees, saturation and lightness of an encoded sRGB colour. | 1774 | /// Hue in degrees, saturation and lightness of an encoded sRGB colour. |
| 1766 | fn to_hsl([r, g, b]: [f32; 3]) -> [f32; 3] { | 1775 | fn to_hsl([r, g, b]: [f32; 3]) -> [f32; 3] { |
| 1767 | let [max, min] = [r.max(g).max(b), r.min(g).min(b)]; | 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,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 | pub fn section(&self, color: [f32; 4]) -> Section { | 294 | pub fn section(&self, color: [f32; 4]) -> Section { |
| 295 | let hue = hue(color); | 295 | let (hue, vividness) = (hue(color), draw::vividness(color)); |
| 296 | let shade = |hue: f32, [saturation, lightness]: [f32; 2]| hsl(hue, saturation, lightness); | 296 | let shade = |hue: f32, [saturation, lightness]: [f32; 2]| { |
| 297 | hsl(hue, saturation * vividness, lightness) | ||
| 298 | }; | ||
| 297 | let Shades { | 299 | let Shades { |
| 298 | frame, | 300 | frame, |
| 299 | tab, | 301 | tab, |
| ... | @@ -326,6 +328,32 @@ mod tests { | ... | @@ -326,6 +328,32 @@ mod tests { |
| 326 | assert_eq!(hsl(240.0, 0.0, 1.0), srgb(0xff, 0xff, 0xff)); | 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 | #[test] | 357 | #[test] |
| 330 | fn icon_badges_stand_clear_of_the_accent() { | 358 | fn icon_badges_stand_clear_of_the_accent() { |
| 331 | let badge = |accent_hue: f32| { | 359 | let badge = |accent_hue: f32| { |