mirror of
https://github.com/storytold/figcraft.git
synced 2026-10-30 05:16:56 -06:00
fix: pasted image keys stay archive-valid when the content hash is taken
The blend menu and its hover preview now live beside upstream's split menu module.
This commit is contained in:
@@ -393,7 +393,8 @@ pub(super) fn paint_paste(s: &mut Session, p: &Value, fills: bool) -> Result<Val
|
||||
// The destination may already use that key for different data (other layers keep
|
||||
// that image): the pasted paint takes the copied data's content hash instead. That key
|
||||
// can be taken too — images are usually keyed by their hash, so the copied key often
|
||||
// is that hash — and then a numbered variant of it.
|
||||
// is that hash — and then a numbered variant of it. Image keys become archive member
|
||||
// names, which keep only letters and digits, so the number is appended plainly.
|
||||
let base = if doc.images.get(&copied).is_some_and(|existing| existing != &d) { figcraft_format::image_hash(&d.bytes) } else { copied };
|
||||
let mut key = base.clone();
|
||||
let mut n = 0u32;
|
||||
@@ -402,7 +403,7 @@ pub(super) fn paint_paste(s: &mut Session, p: &Value, fills: bool) -> Result<Val
|
||||
if n > 999 {
|
||||
return Err(bad(cmd, "no free image key for the copied image"));
|
||||
}
|
||||
key = format!("{base}-{n}");
|
||||
key = format!("{base}{n}");
|
||||
}
|
||||
doc.images.entry(key.clone()).or_insert(d);
|
||||
img.image = key;
|
||||
|
||||
@@ -165,6 +165,22 @@ fn a_copied_image_whose_hash_key_is_also_taken_gets_a_free_key() {
|
||||
assert_ne!(img.image, hash, "the taken hash is not reused");
|
||||
assert_eq!(doc.images.get(&img.image), Some(&copied), "the pasted paint points at the copied data");
|
||||
assert_eq!(doc.images.get(&hash), Some(&taken), "the destination's image stays");
|
||||
let key = img.image.clone();
|
||||
// An image key names an archive member, and only letters and digits are read back: the
|
||||
// numbered key must bring the copied image through a save and reopen.
|
||||
assert!(key.bytes().all(|b| b.is_ascii_alphanumeric()), "the key is archive-valid: {key}");
|
||||
let dir = std::env::temp_dir().join(format!("figcraft-image-key-{}", std::process::id()));
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
let path = dir.join("pasted.figcraft").to_string_lossy().to_string();
|
||||
ex(&mut s, "file.save", json!({"path": path}));
|
||||
let mut t = Session::empty();
|
||||
ex(&mut t, "file.open", json!({"path": path}));
|
||||
let saved = t.doc().unwrap();
|
||||
let figcraft_color::PaintKind::Image(img) = &saved.node(b).unwrap().fills[0].kind else { panic!("the pasted fill is an image") };
|
||||
assert_eq!(img.image, key, "the reloaded paint keeps its key");
|
||||
// A reopened image's size is read from its pixels on demand, so only its bytes are compared.
|
||||
assert_eq!(saved.images.get(&key).map(|i| i.bytes.as_slice()), Some(copied.bytes.as_slice()), "the copied image survives the reload");
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::{BlendMode, Color32, CornerRadius, FIELD_H, Id, PAD, Rect, Sense, Tokens, Ui, a11y, pos2, regular, tr, vec2};
|
||||
use super::{BlendMode, Color32, CornerRadius, FIELD_H, FigApp, Id, NodeId, PAD, Rect, Sense, Tokens, Ui, a11y, json, pos2, regular, tr, vec2};
|
||||
|
||||
/// The smallest width a popup menu is measured in (see [`menu_popup`]).
|
||||
const MENU_MIN_W: f32 = 48.0;
|
||||
@@ -14,11 +14,6 @@ pub(crate) fn menu_style(s: &mut egui::Style) {
|
||||
s.wrap_mode = Some(egui::TextWrapMode::Extend);
|
||||
}
|
||||
|
||||
/// The menu a dropdown or button opens: as wide as its widest row and at least as wide as
|
||||
/// `anchor`. egui measures a popup in a first, invisible pass as wide as the width it is given
|
||||
/// (else its 600 px default area width) and keeps the size measured; rows ([`menu_row`]) take the
|
||||
/// larger of that width and their content, so the menu fits its content. Chain `.width(w)` for a
|
||||
/// menu at least `w` wide.
|
||||
/// The blend modes as menu items in groups (pass-through only for layers); the one picked. Each
|
||||
/// row `preview`s its mode on the canvas while hovered.
|
||||
pub fn blend_menu(ui: &mut Ui, with_pass_through: bool, mut preview: impl FnMut(BlendMode)) -> Option<BlendMode> {
|
||||
@@ -44,6 +39,11 @@ pub(crate) fn preview_blend(app: &mut FigApp, ctx: &egui::Context, ids: &[NodeId
|
||||
app.preview_text(ctx, "node.set", json!({"ids": ids, "props": {"blend": blend.label()}}));
|
||||
}
|
||||
|
||||
/// The menu a dropdown or button opens: as wide as its widest row and at least as wide as
|
||||
/// `anchor`. egui measures a popup in a first, invisible pass as wide as the width it is given
|
||||
/// (else its 600 px default area width) and keeps the size measured; rows ([`menu_row`]) take the
|
||||
/// larger of that width and their content, so the menu fits its content. Chain `.width(w)` for a
|
||||
/// menu at least `w` wide.
|
||||
pub fn menu_popup(anchor: &egui::Response) -> egui::Popup<'static> {
|
||||
egui::Popup::menu(anchor).width(anchor.rect.width().max(MENU_MIN_W)).style(menu_style)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user