Merge pull request #192: update app and package icons
This commit is contained in:
commit
f6e7d754e5
2 files changed
+151
-26
No files matched your search
+104
-21
@@ -219,7 +219,7 @@ fn cmd_snapshot(args: &[String]) -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The app icon: a blue block with a bore, drawn by the CPU renderer on a transparent background
|
||||
/// The app icon: the SolidCraft logo, drawn in code on a transparent background
|
||||
/// (so packages need no committed image). `.ico` holds the Windows sizes as PNGs.
|
||||
fn cmd_icon(args: &[String]) -> Result<(), String> {
|
||||
let out = flag(args, "--out").ok_or("icon needs --out FILE.png or FILE.ico")?;
|
||||
@@ -236,30 +236,113 @@ fn cmd_icon(args: &[String]) -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// This block is the icon image data
|
||||
fn icon_png(size: usize) -> Result<Vec<u8>, String> {
|
||||
use solvecraft_engine::render::{Rgb, Scene, SceneLine, SceneMesh, png, render_transparent};
|
||||
let mut s = Session::default();
|
||||
for (cmd, p) in [
|
||||
("solid.box", json!({"length": 20, "width": 20, "height": 20, "center": [0, 0, 0]})),
|
||||
("solid.cylinder", json!({"diameter": 9, "height": 30, "base": [0, 0, -15], "operation": "cut"})),
|
||||
] {
|
||||
s.execute(cmd, &p).map_err(|e| format!("icon: {e}"))?;
|
||||
}
|
||||
let mut cam = solvecraft_engine::view::home_camera(&s);
|
||||
cam.distance *= 0.95;
|
||||
let mut scene = Scene { radius: 60.0, ..Default::default() };
|
||||
let px = size as f64 / 256.0;
|
||||
for body in &s.world_state().bodies {
|
||||
let mesh = body.mesh();
|
||||
scene.meshes.push(SceneMesh { mesh: std::sync::Arc::clone(&mesh), color: Rgb(90, 160, 240), face_colors: Vec::new() });
|
||||
for (i, e) in mesh.edges.iter().enumerate() {
|
||||
if mesh.shaded_edge_visible(i) {
|
||||
scene.lines.push(SceneLine { points: e.clone(), color: Rgb(24, 52, 96), width: (3.0 * px).max(0.8), on_top: false });
|
||||
use solvecraft_engine::render::png;
|
||||
type P = (f32, f32);
|
||||
// Coverage buffer at 4x4 samples per pixel (2x2 for big sizes), averaged down at the end.
|
||||
let ss = if size > 512 { 2 } else { 4 };
|
||||
let n = size * ss;
|
||||
let mut buf = vec![[0u8; 4]; n * n];
|
||||
let s = n as f32 / 412.217; // SVG units -> samples: the logo fills the height
|
||||
let ox = (n as f32 - 357.0 * s) / 2.0; // and is centred sideways
|
||||
// Fill a convex polygon given in SVG units.
|
||||
let mut fill = |pts: &[P], col: [u8; 3]| {
|
||||
let v: Vec<P> = pts.iter().map(|p| (ox + p.0 * s, p.1 * s)).collect();
|
||||
let (top, bottom) = v.iter().fold((f32::MAX, f32::MIN), |(a, b), p| (a.min(p.1), b.max(p.1)));
|
||||
for y in ((top - 0.5).ceil() as i32).max(0)..=((bottom - 0.5).floor() as i32).min(n as i32 - 1) {
|
||||
let cy = y as f32 + 0.5;
|
||||
let (mut lo, mut hi) = (f32::MAX, f32::MIN);
|
||||
for i in 0..v.len() {
|
||||
let (a, b) = (v[i], v[(i + 1) % v.len()]);
|
||||
if (a.1 <= cy) != (b.1 <= cy) {
|
||||
let x = a.0 + (cy - a.1) / (b.1 - a.1) * (b.0 - a.0);
|
||||
lo = lo.min(x);
|
||||
hi = hi.max(x);
|
||||
}
|
||||
}
|
||||
if lo > hi {
|
||||
continue;
|
||||
}
|
||||
for x in ((lo - 0.5).ceil() as i32).max(0)..=((hi - 0.5).floor() as i32).min(n as i32 - 1) {
|
||||
buf[y as usize * n + x as usize] = [col[0], col[1], col[2], 255];
|
||||
}
|
||||
}
|
||||
};
|
||||
// Faces: each is a few overlapping convex pieces (the filler only does convex shapes).
|
||||
let (light, mid, dark) = ([96, 158, 236], [58, 136, 238], [55, 108, 197]);
|
||||
fill(&[(3.5, 105.9), (177.0, 5.8), (263.4, 55.6), (263.8, 156.0), (177.0, 406.4)], light);
|
||||
fill(&[(177.0, 206.1), (263.8, 156.0), (350.5, 206.2), (350.5, 306.3), (177.0, 406.4)], light);
|
||||
fill(&[(3.5, 105.9), (177.0, 206.1), (177.0, 406.4)], mid);
|
||||
fill(&[(3.5, 105.9), (3.5, 205.7), (90.3, 256.2), (177.0, 206.1)], mid);
|
||||
fill(&[(177.0, 206.1), (90.3, 256.2), (90.0, 356.2), (177.0, 406.4)], mid);
|
||||
fill(&[(264.1, 155.8), (177.0, 206.1), (177.0, 406.4), (263.8, 256.2)], dark);
|
||||
fill(&[(263.8, 256.2), (350.5, 206.2), (350.5, 306.3), (177.0, 406.4)], dark);
|
||||
fill(&[(177.0, 206.1), (177.0, 406.4), (350.5, 306.3)], dark);
|
||||
fill(&[(263.8, 55.9), (263.8, 156.0), (177.1, 105.9)], dark);
|
||||
// White edge lines, 10 units wide: a rectangle per segment and a disc at each inner corner.
|
||||
let white = [255, 255, 255];
|
||||
let mut stroke = |pts: &[P], closed: bool| {
|
||||
let m = pts.len();
|
||||
for i in 0..(if closed { m } else { m - 1 }) {
|
||||
let (a, b) = (pts[i], pts[(i + 1) % m]);
|
||||
let l = ((b.0 - a.0).powi(2) + (b.1 - a.1).powi(2)).sqrt();
|
||||
let (nx, ny) = (-(b.1 - a.1) / l * 5.0, (b.0 - a.0) / l * 5.0);
|
||||
fill(&[(a.0 + nx, a.1 + ny), (b.0 + nx, b.1 + ny), (b.0 - nx, b.1 - ny), (a.0 - nx, a.1 - ny)], white);
|
||||
}
|
||||
for c in &pts[if closed { 0 } else { 1 }..if closed { m } else { m - 1 }] {
|
||||
let disc: Vec<P> = (0..16)
|
||||
.map(|k| {
|
||||
let t = k as f32 * std::f32::consts::TAU / 16.0;
|
||||
(c.0 + 5.0 * t.cos(), c.1 + 5.0 * t.sin())
|
||||
})
|
||||
.collect();
|
||||
fill(&disc, white);
|
||||
}
|
||||
};
|
||||
stroke(&[(263.8, 55.9), (177.1, 105.9), (263.8, 156.0), (263.8, 256.2), (350.6, 206.1)], false);
|
||||
stroke(&[(5.0, 105.9), (178.5, 206.1), (178.5, 406.4)], false);
|
||||
stroke(&[(177.0, 206.1), (263.8, 156.0)], false);
|
||||
stroke(
|
||||
&[
|
||||
(178.5, 5.8),
|
||||
(5.0, 105.9),
|
||||
(5.0, 206.1),
|
||||
(91.7, 256.2),
|
||||
(91.7, 356.4),
|
||||
(178.5, 406.4),
|
||||
(352.0, 306.3),
|
||||
(352.0, 206.1),
|
||||
(265.2, 156.0),
|
||||
(265.2, 55.9),
|
||||
],
|
||||
true,
|
||||
);
|
||||
// Average each ss x ss block down to one pixel (straight alpha = covered share).
|
||||
let mut rgba = Vec::with_capacity(size * size * 4);
|
||||
for y in 0..size {
|
||||
for x in 0..size {
|
||||
let (mut sum, mut cnt) = ([0u32; 3], 0u32);
|
||||
for j in 0..ss {
|
||||
for i in 0..ss {
|
||||
let p = buf[(y * ss + j) * n + x * ss + i];
|
||||
if p[3] != 0 {
|
||||
cnt += 1;
|
||||
for k in 0..3 {
|
||||
sum[k] += u32::from(p[k]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if cnt == 0 {
|
||||
rgba.extend_from_slice(&[0, 0, 0, 0]);
|
||||
} else {
|
||||
let a = 255 * cnt / (ss * ss) as u32;
|
||||
rgba.extend_from_slice(&[(sum[0] / cnt) as u8, (sum[1] / cnt) as u8, (sum[2] / cnt) as u8, a as u8]);
|
||||
}
|
||||
}
|
||||
}
|
||||
let c = render_transparent(&mut scene, &cam, size, size);
|
||||
png(c.w, c.h, c.rgba).ok_or_else(|| "icon: PNG encoding failed".to_string())
|
||||
png(size, size, rgba).ok_or_else(|| "icon: PNG encoding failed".to_string())
|
||||
}
|
||||
|
||||
/// A Windows icon file holding PNG images (Vista and later read these).
|
||||
|
||||
@@ -52,12 +52,54 @@ pub fn app_bar(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
crate::titlebar::drag_area(ui, r);
|
||||
}
|
||||
let left = r.left() + if app.integrated_titlebar { crate::titlebar::TRAFFIC_LIGHTS } else { 0.0 };
|
||||
// At high UI scale, keep the report, Home, Help and window controls reachable.
|
||||
// The theme shortcut remains available in Preferences.
|
||||
// Keep the current header controls and product name when merging the new mark.
|
||||
let compact_actions = r.right() - left < 640.0;
|
||||
// Brand mark: a small solid block drawn in code.
|
||||
let logo = Rect::from_center_size(pos2(left + 20.0, r.center().y), vec2(20.0, 20.0));
|
||||
icons::paint(ui.painter(), logo, "box", Color32::from_rgb(220, 226, 236), Color32::from_rgb(90, 160, 240), Color32::WHITE);
|
||||
// Brand mark: the SolidCraft logo, drawn in code from the SVG's own coordinates
|
||||
// (viewBox 357 x 412). Same 20 px height and centre as the old block.
|
||||
{
|
||||
let c = pos2(left + 20.0, r.center().y);
|
||||
let s = 20.0 / 412.217;
|
||||
let m =
|
||||
|pts: &[(f32, f32)]| -> Vec<egui::Pos2> { pts.iter().map(|&(x, y)| pos2(c.x + (x - 178.5) * s, c.y + (y - 206.1) * s)).collect() };
|
||||
let p = ui.painter();
|
||||
// egui only fills convex shapes, so each face is a few overlapping convex pieces.
|
||||
let face = |col: Color32, pts: &[(f32, f32)]| {
|
||||
p.add(egui::Shape::convex_polygon(m(pts), col, Stroke::NONE));
|
||||
};
|
||||
let (light, mid, dark) = (Color32::from_rgb(96, 158, 236), Color32::from_rgb(58, 136, 238), Color32::from_rgb(55, 108, 197));
|
||||
face(light, &[(3.5, 105.9), (177.0, 5.8), (263.4, 55.6), (263.8, 156.0), (177.0, 406.4)]);
|
||||
face(light, &[(177.0, 206.1), (263.8, 156.0), (350.5, 206.2), (350.5, 306.3), (177.0, 406.4)]);
|
||||
face(mid, &[(3.5, 105.9), (177.0, 206.1), (177.0, 406.4)]);
|
||||
face(mid, &[(3.5, 105.9), (3.5, 205.7), (90.3, 256.2), (177.0, 206.1)]);
|
||||
face(mid, &[(177.0, 206.1), (90.3, 256.2), (90.0, 356.2), (177.0, 406.4)]);
|
||||
face(dark, &[(264.1, 155.8), (177.0, 206.1), (177.0, 406.4), (263.8, 256.2)]);
|
||||
face(dark, &[(263.8, 256.2), (350.5, 206.2), (350.5, 306.3), (177.0, 406.4)]);
|
||||
face(dark, &[(177.0, 206.1), (177.0, 406.4), (350.5, 306.3)]);
|
||||
face(dark, &[(263.8, 55.9), (263.8, 156.0), (177.1, 105.9)]);
|
||||
// White edge lines.
|
||||
let edge = Stroke::new(10.0 * s, Color32::WHITE);
|
||||
let line = |pts: &[(f32, f32)]| {
|
||||
p.add(egui::Shape::line(m(pts), edge));
|
||||
};
|
||||
line(&[(263.8, 55.9), (177.1, 105.9), (263.8, 156.0), (263.8, 256.2), (350.6, 206.1)]);
|
||||
line(&[(5.0, 105.9), (178.5, 206.1), (178.5, 406.4)]);
|
||||
line(&[(177.0, 206.1), (263.8, 156.0)]);
|
||||
p.add(egui::Shape::closed_line(
|
||||
m(&[
|
||||
(178.5, 5.8),
|
||||
(5.0, 105.9),
|
||||
(5.0, 206.1),
|
||||
(91.7, 256.2),
|
||||
(91.7, 356.4),
|
||||
(178.5, 406.4),
|
||||
(352.0, 306.3),
|
||||
(352.0, 206.1),
|
||||
(265.2, 156.0),
|
||||
(265.2, 55.9),
|
||||
]),
|
||||
edge,
|
||||
));
|
||||
}
|
||||
if !compact_actions {
|
||||
ui.painter().text(pos2(left + 36.0, r.center().y), Align2::LEFT_CENTER, "SolidCraft", FontId::proportional(14.0), t.app_bar_text);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user