mirror of
https://github.com/storytold/deckcraft.git
synced 2026-10-26 11:36:58 -06:00
Compare commits
9
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9eb72b66f3 | ||
|
|
399d923516 | ||
|
|
0cff3ac942 | ||
|
|
d0b097d837 | ||
|
|
14f40c408c | ||
|
|
9ec3926793 | ||
|
|
79bf9a688c | ||
|
|
1330ce4b86 | ||
|
|
a0eeb341d2 |
Generated
+28
-28
@@ -857,7 +857,7 @@ checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f"
|
||||
|
||||
[[package]]
|
||||
name = "deckcraft"
|
||||
version = "0.10.0"
|
||||
version = "0.11.1"
|
||||
dependencies = [
|
||||
"arboard",
|
||||
"chrono",
|
||||
@@ -878,7 +878,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deckcraft-anim"
|
||||
version = "0.10.0"
|
||||
version = "0.11.1"
|
||||
dependencies = [
|
||||
"deckcraft-geom",
|
||||
"deckcraft-model",
|
||||
@@ -887,7 +887,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deckcraft-av1"
|
||||
version = "0.10.0"
|
||||
version = "0.11.1"
|
||||
dependencies = [
|
||||
"deckcraft-bitstream",
|
||||
"rayon",
|
||||
@@ -896,14 +896,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deckcraft-bitstream"
|
||||
version = "0.10.0"
|
||||
version = "0.11.1"
|
||||
dependencies = [
|
||||
"craft-media",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deckcraft-cli"
|
||||
version = "0.10.0"
|
||||
version = "0.11.1"
|
||||
dependencies = [
|
||||
"deckcraft-engine",
|
||||
"deckcraft-mcp",
|
||||
@@ -913,14 +913,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deckcraft-color"
|
||||
version = "0.10.0"
|
||||
version = "0.11.1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deckcraft-engine"
|
||||
version = "0.10.0"
|
||||
version = "0.11.1"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"deckcraft-anim",
|
||||
@@ -946,7 +946,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deckcraft-fonts"
|
||||
version = "0.10.0"
|
||||
version = "0.11.1"
|
||||
dependencies = [
|
||||
"epaint_default_fonts",
|
||||
"harfrust 0.13.3",
|
||||
@@ -958,7 +958,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deckcraft-format"
|
||||
version = "0.10.0"
|
||||
version = "0.11.1"
|
||||
dependencies = [
|
||||
"deckcraft-model",
|
||||
"serde",
|
||||
@@ -969,7 +969,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deckcraft-geom"
|
||||
version = "0.10.0"
|
||||
version = "0.11.1"
|
||||
dependencies = [
|
||||
"kurbo",
|
||||
"serde",
|
||||
@@ -977,7 +977,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deckcraft-h264"
|
||||
version = "0.10.0"
|
||||
version = "0.11.1"
|
||||
dependencies = [
|
||||
"deckcraft-bitstream",
|
||||
"rayon",
|
||||
@@ -986,7 +986,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deckcraft-hevc"
|
||||
version = "0.10.0"
|
||||
version = "0.11.1"
|
||||
dependencies = [
|
||||
"deckcraft-bitstream",
|
||||
"rayon",
|
||||
@@ -995,21 +995,21 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deckcraft-isobmff"
|
||||
version = "0.10.0"
|
||||
version = "0.11.1"
|
||||
dependencies = [
|
||||
"craft-media",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deckcraft-matroska"
|
||||
version = "0.10.0"
|
||||
version = "0.11.1"
|
||||
dependencies = [
|
||||
"craft-media",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deckcraft-mcp"
|
||||
version = "0.10.0"
|
||||
version = "0.11.1"
|
||||
dependencies = [
|
||||
"deckcraft-engine",
|
||||
"deckcraft-render",
|
||||
@@ -1019,7 +1019,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deckcraft-media"
|
||||
version = "0.10.0"
|
||||
version = "0.11.1"
|
||||
dependencies = [
|
||||
"deckcraft-av1",
|
||||
"deckcraft-h264",
|
||||
@@ -1037,7 +1037,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deckcraft-metafile"
|
||||
version = "0.10.0"
|
||||
version = "0.11.1"
|
||||
dependencies = [
|
||||
"image",
|
||||
"kurbo",
|
||||
@@ -1046,7 +1046,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deckcraft-model"
|
||||
version = "0.10.0"
|
||||
version = "0.11.1"
|
||||
dependencies = [
|
||||
"deckcraft-color",
|
||||
"deckcraft-geom",
|
||||
@@ -1058,15 +1058,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deckcraft-ogg"
|
||||
version = "0.10.0"
|
||||
version = "0.11.1"
|
||||
|
||||
[[package]]
|
||||
name = "deckcraft-opus"
|
||||
version = "0.10.0"
|
||||
version = "0.11.1"
|
||||
|
||||
[[package]]
|
||||
name = "deckcraft-pdf"
|
||||
version = "0.10.0"
|
||||
version = "0.11.1"
|
||||
dependencies = [
|
||||
"craft-pdf",
|
||||
"deckcraft-fonts",
|
||||
@@ -1084,7 +1084,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deckcraft-pptx"
|
||||
version = "0.10.0"
|
||||
version = "0.11.1"
|
||||
dependencies = [
|
||||
"deckcraft-color",
|
||||
"deckcraft-engine",
|
||||
@@ -1100,7 +1100,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deckcraft-render"
|
||||
version = "0.10.0"
|
||||
version = "0.11.1"
|
||||
dependencies = [
|
||||
"deckcraft-anim",
|
||||
"deckcraft-color",
|
||||
@@ -1119,7 +1119,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deckcraft-text"
|
||||
version = "0.10.0"
|
||||
version = "0.11.1"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"deckcraft-color",
|
||||
@@ -1133,7 +1133,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deckcraft-ui-egui"
|
||||
version = "0.10.0"
|
||||
version = "0.11.1"
|
||||
dependencies = [
|
||||
"deckcraft-anim",
|
||||
"deckcraft-color",
|
||||
@@ -1156,7 +1156,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deckcraft-vp9"
|
||||
version = "0.10.0"
|
||||
version = "0.11.1"
|
||||
dependencies = [
|
||||
"deckcraft-bitstream",
|
||||
"rayon",
|
||||
@@ -1165,7 +1165,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deckcraft-web"
|
||||
version = "0.10.0"
|
||||
version = "0.11.1"
|
||||
dependencies = [
|
||||
"deckcraft-engine",
|
||||
"deckcraft-ui-egui",
|
||||
@@ -5564,7 +5564,7 @@ checksum = "9440ea3e5aeabb0ac63af70daf835274065238cdd0cec83418f417eae38bacee"
|
||||
|
||||
[[package]]
|
||||
name = "xtask"
|
||||
version = "0.10.0"
|
||||
version = "0.11.1"
|
||||
dependencies = [
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ members = ["crates/*", "apps/*", "xtask"]
|
||||
default-members = ["crates/*", "apps/deckcraft", "apps/deckcraft-cli"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.10.0"
|
||||
version = "0.11.1"
|
||||
edition = "2024"
|
||||
license = "MIT OR Apache-2.0"
|
||||
rust-version = "1.95"
|
||||
|
||||
@@ -7,7 +7,10 @@ use deckcraft_model::{
|
||||
AnimClass, AnimStart, Animation, ColorRef, CustomShow, Presentation, Rgba, Shape, ShapeId, Slide, SlideId, TextBody, Transition, Xfrm,
|
||||
};
|
||||
|
||||
use crate::*;
|
||||
use crate::{
|
||||
AnimState, Layer, LinkJump, MotionPath, ShowAction, ShowState, Source, Timeline, TriggerPlay, default_path, ease, morph_crop, morph_frame,
|
||||
morph_pairs, morph_xfrm, show_order, transition_frame, transition_layers,
|
||||
};
|
||||
|
||||
const W: f64 = 960.0;
|
||||
const H: f64 = 540.0;
|
||||
|
||||
@@ -6,7 +6,7 @@ use std::sync::Arc;
|
||||
use deckcraft_model::text::Action;
|
||||
use deckcraft_model::{AnimClass, AnimStart, Animation, Presentation, Shape, ShapeId, Slide, SlideId, Transition, Xfrm};
|
||||
|
||||
use crate::*;
|
||||
use crate::{Click, HitShape, LinkJump, ShowAction, ShowState, Target, Timeline};
|
||||
|
||||
fn shape(id: u32) -> Shape {
|
||||
Shape { id: ShapeId(id), name: format!("S{id}"), xfrm: Some(Xfrm::new(100.0, 100.0, 200.0, 100.0)), ..Default::default() }
|
||||
|
||||
@@ -515,6 +515,244 @@ pub fn preset(name: &str) -> Option<Rgba> {
|
||||
Some(Rgba::rgb(c.0, c.1, c.2))
|
||||
}
|
||||
|
||||
/// Every preset colour name a file may write (`ST_PresetColorVal`, with the later editions' aliases).
|
||||
pub const PRESET_NAMES: [&str; 190] = [
|
||||
"aliceBlue",
|
||||
"antiqueWhite",
|
||||
"aqua",
|
||||
"aquamarine",
|
||||
"azure",
|
||||
"beige",
|
||||
"bisque",
|
||||
"black",
|
||||
"blanchedAlmond",
|
||||
"blue",
|
||||
"blueViolet",
|
||||
"brown",
|
||||
"burlyWood",
|
||||
"cadetBlue",
|
||||
"chartreuse",
|
||||
"chocolate",
|
||||
"coral",
|
||||
"cornflowerBlue",
|
||||
"cornsilk",
|
||||
"crimson",
|
||||
"cyan",
|
||||
"darkBlue",
|
||||
"darkCyan",
|
||||
"darkGoldenrod",
|
||||
"darkGray",
|
||||
"darkGrey",
|
||||
"darkGreen",
|
||||
"darkKhaki",
|
||||
"darkMagenta",
|
||||
"darkOliveGreen",
|
||||
"darkOrange",
|
||||
"darkOrchid",
|
||||
"darkRed",
|
||||
"darkSalmon",
|
||||
"darkSeaGreen",
|
||||
"darkSlateBlue",
|
||||
"darkSlateGray",
|
||||
"darkSlateGrey",
|
||||
"darkTurquoise",
|
||||
"darkViolet",
|
||||
"dkBlue",
|
||||
"dkCyan",
|
||||
"dkGoldenrod",
|
||||
"dkGray",
|
||||
"dkGrey",
|
||||
"dkGreen",
|
||||
"dkKhaki",
|
||||
"dkMagenta",
|
||||
"dkOliveGreen",
|
||||
"dkOrange",
|
||||
"dkOrchid",
|
||||
"dkRed",
|
||||
"dkSalmon",
|
||||
"dkSeaGreen",
|
||||
"dkSlateBlue",
|
||||
"dkSlateGray",
|
||||
"dkSlateGrey",
|
||||
"dkTurquoise",
|
||||
"dkViolet",
|
||||
"deepPink",
|
||||
"deepSkyBlue",
|
||||
"dimGray",
|
||||
"dimGrey",
|
||||
"dodgerBlue",
|
||||
"firebrick",
|
||||
"floralWhite",
|
||||
"forestGreen",
|
||||
"fuchsia",
|
||||
"gainsboro",
|
||||
"ghostWhite",
|
||||
"gold",
|
||||
"goldenrod",
|
||||
"gray",
|
||||
"grey",
|
||||
"green",
|
||||
"greenYellow",
|
||||
"honeydew",
|
||||
"hotPink",
|
||||
"indianRed",
|
||||
"indigo",
|
||||
"ivory",
|
||||
"khaki",
|
||||
"lavender",
|
||||
"lavenderBlush",
|
||||
"lawnGreen",
|
||||
"lemonChiffon",
|
||||
"lightBlue",
|
||||
"lightCoral",
|
||||
"lightCyan",
|
||||
"lightGoldenrodYellow",
|
||||
"lightGray",
|
||||
"lightGrey",
|
||||
"lightGreen",
|
||||
"lightPink",
|
||||
"lightSalmon",
|
||||
"lightSeaGreen",
|
||||
"lightSkyBlue",
|
||||
"lightSlateGray",
|
||||
"lightSlateGrey",
|
||||
"lightSteelBlue",
|
||||
"lightYellow",
|
||||
"ltBlue",
|
||||
"ltCoral",
|
||||
"ltCyan",
|
||||
"ltGoldenrodYellow",
|
||||
"ltGray",
|
||||
"ltGrey",
|
||||
"ltGreen",
|
||||
"ltPink",
|
||||
"ltSalmon",
|
||||
"ltSeaGreen",
|
||||
"ltSkyBlue",
|
||||
"ltSlateGray",
|
||||
"ltSlateGrey",
|
||||
"ltSteelBlue",
|
||||
"ltYellow",
|
||||
"lime",
|
||||
"limeGreen",
|
||||
"linen",
|
||||
"magenta",
|
||||
"maroon",
|
||||
"medAquamarine",
|
||||
"medBlue",
|
||||
"medOrchid",
|
||||
"medPurple",
|
||||
"medSeaGreen",
|
||||
"medSlateBlue",
|
||||
"medSpringGreen",
|
||||
"medTurquoise",
|
||||
"medVioletRed",
|
||||
"mediumAquamarine",
|
||||
"mediumBlue",
|
||||
"mediumOrchid",
|
||||
"mediumPurple",
|
||||
"mediumSeaGreen",
|
||||
"mediumSlateBlue",
|
||||
"mediumSpringGreen",
|
||||
"mediumTurquoise",
|
||||
"mediumVioletRed",
|
||||
"midnightBlue",
|
||||
"mintCream",
|
||||
"mistyRose",
|
||||
"moccasin",
|
||||
"navajoWhite",
|
||||
"navy",
|
||||
"oldLace",
|
||||
"olive",
|
||||
"oliveDrab",
|
||||
"orange",
|
||||
"orangeRed",
|
||||
"orchid",
|
||||
"paleGoldenrod",
|
||||
"paleGreen",
|
||||
"paleTurquoise",
|
||||
"paleVioletRed",
|
||||
"papayaWhip",
|
||||
"peachPuff",
|
||||
"peru",
|
||||
"pink",
|
||||
"plum",
|
||||
"powderBlue",
|
||||
"purple",
|
||||
"red",
|
||||
"rosyBrown",
|
||||
"royalBlue",
|
||||
"saddleBrown",
|
||||
"salmon",
|
||||
"sandyBrown",
|
||||
"seaGreen",
|
||||
"seaShell",
|
||||
"sienna",
|
||||
"silver",
|
||||
"skyBlue",
|
||||
"slateBlue",
|
||||
"slateGray",
|
||||
"slateGrey",
|
||||
"snow",
|
||||
"springGreen",
|
||||
"steelBlue",
|
||||
"tan",
|
||||
"teal",
|
||||
"thistle",
|
||||
"tomato",
|
||||
"turquoise",
|
||||
"violet",
|
||||
"wheat",
|
||||
"white",
|
||||
"whiteSmoke",
|
||||
"yellow",
|
||||
"yellowGreen",
|
||||
];
|
||||
|
||||
/// Every system colour name a file may write (`ST_SystemColorVal`).
|
||||
pub const SYSTEM_NAMES: [&str; 30] = [
|
||||
"scrollBar",
|
||||
"background",
|
||||
"activeCaption",
|
||||
"inactiveCaption",
|
||||
"menu",
|
||||
"window",
|
||||
"windowFrame",
|
||||
"menuText",
|
||||
"windowText",
|
||||
"captionText",
|
||||
"activeBorder",
|
||||
"inactiveBorder",
|
||||
"appWorkspace",
|
||||
"highlight",
|
||||
"highlightText",
|
||||
"btnFace",
|
||||
"btnShadow",
|
||||
"grayText",
|
||||
"btnText",
|
||||
"inactiveCaptionText",
|
||||
"btnHighlight",
|
||||
"3dDkShadow",
|
||||
"3dLight",
|
||||
"infoText",
|
||||
"infoBk",
|
||||
"hotLight",
|
||||
"gradientActiveCaption",
|
||||
"gradientInactiveCaption",
|
||||
"menuHighlight",
|
||||
"menuBar",
|
||||
];
|
||||
|
||||
/// Whether `name` is a preset colour name a file may write.
|
||||
pub fn is_preset_name(name: &str) -> bool {
|
||||
PRESET_NAMES.contains(&name)
|
||||
}
|
||||
|
||||
/// Whether `name` is a system colour name a file may write.
|
||||
pub fn is_system_name(name: &str) -> bool {
|
||||
SYSTEM_NAMES.contains(&name)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -5,7 +5,7 @@ use std::sync::Arc;
|
||||
use deckcraft_model::anim::{ANIMATIONS, AnimClass, AnimStart, Animation, TextBuild, animation_info};
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use super::*;
|
||||
use super::{CommandSpec, always, bad, bool_or, bool_param, cmd, has_selection, has_slide, id_param, ok, str_param, targets, usize_param};
|
||||
use crate::{Result, Session};
|
||||
|
||||
pub fn specs() -> Vec<CommandSpec> {
|
||||
|
||||
@@ -4,7 +4,10 @@ use deckcraft_geom::{Point, Rect, Xfrm};
|
||||
use deckcraft_model::{Shape, ShapeId, ShapeKind};
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use super::*;
|
||||
use super::{
|
||||
CommandSpec, NO_ID_SPACE, bad, bool_or, cmd, edit_geometry, has_doc, has_selection, has_slide, has_two, id_param, ok, str_param, targets,
|
||||
usize_param, with_param, xfrm_of,
|
||||
};
|
||||
use crate::{Result, Session};
|
||||
|
||||
pub fn specs() -> Vec<CommandSpec> {
|
||||
|
||||
@@ -9,7 +9,7 @@ use deckcraft_model::{MediaId, ShapeId, ShapeKind};
|
||||
use image::{GrayImage, Rgba, RgbaImage};
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use super::*;
|
||||
use super::{CommandSpec, NO_ID_SPACE, bad, cmd, has_selection, str_param, targets};
|
||||
use crate::{Result, Session};
|
||||
|
||||
/// The effects offered: `a14` element name and label.
|
||||
|
||||
@@ -8,7 +8,7 @@ use std::collections::VecDeque;
|
||||
use deckcraft_model::{ShapeId, ShapeKind};
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use super::*;
|
||||
use super::{CommandSpec, NO_ID_SPACE, bad, cmd, f64_or, has_selection, targets};
|
||||
use crate::{Result, Session};
|
||||
|
||||
/// Larger pictures are refused rather than matted slowly (16 megapixels).
|
||||
@@ -184,7 +184,12 @@ fn remove_background(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use deckcraft_model::{ShapeId, ShapeKind};
|
||||
use serde_json::json;
|
||||
|
||||
use super::*;
|
||||
use crate::Session;
|
||||
use crate::cmd::base64_encode;
|
||||
|
||||
/// A red disc on a white-to-grey gradient.
|
||||
fn disc() -> image::RgbaImage {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use crate::*;
|
||||
use crate::Session;
|
||||
|
||||
fn deck() -> Session {
|
||||
let mut s = Session::with_new();
|
||||
|
||||
@@ -8,7 +8,7 @@ use deckcraft_model::style::PictureFill;
|
||||
use deckcraft_model::{MediaId, Shape, ShapeId, ShapeKind};
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use super::*;
|
||||
use super::{CommandSpec, NO_ID_SPACE, bad, bool_or, cmd, f64_param, has_doc, targets};
|
||||
use crate::{Result, Session};
|
||||
|
||||
pub fn specs() -> Vec<CommandSpec> {
|
||||
|
||||
@@ -10,7 +10,10 @@ use deckcraft_model::theme::{builtin_color_schemes, builtin_font_schemes, builti
|
||||
use deckcraft_model::{Background, Fill, PhType, Presentation, Shape, ShapeId, defaults};
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use super::*;
|
||||
use super::{
|
||||
CommandSpec, NO_ID_SPACE, always, bad, bool_or, bool_param, cmd, color_param, color_value, f64_param, has_doc, has_slide, ok, rect_param,
|
||||
str_param, usize_param, with_param,
|
||||
};
|
||||
use crate::{Result, Session, Target};
|
||||
|
||||
pub fn specs() -> Vec<CommandSpec> {
|
||||
|
||||
@@ -7,7 +7,10 @@ use deckcraft_model::text::TextBody;
|
||||
use deckcraft_model::{MediaId, Shape, ShapeId, Slide, SlideId};
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use super::*;
|
||||
use super::{
|
||||
CommandSpec, NO_ID_SPACE, bad, base64_encode, bool_or, can_redo, can_undo, cmd, has_doc, has_selection, has_slide, id_param, ids_param, ok,
|
||||
richclip, str_param, targets, xfrm_of,
|
||||
};
|
||||
use crate::{HistoryEntry, Result, Session};
|
||||
|
||||
pub fn specs() -> Vec<CommandSpec> {
|
||||
@@ -245,7 +248,11 @@ fn bring_media(cmd: &str, doc: &mut deckcraft_model::Presentation, media: &[deck
|
||||
let mut map = HashMap::new();
|
||||
for m in media {
|
||||
match doc.media(m.id) {
|
||||
None => doc.media.push(m.clone()),
|
||||
None => {
|
||||
// Keeps the foreign id, so later ids must start past it.
|
||||
doc.media.push(m.clone());
|
||||
doc.next_id = doc.next_id.max(m.id.0);
|
||||
}
|
||||
Some(x) if x.data == m.data => {}
|
||||
Some(_) => {
|
||||
let id = doc.add_media(&m.name, &m.content_type, m.data.as_ref().clone()).ok_or_else(|| bad(cmd, NO_ID_SPACE))?;
|
||||
|
||||
@@ -7,7 +7,7 @@ use deckcraft_media::MediaInfo;
|
||||
use deckcraft_model::{MediaId, MediaItem, Presentation, Shape, ShapeKind, defaults};
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use super::*;
|
||||
use super::{CommandSpec, always, bad, base64_decode, base64_encode, bool_or, cmd, f64_or, has_doc, ok, str_param, usize_param};
|
||||
use crate::{DocState, EngineError, Result, Session};
|
||||
|
||||
pub fn specs() -> Vec<CommandSpec> {
|
||||
|
||||
@@ -6,7 +6,7 @@ use deckcraft_model::text::RunKind;
|
||||
use deckcraft_model::{Paragraph, Shape, ShapeKind, Slide, TextBody};
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use super::*;
|
||||
use super::{CommandSpec, bad, bool_or, cmd, has_doc, str_param};
|
||||
use crate::{Result, Session};
|
||||
|
||||
pub fn specs() -> Vec<CommandSpec> {
|
||||
|
||||
@@ -8,8 +8,12 @@ use deckcraft_model::{ColorRef, ColorTransform, Effects, Fill, Line, SchemeSlot,
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use super::text::{format_body, format_para, format_run};
|
||||
use super::*;
|
||||
use super::{
|
||||
CommandSpec, always, bad, base64_decode, bool_or, bool_param, cmd, color_param, edit_shapes, f64_or, f64_param, has_doc, has_text,
|
||||
has_text_or_shapes, ids_param, ok, str_param,
|
||||
};
|
||||
use crate::{Result, Session};
|
||||
use deckcraft_model::ShapeId;
|
||||
|
||||
pub fn specs() -> Vec<CommandSpec> {
|
||||
vec![
|
||||
|
||||
@@ -8,7 +8,7 @@ use deckcraft_model::resolve::{self, Ctx};
|
||||
use deckcraft_model::{Fill, Presentation, Rgba, Shape, ShapeKind};
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use super::*;
|
||||
use super::{CommandSpec, bad, cmd, has_slide, str_param, usize_param, xfrm_of};
|
||||
use crate::{Result, Selection, Session};
|
||||
|
||||
pub fn specs() -> Vec<CommandSpec> {
|
||||
|
||||
@@ -8,7 +8,10 @@ use deckcraft_model::text::{AutoFit, RunKind, TextBody};
|
||||
use deckcraft_model::{Chart, ColorRef, Fill, Geom, Line, MediaClip, Shape, ShapeId, ShapeKind, ShapeStyle, Table};
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use super::*;
|
||||
use super::{
|
||||
CommandSpec, NO_ID_SPACE, always, bad, base64_decode, base64_encode, bool_or, bool_param, cmd, color_value, edit_shapes, has_doc, has_selection,
|
||||
has_slide, has_text_or_shapes, ok, rect_param, str_param, usize_param, xfrm_of,
|
||||
};
|
||||
use crate::{EngineError, Result, Session};
|
||||
|
||||
pub fn specs() -> Vec<CommandSpec> {
|
||||
|
||||
@@ -4,7 +4,7 @@ use deckcraft_model::resolve::{self, Ctx};
|
||||
use deckcraft_model::{Shape, ShapeKind};
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use super::*;
|
||||
use super::{CommandSpec, always, bad, cmd, has_doc, id_param, str_param, usize_param, xfrm_of};
|
||||
use crate::{Result, Session};
|
||||
|
||||
pub fn specs() -> Vec<CommandSpec> {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
use deckcraft_model::{MediaClip, ShapeId, ShapeKind};
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use super::*;
|
||||
use super::{CommandSpec, NO_ID_SPACE, bad, bool_param, cmd, edit_shapes, has_doc, targets};
|
||||
use crate::{EngineError, MediaStatus, Result, Session, UiRequest};
|
||||
|
||||
pub fn specs() -> Vec<CommandSpec> {
|
||||
@@ -179,7 +179,11 @@ fn gif_play(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use deckcraft_model::ShapeId;
|
||||
use serde_json::json;
|
||||
|
||||
use crate::cmd::base64_encode;
|
||||
use crate::{MediaStatus, Session, UiRequest};
|
||||
|
||||
fn wav() -> Vec<u8> {
|
||||
let rate = 8000u32;
|
||||
|
||||
@@ -9,7 +9,7 @@ use i_overlay::core::overlay_rule::OverlayRule;
|
||||
use i_overlay::float::single::SingleFloatOverlay;
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use super::*;
|
||||
use super::{CommandSpec, NO_ID_SPACE, bad, cmd, has_two, str_param, targets, xfrm_of};
|
||||
use crate::{Result, Session};
|
||||
|
||||
/// Closed polygons (slide points) describing a filled area (non-zero winding).
|
||||
@@ -255,7 +255,12 @@ fn merge(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use deckcraft_model::{ShapeId, ShapeKind};
|
||||
use serde_json::json;
|
||||
|
||||
use super::{Area, area_of, area_size, letters_of, merge_areas};
|
||||
use crate::Session;
|
||||
use crate::cmd::base64_encode;
|
||||
|
||||
fn square(x: f64, y: f64, s: f64) -> Area {
|
||||
vec![vec![[x, y], [x + s, y], [x + s, y + s], [x, y + s]]]
|
||||
|
||||
@@ -394,8 +394,14 @@ pub(crate) fn color_value(v: &Value) -> Option<deckcraft_model::ColorRef> {
|
||||
let mut c = if let Some(s) = o.get("scheme").and_then(Value::as_str) {
|
||||
ColorRef::scheme(deckcraft_color::SchemeSlot::from_xml(s)?)
|
||||
} else if let Some(name) = o.get("preset").and_then(Value::as_str) {
|
||||
if !deckcraft_color::is_preset_name(name) {
|
||||
return None;
|
||||
}
|
||||
ColorRef { base: deckcraft_model::ColorBase::Preset { name: name.to_string() }, mods: vec![] }
|
||||
} else if let Some(name) = o.get("system").and_then(Value::as_str) {
|
||||
if !deckcraft_color::is_system_name(name) {
|
||||
return None;
|
||||
}
|
||||
let last = deckcraft_color::Rgba::from_hex(o.get("last").and_then(Value::as_str)?)?;
|
||||
ColorRef { base: deckcraft_model::ColorBase::System { name: name.to_string(), last }, mods: vec![] }
|
||||
} else {
|
||||
@@ -657,5 +663,11 @@ mod tests {
|
||||
let system = color_value(&json!({"system": "windowText", "last": "#123456"}));
|
||||
assert_eq!(system, Some(ColorRef { base: ColorBase::System { name: "windowText".into(), last: Rgba::rgb(0x12, 0x34, 0x56) }, mods: vec![] }));
|
||||
assert_eq!(color_value(&json!({"system": "windowText"})), None, "a system color needs its last value");
|
||||
assert_eq!(color_value(&json!({"preset": "notAColor"})), None);
|
||||
assert_eq!(color_value(&json!({"system": "notAColor", "last": "#000000"})), None);
|
||||
for name in ["aliceBlue", "darkBlue", "ltSlateGrey"] {
|
||||
assert!(color_value(&json!({"preset": name})).is_some(), "{name}");
|
||||
}
|
||||
assert!(color_value(&json!({"system": "3dDkShadow", "last": "#000000"})).is_some());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ use deckcraft_model::style::{Effects, Reflection, Shadow};
|
||||
use deckcraft_model::{ColorRef, Geom, Line, Shape};
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use super::*;
|
||||
use super::{CommandSpec, always, bad, cmd, edit_shapes, has_selection, str_param};
|
||||
use crate::{Result, Session};
|
||||
|
||||
pub fn specs() -> Vec<CommandSpec> {
|
||||
|
||||
@@ -10,7 +10,7 @@ use deckcraft_geom::{PathEl, Point, Xfrm};
|
||||
use deckcraft_model::{CustomPath, Geom, Shape, ShapeId, ShapeKind};
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use super::*;
|
||||
use super::{CommandSpec, bad, cmd, f64_param, has_selection, id_param, ok, str_param, xfrm_of};
|
||||
use crate::{Result, Session};
|
||||
|
||||
pub fn specs() -> Vec<CommandSpec> {
|
||||
|
||||
@@ -6,7 +6,7 @@ use deckcraft_model::style::{PictureAdjust, PictureMode};
|
||||
use deckcraft_model::{ColorRef, Shape, ShapeId, ShapeKind};
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use super::*;
|
||||
use super::{CommandSpec, always, bad, bool_or, cmd, current_shapes, edit_shapes, has_selection, str_param, targets, text, xfrm_of};
|
||||
use crate::{Result, Session};
|
||||
|
||||
pub fn specs() -> Vec<CommandSpec> {
|
||||
|
||||
@@ -13,7 +13,7 @@ use deckcraft_model::text::{Action, Hyperlink, RunProps, TextBody};
|
||||
use deckcraft_model::{LayoutId, Master, MasterId, MediaId, Presentation, ShapeId, ShapeKind, Slide, SlideId};
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use super::*;
|
||||
use super::{CommandSpec, NO_ID_SPACE, bad, bool_or, cmd, has_doc, usize_param};
|
||||
use crate::{Result, Session, media_refs};
|
||||
|
||||
pub fn specs() -> Vec<CommandSpec> {
|
||||
|
||||
@@ -5,7 +5,9 @@ use std::sync::Arc;
|
||||
use deckcraft_model::Comment;
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use super::*;
|
||||
use super::{
|
||||
CommandSpec, always, bad, base64_decode, bool_or, bool_param, cmd, f64_or, has_doc, has_slide, has_text, id_param, ok, str_param, usize_param,
|
||||
};
|
||||
use crate::{Result, Session, Target, UiRequest};
|
||||
|
||||
pub fn specs() -> Vec<CommandSpec> {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
use deckcraft_model::{Shape, ShapeId};
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use super::*;
|
||||
use super::{CommandSpec, bad, bool_or, cmd, has_slide, id_param, ids_param, ok};
|
||||
use crate::{Result, Session};
|
||||
|
||||
pub fn specs() -> Vec<CommandSpec> {
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use crate::Session;
|
||||
use crate::tools::{Mods, PointerEvent, PointerKind};
|
||||
use crate::*;
|
||||
|
||||
/// A blank slide with A, B, C (back to front) and G = group [D, H = group [E, F]] in front.
|
||||
fn deck() -> (Session, Vec<u32>) {
|
||||
|
||||
@@ -8,7 +8,10 @@ use deckcraft_model::style::{Dash, Glow, Gradient, GradientShape, GradientStop,
|
||||
use deckcraft_model::{ColorRef, Effects, Fill, Geom, Line, ShapeStyle};
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use super::*;
|
||||
use super::{
|
||||
CommandSpec, NO_ID_SPACE, bad, bool_or, bool_param, cmd, color_param, color_value, edit_geometry, edit_shapes, f64_param, has_selection,
|
||||
has_slide, id_param, ok, str_param, targets, usize_param, with_param, xfrm_of,
|
||||
};
|
||||
use crate::{Result, Session};
|
||||
|
||||
pub fn specs() -> Vec<CommandSpec> {
|
||||
@@ -1519,6 +1522,8 @@ fn ink_to_shape(s: &mut Session, _p: &Value) -> Result<Value> {
|
||||
mod tests {
|
||||
#![allow(clippy::unwrap_used)]
|
||||
|
||||
use deckcraft_model::ShapeId;
|
||||
|
||||
use super::*;
|
||||
use serde_json::json;
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ use deckcraft_model::text::TextBody;
|
||||
use deckcraft_model::{LayoutId, LayoutType, Presentation, Section, SlideId, defaults};
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use super::*;
|
||||
use super::{CommandSpec, NO_ID_SPACE, bad, bool_or, bool_param, cmd, has_doc, has_slide, ok, opt_usize_param, str_param, usize_param};
|
||||
use crate::{Result, Session, Target};
|
||||
|
||||
pub fn specs() -> Vec<CommandSpec> {
|
||||
|
||||
@@ -14,7 +14,7 @@ use deckcraft_model::{
|
||||
};
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use super::*;
|
||||
use super::{CommandSpec, NO_ID_SPACE, bad, bool_or, cmd, current_shapes, has_selection, shape_of, str_param, targets, xfrm_of};
|
||||
use crate::{EngineError, Result, Session};
|
||||
|
||||
/// Deepest outline level the Text Pane allows.
|
||||
|
||||
@@ -4,7 +4,10 @@ use deckcraft_geom::{Affine, Point, Rect};
|
||||
use deckcraft_model::{Shape, ShapeKind, Table};
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use super::*;
|
||||
use super::{
|
||||
CommandSpec, bad, bool_or, bool_param, cmd, color_param, edit_shapes, f64_param, has_selection, has_slide, id_param, ok, str_param,
|
||||
table_drawn_heights, usize_param, xfrm_of,
|
||||
};
|
||||
use crate::{DocState, Result, Session};
|
||||
|
||||
pub fn specs() -> Vec<CommandSpec> {
|
||||
|
||||
@@ -9,7 +9,10 @@ use deckcraft_model::text::{Align, AutoFit, Hyperlink, Paragraph, Run, RunKind,
|
||||
use deckcraft_model::{Presentation, Shape, ShapeId, ShapeKind};
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use super::*;
|
||||
use super::{
|
||||
CommandSpec, NO_TEXT_POINT, SELECT_TEXT_OR_SHAPE, always, bad, bool_or, cmd, edit_shapes, has_doc, has_slide, has_text, id_param, ok, shape_of,
|
||||
str_param, xfrm_of,
|
||||
};
|
||||
use crate::{DocState, HistoryEntry, Result, Selection, Session, Target, TextSel, push_undo};
|
||||
|
||||
/// Catalogue text-editing, selection, autocorrect, font, IME, and surrounding-deletion commands
|
||||
|
||||
@@ -6,7 +6,7 @@ use deckcraft_model::Transition;
|
||||
use deckcraft_model::anim::TRANSITIONS;
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use super::*;
|
||||
use super::{CommandSpec, NO_ID_SPACE, always, bad, bool_param, cmd, has_slide, ok, str_param, usize_param};
|
||||
use crate::{Result, Session};
|
||||
|
||||
pub fn specs() -> Vec<CommandSpec> {
|
||||
|
||||
@@ -13,7 +13,7 @@ use deckcraft_model::text::{Action, Hyperlink, TextBody};
|
||||
use deckcraft_model::{LayoutType, Shape, ShapeId, ShapeKind, SlideId, defaults};
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use super::*;
|
||||
use super::{CommandSpec, NO_ID_SPACE, bad, bool_or, cmd, has_slide, str_param};
|
||||
use crate::{Result, Session};
|
||||
|
||||
pub fn specs() -> Vec<CommandSpec> {
|
||||
|
||||
@@ -286,7 +286,11 @@ pub fn adopt(doc: &mut Presentation, rev: &Presentation, slide: &Slide, replacin
|
||||
let Some(m) = rev.media(id) else { continue };
|
||||
match doc.media(id) {
|
||||
Some(x) if x.data == m.data => {}
|
||||
None => doc.media.push(m.clone()),
|
||||
None => {
|
||||
// Keeps the revision's id, so later ids must start past it.
|
||||
doc.media.push(m.clone());
|
||||
doc.next_id = doc.next_id.max(id.0);
|
||||
}
|
||||
Some(_) => {
|
||||
let new = doc
|
||||
.add_media(&m.name, &m.content_type, m.data.as_ref().clone())
|
||||
|
||||
@@ -9,7 +9,7 @@ use std::collections::VecDeque;
|
||||
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use crate::*;
|
||||
use crate::{EngineError, Session, command_specs, sample};
|
||||
|
||||
/// Commands that touch the file system, other windows or real time; the soak drives the rest.
|
||||
fn skipped(id: &str) -> bool {
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
use serde_json::json;
|
||||
|
||||
use crate::*;
|
||||
use crate::tools::{Mods, PointerEvent, PointerKind};
|
||||
use crate::{
|
||||
DocState, History, Presentation, Session, Target, TextSel, ToolKind, Value, cmd, command_specs, connect, find_command, model, sample, tools,
|
||||
};
|
||||
use deckcraft_model::ShapeId;
|
||||
|
||||
fn session() -> Session {
|
||||
Session::with_new()
|
||||
@@ -947,6 +951,34 @@ fn dragging_across_cells_selects_a_range_of_cells() {
|
||||
assert_eq!(cmd::table::cell_block(&t, (1, 1), (2, 2)), ((0, 0), (2, 2)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pressing_a_table_outside_its_cells_with_a_range_selected_moves_the_table() {
|
||||
let mut s = session();
|
||||
let id = table_at(&mut s, &[100.0, 100.0], json!([["a", "b"], ["c", "d"]]));
|
||||
let drag = |s: &mut Session, kind, x: f64, y: f64| s.pointer(tev(kind, x, y)).unwrap();
|
||||
s.execute("text.edit", &json!({"id": id, "cell": [0, 0]})).unwrap();
|
||||
drag(&mut s, PointerKind::Down, 150.0, 120.0);
|
||||
drag(&mut s, PointerKind::Drag, 250.0, 160.0);
|
||||
drag(&mut s, PointerKind::Up, 250.0, 160.0);
|
||||
assert_eq!(s.doc().unwrap().selection.cells, Some((id, (0, 0), (1, 1))));
|
||||
// Just left of the table's edge, where a press still hits the table but no cell.
|
||||
drag(&mut s, PointerKind::Down, 98.0, 120.0);
|
||||
assert_eq!(s.doc().unwrap().selection.cells, None, "the range ends");
|
||||
drag(&mut s, PointerKind::Drag, 148.0, 170.0);
|
||||
drag(&mut s, PointerKind::Up, 148.0, 170.0);
|
||||
let st = s.doc().unwrap();
|
||||
assert!(st.selection.text.is_none());
|
||||
assert_eq!(st.selection.shapes, vec![id]);
|
||||
let x = st.shape(id).unwrap().xfrm.unwrap();
|
||||
assert!((x.x - 150.0).abs() < 10.0 && (x.y - 150.0).abs() < 10.0, "the table moves: {x:?}");
|
||||
// A click (no drag) there keeps the table selected and puts no caret in the nearest cell.
|
||||
let (x0, y0) = (x.x - 2.0, x.y + 20.0);
|
||||
drag(&mut s, PointerKind::Down, x0, y0);
|
||||
drag(&mut s, PointerKind::Up, x0, y0);
|
||||
let st = s.doc().unwrap();
|
||||
assert_eq!((st.selection.shapes.clone(), st.selection.text.is_none()), (vec![id], true));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_click_in_a_selected_table_enters_the_clicked_cell() {
|
||||
let mut s = session();
|
||||
@@ -1970,6 +2002,45 @@ mod copy_remap {
|
||||
assert_eq!(img.get_pixel(img.width() / 2, img.height() / 2).0, [255, 0, 0, 255]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pictures_inserted_after_a_paste_from_another_deck_get_media_ids_of_their_own() {
|
||||
let mut a = Session::with_new();
|
||||
let mut b = Session::with_new();
|
||||
let (pa, ..) = populate(&mut a, b"deck a picture");
|
||||
// Deck a's picture uses a media id above its shape ids that deck b has not handed out yet.
|
||||
{
|
||||
let doc = Arc::make_mut(&mut a.doc_mut().unwrap().doc);
|
||||
let high = MediaId(doc.next_id + 20);
|
||||
doc.next_id = high.0;
|
||||
doc.media[0].id = high;
|
||||
let slide = Arc::make_mut(&mut doc.slides[0]);
|
||||
let Some(ShapeKind::Picture { fill }) = slide.shapes.iter_mut().find(|x| x.id == pa).map(|x| &mut x.kind) else { panic!() };
|
||||
fill.media = high;
|
||||
}
|
||||
assert_eq!(picture_bytes(&a, &shape(&a, 0, pa)), b"deck a picture");
|
||||
select(&mut a, &[pa]);
|
||||
a.execute("edit.copy", &json!({})).unwrap();
|
||||
b.clipboard = a.clipboard.clone();
|
||||
b.doc_mut().unwrap().selection.shapes.clear();
|
||||
let r = b.execute("edit.paste", &json!({})).unwrap();
|
||||
let pasted = ShapeId(r["ids"][0].as_u64().unwrap() as u32);
|
||||
let mut inserted = vec![];
|
||||
for i in 0..12u8 {
|
||||
let png = solid_png([i, 0, 0]);
|
||||
let r = b.execute("insert.picture", &json!({"data": cmd::base64_encode(&png), "name": "a.png"})).unwrap();
|
||||
inserted.push((ShapeId(r["id"].as_u64().unwrap() as u32), png));
|
||||
}
|
||||
assert_eq!(picture_bytes(&b, &shape(&b, 0, pasted)), b"deck a picture");
|
||||
for (id, png) in inserted {
|
||||
assert_eq!(picture_bytes(&b, &shape(&b, 0, id)), png);
|
||||
}
|
||||
let d = b.doc().unwrap();
|
||||
let mut ids: Vec<_> = d.doc.media.iter().map(|m| m.id).collect();
|
||||
ids.sort();
|
||||
ids.dedup();
|
||||
assert_eq!(ids.len(), d.doc.media.len(), "media ids are unique");
|
||||
}
|
||||
|
||||
/// Slide 0 of `s` gets a table whose first cell, background and second cell's picture bullet
|
||||
/// each use a picture of their own, and a transition sound. `tag` starts the bytes of each.
|
||||
/// Returns the table.
|
||||
|
||||
@@ -705,10 +705,13 @@ impl Session {
|
||||
}
|
||||
}
|
||||
Some(Hit::Shape { id, .. })
|
||||
if !(mods.shift || mods.cmd) && st.selection.cells.is_some_and(|c| c.0 == id && st.selection.shapes == [id]) =>
|
||||
if !(mods.shift || mods.cmd)
|
||||
&& st.selection.cells.is_some_and(|c| c.0 == id && st.selection.shapes == [id])
|
||||
&& in_table_cell(st, id, p) =>
|
||||
{
|
||||
// A press in a table whose cells are selected starts again in the cell under the
|
||||
// pointer: a click puts the caret there and a drag selects another range.
|
||||
// pointer: a click puts the caret there and a drag selects another range. A press
|
||||
// on the table outside its cells selects and moves the table like any shape.
|
||||
let before = self.text_selection();
|
||||
if self.edit_text_at(id, p)? {
|
||||
self.select(|_, sel| sel.cells = None)?;
|
||||
@@ -736,15 +739,17 @@ impl Session {
|
||||
let origs: Vec<(ShapeId, Xfrm)> =
|
||||
ids.iter().filter_map(|i| st.shape(*i).filter(|s| !s.locked).map(|s| (*i, xfrm_of(&st.doc, &st.selection, s)))).collect();
|
||||
// A click (no drag) on text puts the caret there; unselected text boxes and
|
||||
// placeholders take the caret straight away like PowerPoint. A click in a selected
|
||||
// table puts the caret in the clicked cell.
|
||||
// placeholders take the caret straight away like PowerPoint. A click in a cell of a
|
||||
// selected table puts the caret there; a click on its grab band keeps the table selected.
|
||||
let table = st.shape(id).is_some_and(|s| matches!(s.kind, ShapeKind::Table(_)));
|
||||
// A click on the frame band of a text box selects the box whole, as PowerPoint
|
||||
// does, so it can be moved, duplicated or deleted; only the inside takes the caret.
|
||||
let on_frame = text
|
||||
&& is_text_first(st, id)
|
||||
&& st.shape(id).is_some_and(|s| edit_zone(&xfrm_of(&st.doc, &st.selection, s), p, tol) == EditZone::Frame);
|
||||
let enter_text = (!toggle && !on_frame && ((text && (selected || is_text_first(st, id))) || (table && selected))).then_some((id, p));
|
||||
let enter_text =
|
||||
(!toggle && !on_frame && ((text && (selected || is_text_first(st, id))) || (table && selected && in_table_cell(st, id, p))))
|
||||
.then_some((id, p));
|
||||
let ids2 = ids.clone();
|
||||
self.select(|_, sel| {
|
||||
sel.shapes = ids2;
|
||||
@@ -1990,6 +1995,15 @@ pub fn resize_box(o: &Xfrm, h: u8, d: Vec2, keep_aspect: bool, from_center: bool
|
||||
Xfrm { x: c.x - nw / 2.0, y: c.y - nh / 2.0, w: nw, h: nh, rot: o.rot, flip_h: fh, flip_v: fv }
|
||||
}
|
||||
|
||||
/// Whether `p` lies in a cell of table `id`, not in the grab band around it (`cell_at` would take
|
||||
/// the nearest cell).
|
||||
fn in_table_cell(st: &crate::DocState, id: ShapeId, p: Point) -> bool {
|
||||
st.shape(id).and_then(|sh| cmd::table::table_frame(st, sh)).is_some_and(|f| {
|
||||
let q = f.local(p);
|
||||
(0.0..=f.width()).contains(&q.x) && (0.0..=f.height()).contains(&q.y) && f.cell_at(q).is_some()
|
||||
})
|
||||
}
|
||||
|
||||
fn hit_shape(st: &crate::DocState, sh: &Shape, p: Point, tol: f64) -> Option<Hit> {
|
||||
let x = xfrm_of(&st.doc, &st.selection, sh);
|
||||
let inv = x.affine().inverse();
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use super::*;
|
||||
use std::io::{Cursor, Write};
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::{Budget, Presentation, load, load_checked, load_within, read_entry, read_entry_max, save};
|
||||
use deckcraft_model::chart::{Chart, ChartPart, ChartRel, ChartType};
|
||||
use deckcraft_model::{Master, Shape, ShapeId};
|
||||
use deckcraft_model::{Master, Shape, ShapeId, ShapeKind};
|
||||
use serde_json::{Value, json};
|
||||
|
||||
const PART: &str = "ppt/embeddings/owned.xlsx";
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
//! ribbons, gears, tabs and chart markers. Like the rest of `preset`, every outline is our own
|
||||
//! construction, fitted to how the shapes look and to what their adjust values mean.
|
||||
|
||||
use super::*;
|
||||
use super::{
|
||||
Corner, Dir, FillMode, Geometry, Handle, Pb, Point, Rect, Vec2, corner_rect, fill, fill_eo, fill_only, mid_sites, orient, shade, simple,
|
||||
stroke_only, vis,
|
||||
};
|
||||
|
||||
/// Builds the presets this module knows; `None` for any other name.
|
||||
pub(super) fn build(name: &str, w: f64, h: f64, a: &dyn Fn(usize) -> f64) -> Option<Geometry> {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use super::*;
|
||||
use super::{
|
||||
Band, BezPath, CATALOG, Guide, Point, Rect, WarpLine, adjust_count, adjust_defaults, band_of_line, handles, is_follow, is_supported, warp,
|
||||
};
|
||||
|
||||
const W: f64 = 420.0;
|
||||
const H: f64 = 200.0;
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
use super::*;
|
||||
use super::{
|
||||
ColorRef, Fill, LayoutType, Paragraph, PhType, Placeholder, Presentation, Run, RunProps, SchemeSlot, Shape, ShapeId, ShapeKind, ShapeStyle,
|
||||
Slide, Table, TextBody, Theme, Xfrm, defaults, ink_pressure_is_legacy, ink_sample_width, parent_of, resolve, sibling_list_mut, style, text,
|
||||
theme, walk,
|
||||
};
|
||||
use crate::resolve::Ctx;
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! Synthetic Ogg streams: a small page writer (RFC 3533) and Opus-like packets.
|
||||
|
||||
use super::*;
|
||||
use super::{Codec, Error, OpusTiming, Packet, Stream, crc32, open, opus_packet_samples, sniff};
|
||||
|
||||
/// Write one page holding `segments` (lacing already computed by `lace`).
|
||||
fn page(out: &mut Vec<u8>, flags: u8, granule: i64, serial: u32, seq: u32, lacing: &[u8], body: &[u8]) {
|
||||
|
||||
+10
-1
@@ -1,6 +1,15 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::*;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use deckcraft_fonts::FontDb;
|
||||
use deckcraft_render::RenderOpts;
|
||||
use kurbo::Rect;
|
||||
|
||||
use super::{
|
||||
MAX_RASTER_PIXELS, PAPERS, PageLayout, PdfError, PdfOptions, Presentation, export, glyph_text_ranges, handout_boxes, master_text_pieces,
|
||||
master_texts, notes_frames, notes_slide_box, paper_size, raster_scale,
|
||||
};
|
||||
use deckcraft_model::text::TextBody;
|
||||
|
||||
fn deck(n: usize) -> Presentation {
|
||||
|
||||
@@ -36,9 +36,10 @@ pub fn color(w: &mut W, c: &ColorRef) {
|
||||
let (tag, a) = match &c.base {
|
||||
ColorBase::Rgb { rgb } => ("a:srgbClr", A::new().a("val", rgb.hex())),
|
||||
ColorBase::Scheme { slot } => ("a:schemeClr", A::new().a("val", slot.xml_name())),
|
||||
ColorBase::Preset { name } => ("a:prstClr", A::new().a("val", if name.is_empty() { "black" } else { name })),
|
||||
// The reader keeps any name a file has; only names the schema allows are written.
|
||||
ColorBase::Preset { name } => ("a:prstClr", A::new().a("val", if deckcraft_color::is_preset_name(name) { name } else { "black" })),
|
||||
ColorBase::System { name, last } => {
|
||||
("a:sysClr", A::new().a("val", if name.is_empty() { "windowText" } else { name }).a("lastClr", last.hex()))
|
||||
("a:sysClr", A::new().a("val", if deckcraft_color::is_system_name(name) { name } else { "windowText" }).a("lastClr", last.hex()))
|
||||
}
|
||||
};
|
||||
let rgb_alpha = match &c.base {
|
||||
@@ -788,3 +789,23 @@ pub fn paragraph(w: &mut W, x: &mut Exp, o: &mut Out, p: &Paragraph, math: bool,
|
||||
pub fn has_math(tb: &TextBody) -> bool {
|
||||
tb.paragraphs.iter().flat_map(|p| &p.runs).any(|r| matches!(&r.kind, RunKind::Math { omml } if !omml.is_empty()))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use deckcraft_color::Rgba;
|
||||
|
||||
#[test]
|
||||
fn unknown_preset_and_system_names_save_as_black_and_window_text() {
|
||||
let write = |base: ColorBase| {
|
||||
let mut w = W::frag();
|
||||
color(&mut w, &ColorRef { base, mods: vec![] });
|
||||
w.s
|
||||
};
|
||||
assert_eq!(write(ColorBase::Preset { name: "notAColor".into() }), r#"<a:prstClr val="black"/>"#);
|
||||
assert_eq!(write(ColorBase::Preset { name: "darkBlue".into() }), r#"<a:prstClr val="darkBlue"/>"#);
|
||||
let last = Rgba::rgb(0x12, 0x34, 0x56);
|
||||
assert_eq!(write(ColorBase::System { name: "notAColor".into(), last }), r#"<a:sysClr val="windowText" lastClr="123456"/>"#);
|
||||
assert_eq!(write(ColorBase::System { name: "3dLight".into(), last }), r#"<a:sysClr val="3dLight" lastClr="123456"/>"#);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
use super::*;
|
||||
use super::{
|
||||
Ctx, Fields, Fill, Image, Opts, PathMorph, Point, RENDERER, Rect, Region, RenderOpts, Renderer, Rgba, SlideFields, TextMorph, crop_dest,
|
||||
dpi_for_scale, gif_frame, image_dpi, images, morph_path, morph_text, needs_filters, parse_path, pasteboard_region, place_slide, render_blend,
|
||||
render_shapes, render_slide, render_slide_drawn, slide_extent, text_transform,
|
||||
};
|
||||
use deckcraft_model::chart::{ChartType, Trendline, TrendlineType};
|
||||
use deckcraft_model::style::ColorRef;
|
||||
use deckcraft_model::text::TextBody;
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
//! Render tests for shape, line, fill and effect cases checked against PowerPoint's own exports
|
||||
//! of synthetic "wild" slides (observed black-box; see `plan/powerpoint/tools/gen_wild.py`).
|
||||
|
||||
use super::*;
|
||||
use super::{
|
||||
BezPath, Fill, Image, Line, MAX_DASHES, PathEl, Point, Rect, RenderOpts, ShapeKind, dash_with_caps, head_size, images, limit_dash_count, paint,
|
||||
render_slide, trim_ends,
|
||||
};
|
||||
use deckcraft_color::Rgba;
|
||||
use deckcraft_model::style::ColorRef;
|
||||
use deckcraft_model::{CustomPath, Geom, Presentation, Shape, ShapeId, Xfrm};
|
||||
use kurbo::Shape as _;
|
||||
|
||||
fn deck_with(shapes: Vec<Shape>) -> Presentation {
|
||||
let mut p = Presentation::default();
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
use super::*;
|
||||
use super::{
|
||||
Affinity, Align, Anchor, Arc, AutoFit, BodyProps, Bullet, Caps, Cell, Ctx, Dash, Deco, DecoLine, FontDb, FontFace, GlyphRun, LINE_PITCH,
|
||||
MoveMode, NoFields, Opts, ParaProps, Point, Pos, Rect, Rgba, SUB_SUPER, Shape, Spacing, Strike, TextBody, TextLayout, autonum,
|
||||
break_opportunities, caps_char, chinese_num, covers_symbol, is_symbol_font, is_upright, layout, layout_scaled, map_symbol_bullet, measure_str,
|
||||
resolve, shaping_features, symbol_char, symbol_font_char, symbol_font_draw, symbol_unicode, symbol_unicode_alts,
|
||||
};
|
||||
use deckcraft_model::Fill;
|
||||
use deckcraft_model::defaults;
|
||||
use deckcraft_model::text::{Paragraph, Run};
|
||||
|
||||
@@ -12,7 +12,7 @@ use egui_kittest::Harness;
|
||||
use egui_kittest::kittest::Queryable;
|
||||
use serde_json::json;
|
||||
|
||||
use super::*;
|
||||
use crate::backstage::{Backstage, NAV_W, Nav, Page, PrintScope, entries, format_bytes, print_page_size, print_params, print_slides, split_path};
|
||||
use crate::{Services, SlideApp};
|
||||
|
||||
fn harness(app: SlideApp) -> Harness<'static, SlideApp> {
|
||||
|
||||
+18
-3
@@ -61,6 +61,12 @@ revision history.
|
||||
- **Impact:** long-session crash rate is sampled in CI; RSS is not measured directly.
|
||||
- **Estimate:** 1–2 h (RSS sampling per platform).
|
||||
|
||||
### A control-server timing test may fail under load
|
||||
- **Evidence:** `silent_refused_peers_do_not_delay_the_next_refusal` in
|
||||
`apps/deckcraft/src/control_server.rs` asserts that a refusal arrives in under 1 s, so it can
|
||||
fail on a busy machine although the server behaves correctly.
|
||||
- **Estimate:** under 1 h (a looser bound or a logical clock).
|
||||
|
||||
## File formats
|
||||
|
||||
Detail in [file-format-parity.md](file-format-parity.md).
|
||||
@@ -260,9 +266,17 @@ and starts at that effect. The pane has an advanced timeline with draggable bars
|
||||
`review.thesaurus` looks up the selected word (checklist row partial). AutoCorrect has
|
||||
PowerPoint's options (two initial capitals, sentence and day capitals, caps lock, fractions, an
|
||||
editable replace list), and AutoFormat superscripts ordinals (1st) and starts bulleted and
|
||||
numbered lists (`* `, `1. `, Backspace puts the typed text back) as you type; no undo of a
|
||||
single correction.
|
||||
- **Estimate:** 1–2 h (bundle openly licensed dictionaries).
|
||||
numbered lists (`* `, `1. `) as you type; no undo of a single correction. Automatic lists start
|
||||
only while Replace text as you type is on, where PowerPoint has a separate AutoFormat option,
|
||||
and Backspace right after one puts the typed lead back, which PowerPoint does not.
|
||||
- **Estimate:** 1–2 h (bundle openly licensed dictionaries; under 1 h for the AutoFormat option).
|
||||
|
||||
### Copying text and cell ranges
|
||||
- **Evidence:** a copied text range carries no media, so a picture bullet in it is lost when
|
||||
pasted into another deck; the writer saves a bullet whose picture is missing as `•`. A copied
|
||||
range of table cells is tab-separated text, not a table, so pasting it makes text rather than
|
||||
cells.
|
||||
- **Estimate:** 2–4 h.
|
||||
|
||||
### Equations
|
||||
- **Evidence:** checklist row missing; PR #55 (OMML editor, +10.5k lines) is open.
|
||||
@@ -357,6 +371,7 @@ Detail in [localization-parity.md](localization-parity.md).
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-25 | trivial | New entries: copying text and cell ranges (no media, no table), a control-server timing test that may fail under load; Proofing notes that automatic lists follow Replace text as you type and that Backspace puts the typed lead back |
|
||||
| 2026-10-24 | trivial | AutoFormat starts bulleted and numbered lists as you type (`* `, `-- `, `> `, `1. `, `a) ` and the rest), with an AutoCorrect option to turn it off |
|
||||
| 2026-10-22 | minor | Animation Pane ▸ Play From starts the preview at the selected effect; gap 10 keeps the effect gallery (7–13 h) |
|
||||
| 2026-10-24 | trivial | Columns dialog (number and spacing of text columns); dialog count 41 → 42 |
|
||||
|
||||
@@ -24,7 +24,9 @@ KEYCHAIN="$TMP_DIR/deckcraft-signing.keychain-db"
|
||||
CERT="$TMP_DIR/deckcraft-signing.p12"
|
||||
KC_PASS="${KEYCHAIN_PASSWORD:-$(openssl rand -hex 24)}"
|
||||
|
||||
trap 'rm -rf "$TMP_DIR"' EXIT
|
||||
# Remove only the decoded certificate: the keychain lives in the same directory and codesign
|
||||
# needs it until the workflow's "Remove temporary keychain" step deletes it.
|
||||
trap 'rm -f "${CERT:-}"' EXIT
|
||||
printf '%s' "$APPLE_CERTIFICATE" | base64 --decode >"$CERT"
|
||||
security create-keychain -p "$KC_PASS" "$KEYCHAIN"
|
||||
security set-keychain-settings -lut 21600 "$KEYCHAIN"
|
||||
|
||||
Reference in New Issue
Block a user