Never crash: ui-egui is panic-free (Rule 9) (#57)
- canvas: partial redraws read last_damage with unwrap_or(EMPTY), and the
GPU cache is looked up with let-else after the upload.
- icons: a missing icon (and missing fallback) yields empty bytes, which
egui shows as a broken image, instead of expect.
- type tool: the caret update is an if-let instead of expect("editing").
- Variables dialog: pages return early if their JSON state is malformed
instead of unwrap.
- parity: the same hunk as #40.
- ui-egui denies unwrap/expect/panic/unimplemented/todo/unreachable
(a one-line attribute in lib.rs).
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
6dffa0894c
commit
86cc989e72
|
|
@ -218,7 +218,7 @@ pub fn ensure_texture(app: &mut PhotocraftApp, ctx: &egui::Context, idx: usize)
|
||||||
&& last_damage.is_some();
|
&& last_damage.is_some();
|
||||||
let t0 = crate::gpu_canvas::now_ms();
|
let t0 = crate::gpu_canvas::now_ms();
|
||||||
if partial {
|
if partial {
|
||||||
let r = last_damage.unwrap().intersect(&doc.bounds());
|
let r = last_damage.unwrap_or(DRect::EMPTY).intersect(&doc.bounds());
|
||||||
if !r.is_empty() {
|
if !r.is_empty() {
|
||||||
let buf = photocraft_compose::render(&doc, r);
|
let buf = photocraft_compose::render(&doc, r);
|
||||||
let t1 = crate::gpu_canvas::now_ms();
|
let t1 = crate::gpu_canvas::now_ms();
|
||||||
|
|
@ -313,7 +313,7 @@ fn ensure_gpu(app: &mut PhotocraftApp, idx: usize) -> bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if partial && !done {
|
if partial && !done {
|
||||||
let r = last_damage.unwrap().intersect(&doc.bounds());
|
let r = last_damage.unwrap_or(DRect::EMPTY).intersect(&doc.bounds());
|
||||||
if r.is_empty() {
|
if r.is_empty() {
|
||||||
done = true;
|
done = true;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -331,7 +331,7 @@ fn ensure_gpu(app: &mut PhotocraftApp, idx: usize) -> bool {
|
||||||
gpu.upload_buffer_full(id.0, &full);
|
gpu.upload_buffer_full(id.0, &full);
|
||||||
app.perf.record("full", size[0] as u64 * size[1] as u64, t1 - t0, crate::gpu_canvas::now_ms() - t1);
|
app.perf.record("full", size[0] as u64 * size[1] as u64, t1 - t0, crate::gpu_canvas::now_ms() - t1);
|
||||||
}
|
}
|
||||||
let cache = app.canvases.get_mut(&id).expect("inserted above");
|
let Some(cache) = app.canvases.get_mut(&id) else { return true };
|
||||||
cache.revision = revision;
|
cache.revision = revision;
|
||||||
cache.preview_key = preview_key;
|
cache.preview_key = preview_key;
|
||||||
cache.on_gpu = true;
|
cache.on_gpu = true;
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ pub fn exists(name: &str) -> bool {
|
||||||
|
|
||||||
/// An egui image for an icon, tinted.
|
/// An egui image for an icon, tinted.
|
||||||
pub fn image(name: &str, size: f32, tint: Color32) -> egui::Image<'static> {
|
pub fn image(name: &str, size: f32, tint: Color32) -> egui::Image<'static> {
|
||||||
let bytes = white_icons().get(name).or_else(|| white_icons().get("square")).cloned().expect("square icon present");
|
let bytes = white_icons().get(name).or_else(|| white_icons().get("square")).cloned().unwrap_or_default();
|
||||||
egui::Image::from_bytes(format!("bytes://icons/{name}.svg"), egui::load::Bytes::Shared(bytes)).fit_to_exact_size(Vec2::splat(size)).tint(tint)
|
egui::Image::from_bytes(format!("bytes://icons/{name}.svg"), egui::load::Bytes::Shared(bytes)).fit_to_exact_size(Vec2::splat(size)).tint(tint)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
//! data). The [`control`] module exposes both to automation, so agents can drive and inspect every
|
//! data). The [`control`] module exposes both to automation, so agents can drive and inspect every
|
||||||
//! part of the interface.
|
//! part of the interface.
|
||||||
#![forbid(unsafe_code)]
|
#![forbid(unsafe_code)]
|
||||||
|
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
|
||||||
|
|
||||||
pub mod actions;
|
pub mod actions;
|
||||||
pub mod adjust_ui;
|
pub mod adjust_ui;
|
||||||
|
|
|
||||||
|
|
@ -85,12 +85,13 @@ pub fn pointer_down(app: &mut PhotocraftApp, x: f64, y: f64, shift: bool) -> boo
|
||||||
let id = LayerId(ed.layer);
|
let id = LayerId(ed.layer);
|
||||||
if hit_layer(app, x, y) == Some(id) {
|
if hit_layer(app, x, y) == Some(id) {
|
||||||
let off = hit_offset(app, id, x, y);
|
let off = hit_offset(app, id, x, y);
|
||||||
let e = app.ui.text_edit.as_mut().expect("editing");
|
if let Some(e) = app.ui.text_edit.as_mut() {
|
||||||
e.caret = off;
|
e.caret = off;
|
||||||
if !shift {
|
if !shift {
|
||||||
e.anchor = off;
|
e.anchor = off;
|
||||||
|
}
|
||||||
|
e.dragging = true;
|
||||||
}
|
}
|
||||||
e.dragging = true;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
commit(app);
|
commit(app);
|
||||||
|
|
|
||||||
|
|
@ -138,7 +138,7 @@ pub fn body(app: &mut PhotocraftApp, ui: &mut egui::Ui, fields: &mut Map<String,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn define_page(ui: &mut egui::Ui, _t: &Tokens, layers: &[(u64, String, bool)], state: &mut Value) {
|
fn define_page(ui: &mut egui::Ui, _t: &Tokens, layers: &[(u64, String, bool)], state: &mut Value) {
|
||||||
let defs = state.get_mut("defs").and_then(Value::as_array_mut).unwrap();
|
let Some(defs) = state.get_mut("defs").and_then(Value::as_array_mut) else { return };
|
||||||
ui.label("Variables bind a layer's visibility, text or pixels to a named data slot.");
|
ui.label("Variables bind a layer's visibility, text or pixels to a named data slot.");
|
||||||
ui.add_space(4.0);
|
ui.add_space(4.0);
|
||||||
let mut remove = None;
|
let mut remove = None;
|
||||||
|
|
@ -199,7 +199,7 @@ fn data_sets_page(ui: &mut egui::Ui, _t: &Tokens, state: &mut Value, fields: &mu
|
||||||
ui.weak("Define at least one variable first.");
|
ui.weak("Define at least one variable first.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let sets = state.get_mut("dataSets").and_then(Value::as_array_mut).unwrap();
|
let Some(sets) = state.get_mut("dataSets").and_then(Value::as_array_mut) else { return };
|
||||||
let mut cur = fields.get("__cur").and_then(Value::as_u64).unwrap_or(0) as usize;
|
let mut cur = fields.get("__cur").and_then(Value::as_u64).unwrap_or(0) as usize;
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
|
|
@ -233,7 +233,7 @@ fn data_sets_page(ui: &mut egui::Ui, _t: &Tokens, state: &mut Value, fields: &mu
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
ui.add_space(4.0);
|
ui.add_space(4.0);
|
||||||
let values = set.get_mut("values").and_then(Value::as_array_mut).unwrap();
|
let Some(values) = set.get_mut("values").and_then(Value::as_array_mut) else { return };
|
||||||
for (vname, vty) in &def_meta {
|
for (vname, vty) in &def_meta {
|
||||||
// find or create a value entry for this variable
|
// find or create a value entry for this variable
|
||||||
let idx = values.iter().position(|v| v.get("variable").and_then(Value::as_str) == Some(vname));
|
let idx = values.iter().position(|v| v.get("variable").and_then(Value::as_str) == Some(vname));
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue