Explain runtime invariants and avoid redundant unwraps

This commit is contained in:
Samuel Onoja committed 2026-10-22 01:28:03 +01:00
1 parent 34f2f07301
commit 1b51ba83b4
116 files changed
+1643 -515

No files matched your search

+1 -1
View File
@@ -55,7 +55,7 @@ impl VelocityTracker {
for col in 0..=degree {
let pivot = (col..=degree)
.max_by(|&a, &b| matrix[a][col].abs().total_cmp(&matrix[b][col].abs()))
.unwrap();
.expect("the inclusive pivot range contains at least the current column");
matrix.swap(col, pivot);
let divisor = matrix[col][col];
if divisor.abs() < 1e-12 {
+7 -2
View File
@@ -310,13 +310,18 @@ pub(crate) fn parse(node: &KdlNode) -> Result<Value, Error> {
.into_iter()
.filter(|rule| !names.contains(&rule.name)),
);
values.insert("rules".into(), serde_json::to_value(rules).unwrap());
values.insert(
"rules".into(),
serde_json::to_value(rules)
.expect("typed configuration and IPC values must serialize to JSON"),
);
let settings: Settings =
serde_json::from_value(Value::Object(values)).map_err(|error| diagnostic(node, error))?;
settings
.validate()
.map_err(|error| diagnostic(node, error))?;
Ok(serde_json::to_value(settings).unwrap())
Ok(serde_json::to_value(settings)
.expect("typed configuration and IPC values must serialize to JSON"))
}
fn builtins() -> Vec<Rule> {
[
+14 -4
View File
@@ -166,7 +166,10 @@ fn object(doc: &KdlDocument, parent: &str) -> Result<Map<String, Value>, Error>
let records = result
.entry(key)
.or_insert_with(|| Value::Array(Vec::new()));
records.as_array_mut().unwrap().push(value);
records
.as_array_mut()
.expect("repeated KDL records are stored as arrays")
.push(value);
} else {
insert(&mut result, key, value)?;
}
@@ -255,7 +258,10 @@ pub(crate) fn node_value(node: &KdlNode, key: &str, parent: &str) -> Result<Valu
return Err(Error(format!("{} requires one value", node.name())));
}
Ok(args.into_iter().next().unwrap())
Ok(args
.into_iter()
.next()
.expect("a scalar KDL node was checked to have one argument"))
}
#[derive(Clone, Debug)]
@@ -305,7 +311,8 @@ impl Document {
let mut document = self.clone();
document.value["theme"] =
serde_json::to_value(&theme.tokens).expect("serializable theme tokens");
document.value["theme"]["appearance"] = serde_json::to_value(theme.appearance).unwrap();
document.value["theme"]["appearance"] = serde_json::to_value(theme.appearance)
.expect("typed configuration and IPC values must serialize to JSON");
document
}
@@ -882,7 +889,10 @@ fn kdl_value(value: &Value) -> Result<KdlValue, Error> {
} else if let Some(i) = n.as_u64() {
KdlValue::Integer(i.into())
} else {
KdlValue::Float(n.as_f64().unwrap())
KdlValue::Float(
n.as_f64()
.expect("JSON numbers fit the KDL numeric representation"),
)
}
}
_ => return Err(Error("Expected a scalar value".into())),
+4 -2
View File
@@ -64,10 +64,12 @@ pub fn builtins() -> Vec<Family> {
id: (*id).into(),
name: (*name).into(),
light: Some(Palette::from(
&crate::theme::preset(light, Appearance::Light).unwrap(),
&crate::theme::preset(light, Appearance::Light)
.expect("built-in theme families must reference registered presets"),
)),
dark: Some(Palette::from(
&crate::theme::preset(dark, Appearance::Dark).unwrap(),
&crate::theme::preset(dark, Appearance::Dark)
.expect("built-in theme families must reference registered presets"),
)),
})
.collect()
+54 -22
View File
@@ -552,8 +552,10 @@ fn token_value(tokens: &Tokens) -> Result<Value, String> {
let mut value = serde_json::to_value(tokens).map_err(|error| error.to_string())?;
// Keep authoring defaults available for merging. The wire format omits
// automatic styles so older snapshots still round-trip unchanged.
value["focus_ring"]["style"] = serde_json::to_value(tokens.focus_ring.style).unwrap();
value["border"]["style"] = serde_json::to_value(tokens.border.style).unwrap();
value["focus_ring"]["style"] = serde_json::to_value(tokens.focus_ring.style)
.expect("typed configuration and IPC values must serialize to JSON");
value["border"]["style"] = serde_json::to_value(tokens.border.style)
.expect("typed configuration and IPC values must serialize to JSON");
Ok(value)
}
@@ -673,8 +675,16 @@ pub fn preset(name: &str, appearance: Appearance) -> Result<Tokens, String> {
} else {
[1.; 4]
};
t.colors.surface_raised = hex(blend(rgba(base).unwrap(), shade, 0.055));
t.colors.application_background = hex(blend(rgba(base).unwrap(), shade, 0.025));
t.colors.surface_raised = hex(blend(
rgba(base).expect("resolved theme colors must be valid hexadecimal RGBA"),
shade,
0.055,
));
t.colors.application_background = hex(blend(
rgba(base).expect("resolved theme colors must be valid hexadecimal RGBA"),
shade,
0.025,
));
t.colors.text_primary = text.into();
t.colors.text_muted = muted.into();
t.colors.accent = accent.into();
@@ -708,8 +718,10 @@ pub fn authored_warnings(tokens: &Tokens) -> Vec<String> {
),
];
let check = |foreground: &str, background: &str, minimum: f64| {
let foreground = rgba(foreground).unwrap();
let background = rgba(background).unwrap();
let foreground =
rgba(foreground).expect("resolved theme colors must be valid hexadecimal RGBA");
let background =
rgba(background).expect("resolved theme colors must be valid hexadecimal RGBA");
contrast(composite(foreground, background), background) >= minimum
};
for (name, background) in surfaces {
@@ -813,7 +825,8 @@ fn transform(t: &mut Tokens, accessibility: &Accessibility, warnings: &mut Vec<S
for target in [[0., 0., 0., 1.], [1.; 4]] {
for step in 0..=1000 {
let distance = f64::from(step) / 1000.;
let color = rgba(&hex(blend(requested, target, distance))).unwrap();
let color = rgba(&hex(blend(requested, target, distance)))
.expect("hex output must be a valid RGBA color");
if acceptable(color) && distance < best_distance {
best = Some(color);
best_distance = distance;
@@ -858,14 +871,17 @@ fn transform(t: &mut Tokens, accessibility: &Accessibility, warnings: &mut Vec<S
}
if accessibility.increase_contrast {
t.colors.border = hex(readable_across(
rgba(&t.colors.border).unwrap(),
rgba(&t.colors.border).expect("resolved theme colors must be valid hexadecimal RGBA"),
&backgrounds,
3.,
));
if backgrounds
.iter()
.any(|background| contrast(rgba(&t.colors.border).unwrap(), *background) < 3.)
{
if backgrounds.iter().any(|background| {
contrast(
rgba(&t.colors.border)
.expect("resolved theme colors must be valid hexadecimal RGBA"),
*background,
) < 3.
}) {
warnings.push("Border cannot reach 3:1 contrast on all configured surfaces".into());
}
resolve_material_contrast(t, minimum);
@@ -889,10 +905,14 @@ fn resolve_gradients(tokens: &mut Tokens, preset_end: Option<&str>) {
};
let backgrounds = [
rgba(&tokens.colors.surface_base).unwrap(),
rgba(&tokens.colors.surface_raised).unwrap(),
rgba(&tokens.colors.application_background).unwrap(),
rgba(&tokens.surface.bar.background).unwrap(),
rgba(&tokens.colors.surface_base)
.expect("resolved theme colors must be valid hexadecimal RGBA"),
rgba(&tokens.colors.surface_raised)
.expect("resolved theme colors must be valid hexadecimal RGBA"),
rgba(&tokens.colors.application_background)
.expect("resolved theme colors must be valid hexadecimal RGBA"),
rgba(&tokens.surface.bar.background)
.expect("resolved theme colors must be valid hexadecimal RGBA"),
];
for (paint, color, endpoint, focus) in [
(
@@ -915,7 +935,11 @@ fn resolve_gradients(tokens: &mut Tokens, preset_end: Option<&str>) {
let to = endpoint.map_or_else(|| shade(color), str::to_owned);
let to = if focus {
hex(readable_across(rgba(&to).unwrap(), &backgrounds, 3.))
hex(readable_across(
rgba(&to).expect("resolved theme colors must be valid hexadecimal RGBA"),
&backgrounds,
3.,
))
} else {
to
};
@@ -931,10 +955,14 @@ fn resolve_material_contrast(tokens: &mut Tokens, minimum: f64) {
if tokens.material.style == "solid" {
return;
}
let surface = rgba(&tokens.colors.surface_base).unwrap();
let bar = rgba(&tokens.surface.bar.background).unwrap();
let raised = rgba(&tokens.colors.surface_raised).unwrap();
let app = rgba(&tokens.colors.application_background).unwrap();
let surface = rgba(&tokens.colors.surface_base)
.expect("resolved theme colors must be valid hexadecimal RGBA");
let bar = rgba(&tokens.surface.bar.background)
.expect("resolved theme colors must be valid hexadecimal RGBA");
let raised = rgba(&tokens.colors.surface_raised)
.expect("resolved theme colors must be valid hexadecimal RGBA");
let app = rgba(&tokens.colors.application_background)
.expect("resolved theme colors must be valid hexadecimal RGBA");
let initial = tokens.material.opacity * tokens.material.tint_strength;
let readable = |backgrounds: &[[f64; 4]]| {
[[0., 0., 0., 1.], [1.; 4]].into_iter().any(|text| {
@@ -962,7 +990,11 @@ fn resolve_material_contrast(tokens: &mut Tokens, minimum: f64) {
(&mut tokens.surface.bar.text_primary, bar.as_slice()),
(&mut tokens.surface.bar.text_muted, bar.as_slice()),
] {
*color = hex(readable_across(rgba(color).unwrap(), backgrounds, minimum));
*color = hex(readable_across(
rgba(color).expect("resolved theme colors must be valid hexadecimal RGBA"),
backgrounds,
minimum,
));
}
}
+14 -3
View File
@@ -184,7 +184,15 @@ impl OutputWorkspaceMap {
let mut removed = self
.connected_outputs()
.filter(|id| !desired.contains_key(id))
.map(|id| (id, outputs.outputs.remove(&id).unwrap()))
.map(|id| {
(
id,
outputs
.outputs
.remove(&id)
.expect("workspace reconciliation only accesses connected outputs"),
)
})
.collect::<Vec<_>>();
// Preserve the original focused workspace when several outputs vanish.
removed.sort_by_key(|(id, _)| (Some(*id) == self.focused, *id));
@@ -196,7 +204,10 @@ impl OutputWorkspaceMap {
// against the final inventory, where that donor no longer needs it.
for (id, record) in returning {
let reclaimed = outputs.reclaim(id, &record);
let state = outputs.outputs.get_mut(&id).unwrap();
let state = outputs
.outputs
.get_mut(&id)
.expect("workspace reconciliation only accesses connected outputs");
state.workspaces.extend(reclaimed.iter().copied());
if reclaimed.contains(&record.active) {
state.activate(record.active);
@@ -635,7 +646,7 @@ impl OutputWorkspaceMap {
self.homes.insert(workspace, output);
self.outputs
.get_mut(&output)
.unwrap()
.expect("workspace reconciliation only accesses connected outputs")
.workspaces
.insert(workspace);
self.bump_revision(workspace);
+4 -1
View File
@@ -544,7 +544,10 @@ impl WorkspaceSet {
replacements.push((workspace.id, layout));
}
for (id, layout) in replacements {
self.workspaces.get_mut(&id).unwrap().layout = layout;
self.workspaces
.get_mut(&id)
.expect("replacement layouts were built from existing workspace IDs")
.layout = layout;
}
self.default_layout_mode = mode;
self.default_column_width = width;
+4 -1
View File
@@ -41,7 +41,10 @@ impl OutputWorkspaceMap {
id,
output,
index: index as u32 + 1,
window_count: workspaces.workspace(id).unwrap().window_count(),
window_count: workspaces
.workspace(id)
.expect("workspace views enumerate registered workspace IDs")
.window_count(),
visible,
focused: visible && self.focused_output() == Some(output),
}
+8 -2
View File
@@ -666,7 +666,10 @@ impl ScrollingLayout {
let viewport_center = bounds.x + bounds.width / 2.0;
let window_center = rect.x + rect.width / 2.0;
self.viewport_x += window_center - viewport_center;
let active = self.window_location(window).unwrap().0;
let active = self
.window_location(window)
.expect("geometry resolution confirmed the window belongs to a column")
.0;
let inner = finite_nonnegative(gaps.inner);
let positions = self
.columns
@@ -678,7 +681,10 @@ impl ScrollingLayout {
Some(position)
})
.collect::<Vec<_>>();
let viewport_width = self.last_column_sizing.unwrap().viewport_width;
let viewport_width = self
.last_column_sizing
.expect("geometry resolution records the column sizing before centering")
.viewport_width;
self.record_viewport_basis(
&positions,
inner,
+3 -3
View File
@@ -49,9 +49,9 @@ impl Geometry {
fn from_values(values: [f64; COUNT]) -> Self {
Self {
bounds: Rect::new(values[0], values[1], values[2], values[3]),
radii: values[4..8].try_into().unwrap(),
margin: values[8..12].try_into().unwrap(),
thickness: values[12..16].try_into().unwrap(),
radii: [values[4], values[5], values[6], values[7]],
margin: [values[8], values[9], values[10], values[11]],
thickness: [values[12], values[13], values[14], values[15]],
gap: values[16],
fusion: values[17],
}
+8 -4
View File
@@ -211,10 +211,14 @@ pub fn notch_border(
let mut paths = Vec::new();
while let Some((a, b)) = segments.pop() {
let mut path = vec![a, b];
while let Some(index) = segments
.iter()
.position(|&(a, _)| close(a, *path.last().unwrap()))
{
while let Some(index) = segments.iter().position(|&(a, _)| {
close(
a,
*path
.last()
.expect("an outline path starts with two points and only grows"),
)
}) {
path.push(segments.swap_remove(index).1);
}
while let Some(index) = segments.iter().position(|&(_, b)| close(b, path[0])) {
+27 -16
View File
@@ -183,10 +183,10 @@ pub fn resolve_with_gap(
result.minimum_width = minimum.required_width();
// Flexible content yields space without changing the stored item preference.
for (_, item) in &candidates {
let Some(measured) = measurements
.get(&item.id)
.filter(|measured| measured.minimum.is_some())
else {
let Some(measured) = measurements.get(&item.id) else {
continue;
};
let Some(minimum) = measured.minimum else {
continue;
};
if fits(&result) {
@@ -199,7 +199,7 @@ pub fn resolve_with_gap(
else {
continue;
};
let minimum = measured.minimum.unwrap().min(width);
let minimum = minimum.min(width);
result.items.insert(
measured.id.clone(),
Placement::Visible {
@@ -352,10 +352,14 @@ pub fn resolve_with_gap(
let mut high = maximum;
for _ in 0..20 {
let width = (low + high) * 0.5;
*result.items.get_mut(&measured.id).unwrap() = Placement::Visible {
representation,
width,
};
*result
.items
.get_mut(&measured.id)
.expect("visible items retain their allocation during width refinement") =
Placement::Visible {
representation,
width,
};
result.update_widths(panel, overflow_width);
if fits(&result) {
low = width
@@ -363,14 +367,21 @@ pub fn resolve_with_gap(
high = width
}
}
*result.items.get_mut(&measured.id).unwrap() = Placement::Visible {
representation,
width: low,
};
*result
.items
.get_mut(&measured.id)
.expect("visible items retain their allocation during width refinement") =
Placement::Visible {
representation,
width: low,
};
}
result
.overflow
.sort_by_key(|id| ordered.iter().position(|item| &item.id == id).unwrap());
result.overflow.sort_by_key(|id| {
ordered
.iter()
.position(|item| &item.id == id)
.expect("overflow contains only authored item IDs")
});
result.update_widths(panel, overflow_width);
result
}
+75 -36
View File
@@ -108,7 +108,8 @@ pub fn readable_across(
white
};
for step in 1..=100 {
let color = rgba(&hex(blend(preferred, target, f64::from(step) / 100.))).unwrap();
let color = rgba(&hex(blend(preferred, target, f64::from(step) / 100.)))
.expect("hex output must be a valid RGBA color");
if score(color) >= minimum {
return color;
}
@@ -137,9 +138,12 @@ fn material_foreground(preferred: [f64; 4], backgrounds: [[f64; 4]; 2]) -> [f64;
let outside = luminance(result) < luminance(backgrounds[0])
|| luminance(result) > luminance(backgrounds[1]);
if outside
&& backgrounds
.iter()
.all(|background| contrast(rgba(&hex(result)).unwrap(), *background) >= 4.5)
&& backgrounds.iter().all(|background| {
contrast(
rgba(&hex(result)).expect("hex output must be a valid RGBA color"),
*background,
) >= 4.5
})
{
result
} else {
@@ -161,8 +165,8 @@ fn transition_paint(from: &Paint, to: &Paint, from_solid: &str, to_solid: &str,
let (a, b) = endpoints(from, from_solid);
let (c, d) = endpoints(to, to_solid);
let color = |a: &str, b: &str| {
let mut a = rgba(a).unwrap();
let mut b = rgba(b).unwrap();
let mut a = rgba(a).expect("resolved theme colors must be valid hexadecimal RGBA");
let mut b = rgba(b).expect("resolved theme colors must be valid hexadecimal RGBA");
for channel in 0..3 {
a[channel] *= a[3];
b[channel] *= b[3];
@@ -180,13 +184,13 @@ fn transition_paint(from: &Paint, to: &Paint, from_solid: &str, to_solid: &str,
let from_angle = from
.effective_gradient()
.or(to.effective_gradient())
.unwrap()
.expect("at least one transition endpoint has a gradient")
.angle
.rem_euclid(360.);
let to_angle = to
.effective_gradient()
.or(from.effective_gradient())
.unwrap()
.expect("at least one transition endpoint has a gradient")
.angle
.rem_euclid(360.);
let delta = (to_angle - from_angle + 180.).rem_euclid(360.) - 180.;
@@ -202,6 +206,10 @@ fn transition_paint(from: &Paint, to: &Paint, from_solid: &str, to_solid: &str,
impl ResolvedTheme {
/// Bound contrast across all backdrops instead of interpolating foregrounds.
///
/// # Panics
/// Both themes must contain valid hexadecimal colors and gradient endpoints,
/// as supplied by configuration resolution. Invalid colors panic during blending.
pub fn transition(&self, to: &Self, progress: f64) -> Self {
if to.reduced_motion || self.accessibility != to.accessibility || progress >= 1.0 {
return to.clone();
@@ -212,7 +220,13 @@ impl ResolvedTheme {
let p = progress.clamp(0., 1.);
let p = p * p * (3. - 2. * p);
let mut frame = to.clone();
let mix = |a: &str, b: &str| hex(blend(rgba(a).unwrap(), rgba(b).unwrap(), p));
let mix = |a: &str, b: &str| {
hex(blend(
rgba(a).expect("resolved theme colors must be valid hexadecimal RGBA"),
rgba(b).expect("resolved theme colors must be valid hexadecimal RGBA"),
p,
))
};
frame.tokens.colors.surface_base = mix(
&self.tokens.colors.surface_base,
&to.tokens.colors.surface_base,
@@ -256,9 +270,12 @@ impl ResolvedTheme {
frame.appearance = preferred.appearance;
if self.appearance == to.appearance {
let body = [
rgba(&frame.tokens.colors.surface_base).unwrap(),
rgba(&frame.tokens.colors.surface_raised).unwrap(),
rgba(&frame.tokens.colors.application_background).unwrap(),
rgba(&frame.tokens.colors.surface_base)
.expect("resolved theme colors must be valid hexadecimal RGBA"),
rgba(&frame.tokens.colors.surface_raised)
.expect("resolved theme colors must be valid hexadecimal RGBA"),
rgba(&frame.tokens.colors.application_background)
.expect("resolved theme colors must be valid hexadecimal RGBA"),
];
let minimum = if to.accessibility.increase_contrast {
7.0
@@ -266,36 +283,46 @@ impl ResolvedTheme {
4.5
};
frame.tokens.colors.text_primary = hex(readable_across(
rgba(&preferred.tokens.colors.text_primary).unwrap(),
rgba(&preferred.tokens.colors.text_primary)
.expect("resolved theme colors must be valid hexadecimal RGBA"),
&body,
minimum,
));
frame.tokens.colors.text_muted = hex(readable_across(
rgba(&preferred.tokens.colors.text_muted).unwrap(),
rgba(&preferred.tokens.colors.text_muted)
.expect("resolved theme colors must be valid hexadecimal RGBA"),
&body,
minimum,
));
let bar = rgba(&frame.tokens.surface.bar.background).unwrap();
let bar = rgba(&frame.tokens.surface.bar.background)
.expect("resolved theme colors must be valid hexadecimal RGBA");
frame.tokens.surface.bar.text_primary = hex(readable(
rgba(&preferred.tokens.surface.bar.text_primary).unwrap(),
rgba(&preferred.tokens.surface.bar.text_primary)
.expect("resolved theme colors must be valid hexadecimal RGBA"),
bar,
minimum,
));
frame.tokens.surface.bar.text_muted = hex(readable(
rgba(&preferred.tokens.surface.bar.text_muted).unwrap(),
rgba(&preferred.tokens.surface.bar.text_muted)
.expect("resolved theme colors must be valid hexadecimal RGBA"),
bar,
minimum,
));
frame.tokens.colors.on_accent = hex(readable(
rgba(&preferred.tokens.colors.on_accent).unwrap(),
rgba(&frame.tokens.colors.accent).unwrap(),
rgba(&preferred.tokens.colors.on_accent)
.expect("resolved theme colors must be valid hexadecimal RGBA"),
rgba(&frame.tokens.colors.accent)
.expect("resolved theme colors must be valid hexadecimal RGBA"),
4.5,
));
return frame;
}
let base = rgba(&frame.tokens.colors.surface_base).unwrap();
let raised = rgba(&frame.tokens.colors.surface_raised).unwrap();
let app = rgba(&frame.tokens.colors.application_background).unwrap();
let base = rgba(&frame.tokens.colors.surface_base)
.expect("resolved theme colors must be valid hexadecimal RGBA");
let raised = rgba(&frame.tokens.colors.surface_raised)
.expect("resolved theme colors must be valid hexadecimal RGBA");
let app = rgba(&frame.tokens.colors.application_background)
.expect("resolved theme colors must be valid hexadecimal RGBA");
let minimum = [base, raised, app]
.into_iter()
.map(luminance)
@@ -308,8 +335,10 @@ impl ResolvedTheme {
frame.tokens.colors.surface_raised = frame.tokens.colors.surface_base.clone();
frame.tokens.colors.application_background = frame.tokens.colors.surface_base.clone();
}
let surface = rgba(&frame.tokens.colors.surface_base).unwrap();
let bar = rgba(&frame.tokens.surface.bar.background).unwrap();
let surface = rgba(&frame.tokens.colors.surface_base)
.expect("resolved theme colors must be valid hexadecimal RGBA");
let bar = rgba(&frame.tokens.surface.bar.background)
.expect("resolved theme colors must be valid hexadecimal RGBA");
let mut opacity = if frame.tokens.material.style == "solid" {
1.
} else {
@@ -318,8 +347,10 @@ impl ResolvedTheme {
for step in 0..=100 {
let candidate = opacity + (1. - opacity) * f64::from(step) / 100.;
let backgrounds = material_bounds(surface, candidate);
let raised = rgba(&frame.tokens.colors.surface_raised).unwrap();
let app = rgba(&frame.tokens.colors.application_background).unwrap();
let raised = rgba(&frame.tokens.colors.surface_raised)
.expect("resolved theme colors must be valid hexadecimal RGBA");
let app = rgba(&frame.tokens.colors.application_background)
.expect("resolved theme colors must be valid hexadecimal RGBA");
let all = [backgrounds[0], backgrounds[1], raised, app];
let minimum = all.into_iter().map(luminance).fold(f64::INFINITY, f64::min);
let maximum = all.into_iter().map(luminance).fold(0., f64::max);
@@ -339,38 +370,46 @@ impl ResolvedTheme {
}
let surface = material_bounds(surface, opacity);
let bar = material_bounds(bar, opacity);
let raised = rgba(&frame.tokens.colors.surface_raised).unwrap();
let app = rgba(&frame.tokens.colors.application_background).unwrap();
let raised = rgba(&frame.tokens.colors.surface_raised)
.expect("resolved theme colors must be valid hexadecimal RGBA");
let app = rgba(&frame.tokens.colors.application_background)
.expect("resolved theme colors must be valid hexadecimal RGBA");
let body_backgrounds = [surface[0], surface[1], raised, app];
let body_bounds = [
*body_backgrounds
.iter()
.min_by(|a, b| luminance(**a).total_cmp(&luminance(**b)))
.unwrap(),
.expect("material contrast checks contain four background samples"),
*body_backgrounds
.iter()
.max_by(|a, b| luminance(**a).total_cmp(&luminance(**b)))
.unwrap(),
.expect("material contrast checks contain four background samples"),
];
frame.tokens.colors.text_primary = hex(material_foreground(
rgba(&preferred.tokens.colors.text_primary).unwrap(),
rgba(&preferred.tokens.colors.text_primary)
.expect("resolved theme colors must be valid hexadecimal RGBA"),
body_bounds,
));
frame.tokens.colors.text_muted = hex(material_foreground(
rgba(&preferred.tokens.colors.text_muted).unwrap(),
rgba(&preferred.tokens.colors.text_muted)
.expect("resolved theme colors must be valid hexadecimal RGBA"),
body_bounds,
));
frame.tokens.surface.bar.text_primary = hex(material_foreground(
rgba(&preferred.tokens.surface.bar.text_primary).unwrap(),
rgba(&preferred.tokens.surface.bar.text_primary)
.expect("resolved theme colors must be valid hexadecimal RGBA"),
bar,
));
frame.tokens.surface.bar.text_muted = hex(material_foreground(
rgba(&preferred.tokens.surface.bar.text_muted).unwrap(),
rgba(&preferred.tokens.surface.bar.text_muted)
.expect("resolved theme colors must be valid hexadecimal RGBA"),
bar,
));
frame.tokens.colors.on_accent = hex(readable(
rgba(&preferred.tokens.colors.on_accent).unwrap(),
rgba(&frame.tokens.colors.accent).unwrap(),
rgba(&preferred.tokens.colors.on_accent)
.expect("resolved theme colors must be valid hexadecimal RGBA"),
rgba(&frame.tokens.colors.accent)
.expect("resolved theme colors must be valid hexadecimal RGBA"),
4.5,
));
frame
+14 -5
View File
@@ -17,6 +17,10 @@ pub struct TileOptions {
pub id: Id,
}
/// Shows a theme family using its validated appearance palettes.
///
/// # Panics
/// The family must contain a usable palette with valid hexadecimal colors.
pub fn tile<M: Clone + 'static>(
family: &Family,
options: TileOptions,
@@ -36,7 +40,9 @@ pub fn tile<M: Clone + 'static>(
let label_color = family
.palette(variant.unwrap_or(Appearance::Light))
.or(family.dark.as_ref())
.map(|palette| crate::parse_color(&palette.text).unwrap())
.map(|palette| {
crate::parse_color(&palette.text).expect("theme family text is a validated color")
})
.unwrap_or(palette.text);
let mut caption = row([])
.spacing(6)
@@ -141,7 +147,9 @@ fn preview_with_radius(
std::sync::Mutex<std::collections::HashMap<String, (Family, icon::Handle)>>,
> = std::sync::OnceLock::new();
let cache = CACHE.get_or_init(Default::default);
let mut cache = cache.lock().unwrap();
let mut cache = cache
.lock()
.expect("theme preview cache must not be used after a mutator panics");
if cache.get(&key).is_none_or(|(cached, _)| cached != family) {
let handle = icon::from_svg_bytes(
svg_with_radius(family, variant, active, selected, radius).into_bytes(),
@@ -207,7 +215,7 @@ fn svg_with_radius(
result.push_str("<clipPath id=\"preview\"><polygon points=\"");
for [x, y] in points {
use std::fmt::Write as _;
write!(result, "{x:.4},{y:.4} ").unwrap();
write!(result, "{x:.4},{y:.4} ").expect("formatting into an owned String cannot fail");
}
result.push_str("\"/></clipPath></defs><g clip-path=\"url(#preview)\">");
for appearance in [Appearance::Light, Appearance::Dark] {
@@ -263,10 +271,11 @@ fn svg_with_radius(
.palette(active.unwrap_or(Appearance::Dark))
.or(family.dark.as_ref())
.or(family.light.as_ref())
.unwrap();
.expect("a theme family provides at least one appearance palette");
let foreground = ferese_theme_model::hex(ferese_theme_model::readable(
[1.; 4],
ferese_theme_model::rgba(&p.accent).unwrap(),
ferese_theme_model::rgba(&p.accent)
.expect("resolved theme colors must be valid hexadecimal RGBA"),
4.5,
));
result.push_str(&format!(r#"<circle cx="183" cy="16" r="10" fill="{}"/><path d="M178 16l3 3 6-6" fill="none" stroke="{foreground}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>"#, p.accent));
+9 -2
View File
@@ -136,7 +136,10 @@ impl TintCache {
.iter()
.position(|entry| std::ptr::eq(entry.source, source) && entry.colors == colors)
{
let entry = self.entries.remove(index).unwrap();
let entry = self
.entries
.remove(index)
.expect("the cache lookup identifies an existing icon entry");
let handle = entry.handle.clone();
self.entries.push_back(entry);
return handle;
@@ -147,7 +150,11 @@ impl TintCache {
if bytes <= TINT_CACHE_BYTES {
while self.entries.len() >= TINT_CACHE_ENTRIES || self.bytes + bytes > TINT_CACHE_BYTES
{
self.bytes -= self.entries.pop_front().unwrap().bytes;
self.bytes -= self
.entries
.pop_front()
.expect("cache eviction requires an entry while over budget")
.bytes;
}
self.bytes += bytes;
self.entries.push_back(TintedIcon {
+12 -2
View File
@@ -94,8 +94,18 @@ impl Palette {
stop.color.a = opacity;
}
let from = crate::composite(candidate.stops[0].unwrap().color, self.sidebar);
let to = crate::composite(candidate.stops[1].unwrap().color, self.sidebar);
let from = crate::composite(
candidate.stops[0]
.expect("the constructed linear gradient contains both endpoint stops")
.color,
self.sidebar,
);
let to = crate::composite(
candidate.stops[1]
.expect("the constructed linear gradient contains both endpoint stops")
.color,
self.sidebar,
);
let lower = Color::from_rgb(from.r.min(to.r), from.g.min(to.g), from.b.min(to.b));
let upper = Color::from_rgb(from.r.max(to.r), from.g.max(to.g), from.b.max(to.b));
let lightness = crate::luminance(foreground);
+4 -1
View File
@@ -6,7 +6,10 @@ pub fn font(family: Option<&str>) -> cosmic::font::Font {
return cosmic::font::default();
};
static FONTS: OnceLock<Mutex<HashMap<String, cosmic::font::Font>>> = OnceLock::new();
let mut fonts = FONTS.get_or_init(Default::default).lock().unwrap();
let mut fonts = FONTS
.get_or_init(Default::default)
.lock()
.expect("shared font cache must not be used after a mutator panics");
if let Some(font) = fonts.get(family) {
return *font;
}