Update prompt shows the app icon and accent color instead of a letter (#2479)

UPDATE_APP (native builds; craft-update is a native-only dependency) carries our 128 px icon and accent (#3ea6e8, its colour in ArtCraft Launcher). The updater, the self-update
helper and craft-update's startup prompt all get them through crate::UPDATE_APP; the
prompt draws the icon, the accent and "by ArtCraft" (craft-update's default developer).

craft-update is pinned to storytold/craft-libs main at 1065653e7 (#434).
Clippy fixes for Rust 1.99 in untouched code (same logic):
- crates/constraints/src/auto.rs
- crates/engine/src/audit.rs
- crates/engine/src/cmd/inquiry.rs
- crates/engine/src/mtext_edit.rs
- crates/geom/src/nurbs.rs
- crates/io/src/dxf_raw.rs
- crates/io/src/dxf_read/document.rs
- crates/render/src/text.rs
- crates/ui-egui/src/cmdline.rs
- xtask/src/layers.rs
This commit is contained in:
Dylan M. Taylor
2026-10-30 01:28:46 -04:00
committed by GitHub
parent f8b09e92e5
commit d6de2db6db
15 changed files with 49 additions and 61 deletions
Generated
+2 -1
View File
@@ -1191,13 +1191,14 @@ dependencies = [
[[package]]
name = "craft-update"
version = "0.1.0"
source = "git+https://github.com/storytold/craft-libs?rev=92d392cff132eaf53e13afff77f38b5c11b73aa0#92d392cff132eaf53e13afff77f38b5c11b73aa0"
source = "git+https://github.com/storytold/craft-libs?rev=1065653e75854e9c17e1ff563acdd344c1b185f0#1065653e75854e9c17e1ff563acdd344c1b185f0"
dependencies = [
"chrono",
"dunce",
"egui",
"flate2",
"getrandom 0.3.4",
"image",
"log",
"plist",
"semver",
+2 -1
View File
@@ -65,7 +65,8 @@ eframe = { version = "0.37", default-features = false }
egui-wgpu = "0.37"
# Monotonic time on native and web (performance.now on wasm).
web-time = "1.1"
craft-update = { git = "https://github.com/storytold/craft-libs", version = "=0.1.0", rev = "92d392cff132eaf53e13afff77f38b5c11b73aa0", features = ["egui"] }
# craft-update with storytold/craft-libs#434: the update prompt shows the app's icon, accent and developer.
craft-update = { git = "https://github.com/storytold/craft-libs", version = "=0.1.0", rev = "1065653e75854e9c17e1ff563acdd344c1b185f0", features = ["egui"] }
rfd = "0.17"
arboard = { version = "3.6", default-features = false }
+1 -1
View File
@@ -218,7 +218,7 @@ fn main() -> eframe::Result {
let logger = logging::install();
// The update helper relaunches this binary after a self-update; it runs and exits here.
#[cfg(not(target_arch = "wasm32"))]
if let Some(done) = craft_update::self_update::finish(&craft_update::suite::CADCRAFT, &std::env::args_os().skip(1).collect::<Vec<_>>()) {
if let Some(done) = craft_update::self_update::finish(&cadcraft_ui_egui::UPDATE_APP, &std::env::args_os().skip(1).collect::<Vec<_>>()) {
if let Err(e) = done {
eprintln!("CADCraft update helper failed: {e}");
std::process::exit(1);
+4 -8
View File
@@ -52,15 +52,11 @@ fn collect(d: &Drawing, handles: &[Handle]) -> Geo {
g.curves.push((GeomRef::whole(h), ga.center, a.radius));
}
}
EntityKind::Circle(c) => {
if c.center.is_finite() && c.radius.is_finite() {
g.curves.push((GeomRef::whole(h), c.center.xy(), c.radius));
}
EntityKind::Circle(c) if c.center.is_finite() && c.radius.is_finite() => {
g.curves.push((GeomRef::whole(h), c.center.xy(), c.radius));
}
EntityKind::Point(p) => {
if p.p.is_finite() {
g.points.push((GeomRef::whole(h), p.p.xy()));
}
EntityKind::Point(p) if p.p.is_finite() => {
g.points.push((GeomRef::whole(h), p.p.xy()));
}
EntityKind::LwPolyline(p) => {
let n = p.vertices.len();
+6 -10
View File
@@ -460,17 +460,13 @@ fn references(d: &mut Drawing, out: &mut Vec<Issue>) {
m.style = "Standard".into();
}
}
EntityKind::Table(t) => {
if !has_name(&d.table_styles, &t.style, |s| &s.name) {
note("table style", &t.style);
t.style = "Standard".into();
}
EntityKind::Table(t) if !has_name(&d.table_styles, &t.style, |s| &s.name) => {
note("table style", &t.style);
t.style = "Standard".into();
}
EntityKind::Leader(l) => {
if !ok_dim(d, &l.style) {
note("dimension style", &l.style);
l.style = "Standard".into();
}
EntityKind::Leader(l) if !ok_dim(d, &l.style) => {
note("dimension style", &l.style);
l.style = "Standard".into();
}
EntityKind::Dimension(dim) => {
if !ok_dim(d, &dim.style) {
+5 -9
View File
@@ -840,11 +840,9 @@ impl Interactive for DistM {
let model = model_points(&self.sources);
s.echo(format!("Distance = {}", fmt_d(s, path_len(model.as_deref().unwrap_or(pts), &self.bulges))));
}
Input::Keyword(k) if k == "Undo" => {
if pts.len() > 1 {
self.arcs.undo(pts, &mut self.bulges);
self.sources.pop();
}
Input::Keyword(k) if k == "Undo" && pts.len() > 1 => {
self.arcs.undo(pts, &mut self.bulges);
self.sources.pop();
}
Input::Keyword(k) if k == "Length" => self.length = true,
Input::Keyword(k) if k == "Total" => return self.total(s, false),
@@ -852,10 +850,8 @@ impl Interactive for DistM {
self.arcs.close(pts, &mut self.bulges)?;
return self.total(s, true);
}
Input::Keyword(k) => {
if !self.arcs.keyword(&k, pts) {
s.echo(format!("{k}: not available yet"));
}
Input::Keyword(k) if !self.arcs.keyword(&k, pts) => {
s.echo(format!("{k}: not available yet"));
}
Input::Enter => return self.total(s, false),
_ => {}
+3 -5
View File
@@ -136,11 +136,9 @@ impl Formatted {
i += 2;
match code {
None => {}
Some('P') => {
if f.paras.len() < MAX_PARAS {
f.push_char('\n', &cur);
f.paras.push(props.clone());
}
Some('P') if f.paras.len() < MAX_PARAS => {
f.push_char('\n', &cur);
f.paras.push(props.clone());
}
Some(c @ ('\\' | '{' | '}')) => f.push_char(c, &cur),
Some('L') => cur.underline = true,
+1 -1
View File
@@ -487,7 +487,7 @@ impl Spline {
/// Arc length between parameters `a` and `b` (adaptive Gauss–Legendre over each knot span).
pub fn length_between(&self, a: f64, b: f64) -> f64 {
if !self.is_valid() || !(a.is_finite() && b.is_finite()) {
if !(self.is_valid() && a.is_finite() && b.is_finite()) {
return 0.0;
}
let (lo, hi) = self.domain();
+2 -4
View File
@@ -135,10 +135,8 @@ fn head(tags: &[Tag]) -> Head {
h.has_reactors |= group.as_deref() == Some("ACAD_REACTORS");
}
(_, Some("ACAD_REACTORS")) => h.reactors.push(norm(&t.str())),
(_, Some("ACAD_XDICTIONARY")) => {
if h.xdict.is_none() {
h.xdict = Some(norm(&t.str()));
}
(_, Some("ACAD_XDICTIONARY")) if h.xdict.is_none() => {
h.xdict = Some(norm(&t.str()));
}
(_, Some(_)) => {}
(5 | 105, None) if h.handle.is_none() => h.handle = Some(norm(&t.str())),
+5 -7
View File
@@ -35,13 +35,11 @@ pub(super) fn parse_entities(
extra = more;
kindent = Some((c, k));
}
"INSERT" => {
if t.i(66) == Some(1) {
let ins_layer = kindent.as_ref().map(|(c, _)| c.layer.clone()).unwrap_or_default();
let attribs = insert_attribs(recs, &mut i, &ins_layer);
if let Some((_, EntityKind::Insert(ins))) = kindent.as_mut() {
ins.attribs = attribs;
}
"INSERT" if t.i(66) == Some(1) => {
let ins_layer = kindent.as_ref().map(|(c, _)| c.layer.clone()).unwrap_or_default();
let attribs = insert_attribs(recs, &mut i, &ins_layer);
if let Some((_, EntityKind::Insert(ins))) = kindent.as_mut() {
ins.attribs = attribs;
}
}
// A VERTEX or ATTRIB with no POLYLINE or INSERT before it; a stray SEQEND holds nothing.
+1 -1
View File
@@ -77,7 +77,7 @@ pub(super) fn mesh(g: &TextGlyph, xf: Mat3, pixel: f64) -> Arc<Mesh> {
let contours = ttf::glyph_contours(&g.face, g.gid);
let points = contours.iter().map(Vec::len).try_fold(0usize, usize::checked_add);
// Font data is untrusted; don't hand unbounded or invalid paths to the tessellator.
if !points.is_some_and(|n| n <= MAX_POINTS) || contours.iter().flatten().any(|p| !p.is_finite() || p.x.abs().max(p.y.abs()) > 1.0e6) {
if points.is_none_or(|n| n > MAX_POINTS) || contours.iter().flatten().any(|p| !p.is_finite() || p.x.abs().max(p.y.abs()) > 1.0e6) {
return Arc::new(Mesh { contours: Arc::default(), triangles: Vec::new() });
}
let contours = match detail {
+5 -7
View File
@@ -379,13 +379,11 @@ pub fn keyboard(app: &mut CadApp, ctx: &egui::Context) {
app.cmd.buffer.pop();
}
}
Key::ArrowUp if !modifiers.any() => {
if !app.cmd.history.is_empty() {
let n = app.cmd.history.len();
let pos = app.cmd.history_pos.map(|p| p.saturating_sub(1)).unwrap_or(n - 1);
app.cmd.history_pos = Some(pos);
app.cmd.buffer = app.cmd.history.get(pos).cloned().unwrap_or_default();
}
Key::ArrowUp if !modifiers.any() && !app.cmd.history.is_empty() => {
let n = app.cmd.history.len();
let pos = app.cmd.history_pos.map(|p| p.saturating_sub(1)).unwrap_or(n - 1);
app.cmd.history_pos = Some(pos);
app.cmd.buffer = app.cmd.history.get(pos).cloned().unwrap_or_default();
}
Key::ArrowDown if !modifiers.any() => {
if !options_key(app, 1)
+9
View File
@@ -8,6 +8,15 @@
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
#![forbid(unsafe_code)]
/// CADCraft as craft-update knows it, with its 128 px icon and accent colour: the update
/// prompt shows both, and an AppImage's menu entry gets the icon.
#[cfg(not(target_arch = "wasm32"))]
pub const UPDATE_APP: craft_update::app::App = craft_update::app::App {
icon_png: Some(include_bytes!("../../../assets/app-icon/hicolor/128x128/apps/ai.storyteller.cadcraft.png")),
accent: Some([0x3e, 0xa6, 0xe8]),
..craft_update::suite::CADCRAFT
};
pub mod aliasdlg;
pub mod animation;
pub mod autosave;
+1 -2
View File
@@ -2,7 +2,6 @@
//! Updates… (desktop only; the web build is always the latest). The flow itself is
//! `craft_update::updater::Updater`.
use craft_update::suite::CADCRAFT;
use craft_update::updater::{Action, Status, Updater};
use crate::CadApp;
@@ -21,7 +20,7 @@ pub struct Runtime {
impl Default for Runtime {
fn default() -> Self {
Self { updater: Updater::new(&CADCRAFT, VERSION), enabled: false, check_requested: false }
Self { updater: Updater::new(&crate::UPDATE_APP, VERSION), enabled: false, check_requested: false }
}
}
+2 -4
View File
@@ -167,10 +167,8 @@ pub fn check(crates: &[Crate]) -> Vec<Violation> {
match classify(&d.name) {
// Unregistered deps are reported on their own entry.
None => {}
Some(Class::Testkit) => {
if d.kind != DepKind::Dev {
out.push(Violation::TestkitAsNormalDep { krate: c.name.clone() });
}
Some(Class::Testkit) if d.kind != DepKind::Dev => {
out.push(Violation::TestkitAsNormalDep { krate: c.name.clone() });
}
Some(dc) => {
let to = dc.layer().unwrap_or(u8::MAX);