mirror of
https://github.com/storytold/deckcraft.git
synced 2026-10-25 19:17:06 -06:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bcf583c5ea | ||
|
|
230b6485d9 |
@@ -137,6 +137,7 @@ app's JSON control channel (`deckcraft --control PORT`).
|
||||
|
||||
| Date | What landed |
|
||||
|---|---|
|
||||
| 2026-10-25 | Windows installer: the Start menu shortcut takes the icon embedded in `deckcraft.exe` instead of an MSI `<Icon>`, so a taskbar pin keeps its icon after an update (#1114); a pin that already lost it has to be unpinned and pinned again once |
|
||||
| 2026-10-24 | AutoFormat as you type: a paragraph that starts with `*`, `-`, `--`, `>`, `<>`, `1.`, `1)`, `I.`, `i.`, `A.`, `a.` or `a)` and a space or tab becomes a bulleted or numbered list in that style; Backspace right after puts the typed text back, and Tools ▸ AutoCorrect and `text.autocorrect {lists}` turn it off |
|
||||
| 2026-10-24 | Tables: while the caret is in a cell, dragging into another cell selects that range of cells (shaded, merged cells taken in whole), and Shift+arrow grows or shrinks it; formatting applies to the selected cells only, and Delete clears their text and keeps the table |
|
||||
| 2026-10-24 | Preferences ▸ Measurement units: inches or centimetres. The Format pane's Size and Position fields, Shape Format ▸ Size on the ribbon, the Slide Size dialog and the ruler show and take the chosen unit; the deck still stores points (#1326) |
|
||||
@@ -293,6 +294,7 @@ app's JSON control channel (`deckcraft --control PORT`).
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-25 | trivial | Progress log: Windows shortcut icon survives updates (#1114). Scores and milestone names unchanged |
|
||||
| 2026-10-24 | trivial | Progress log: selecting a range of table cells by dragging. Scores and milestone names unchanged |
|
||||
| 2026-10-24 | trivial | Progress log: measurement units in centimetres (#1326). Scores unchanged |
|
||||
| 2026-10-22 | trivial | Progress log: Animation Pane ▸ Play From. Scores unchanged |
|
||||
|
||||
@@ -430,4 +430,27 @@ mod tests {
|
||||
assert_eq!(mac, win, "macOS and Windows offer different file types");
|
||||
assert_eq!(mac, linux, "macOS and Linux offer different file types");
|
||||
}
|
||||
|
||||
/// #1114: a shortcut that names an MSI `<Icon>` shows a blank icon once an update deletes the
|
||||
/// cached copy of it, and a taskbar pin (a copy of the shortcut) is never repaired. Shortcuts
|
||||
/// take the icon embedded in `deckcraft.exe`; only `ARPPRODUCTICON` uses the MSI `<Icon>`.
|
||||
#[test]
|
||||
fn windows_shortcuts_use_the_icon_embedded_in_the_exe() {
|
||||
let wxs = include_str!("../../../packaging/windows/deckcraft.wxs");
|
||||
let shortcuts: Vec<&str> = wxs.split("<Shortcut").skip(1).filter_map(|s| s.split("/>").next()).collect();
|
||||
assert!(!shortcuts.is_empty(), "deckcraft.wxs has no shortcut");
|
||||
for shortcut in shortcuts {
|
||||
assert!(!shortcut.contains("Icon="), "a shortcut names an MSI <Icon>:{shortcut}");
|
||||
}
|
||||
assert!(wxs.contains("<Property Id=\"ARPPRODUCTICON\""), "Apps and features keeps its icon");
|
||||
}
|
||||
|
||||
/// The shortcuts take the exe's own icon, and `package.ps1 -SkipBuild` packages an existing
|
||||
/// exe without `DECKCRAFT_REQUIRE_WINRES`, so the script has to refuse one with no icon.
|
||||
#[test]
|
||||
fn the_windows_package_script_rejects_an_exe_without_an_icon() {
|
||||
let script = include_str!("../../../packaging/windows/package.ps1");
|
||||
assert!(script.contains("ExtractIconEx"), "package.ps1 does not count the exe's icons");
|
||||
assert!(script.contains("has no embedded icon"), "package.ps1 does not reject an iconless exe");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,3 +65,6 @@ sync_github_name = true
|
||||
|
||||
[people.Katacc]
|
||||
sync_github_name = true
|
||||
|
||||
[people.SofiaAFigueredo]
|
||||
real_name = "Sofia Alves Figueredo"
|
||||
|
||||
+86
-64
@@ -1366,84 +1366,106 @@ impl<'a> SliceDecoder<'a> {
|
||||
|
||||
/// prediction_unit(): parse, derive motion, store it and predict. Returns merge_flag.
|
||||
fn prediction_unit(&mut self, g: PbGeom, part: PartMode, skip: bool) -> Result<bool> {
|
||||
let PbGeom { xc, yc, ncb, xp, yp, w, h, part_idx } = g;
|
||||
let merge = if skip { true } else { self.dd(MERGE_FLAG) == 1 };
|
||||
let mvf = if merge {
|
||||
let idx = if self.sh.max_num_merge_cand > 1 {
|
||||
let mut i = 0;
|
||||
if self.dd(MERGE_IDX) == 1 {
|
||||
i = 1;
|
||||
while i < self.sh.max_num_merge_cand - 1 && self.bypass() == 1 {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
i
|
||||
} else {
|
||||
0
|
||||
};
|
||||
self.derive_merge(PbGeom { xc, yc, ncb, xp, yp, w, h, part_idx }, part, idx as usize)
|
||||
let idx = self.merge_idx();
|
||||
self.derive_merge(g, part, idx as usize)
|
||||
} else {
|
||||
let pred_idc = if self.sh.is_b() {
|
||||
if w + h != 12 {
|
||||
let depth = self.pic.blk_at(xc, yc).depth as usize;
|
||||
if self.dd(INTER_PRED_IDC + depth) == 1 { 2 } else { self.dd(INTER_PRED_IDC + 4) }
|
||||
} else {
|
||||
self.dd(INTER_PRED_IDC + 4)
|
||||
}
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let mut ref_idx = [-1i8; 2];
|
||||
let mut mvd = [[0i32; 2]; 2];
|
||||
let mut mvp_flag = [0u32; 2];
|
||||
for l in 0..2 {
|
||||
let used = if l == 0 { pred_idc != 1 } else { pred_idc != 0 };
|
||||
if !used {
|
||||
continue;
|
||||
}
|
||||
let nref = self.sh.num_ref_idx[l];
|
||||
let mut ri = 0;
|
||||
if nref > 1 {
|
||||
while ri < nref - 1 {
|
||||
let bin = if ri < 2 { self.dd(REF_IDX + ri as usize) } else { self.bypass() };
|
||||
if bin == 0 {
|
||||
break;
|
||||
}
|
||||
ri += 1;
|
||||
}
|
||||
}
|
||||
ref_idx[l] = ri as i8;
|
||||
if l == 1 && self.sh.mvd_l1_zero && pred_idc == 2 {
|
||||
mvd[1] = [0, 0];
|
||||
} else {
|
||||
mvd[l] = self.mvd_coding()?;
|
||||
}
|
||||
mvp_flag[l] = self.dd(MVP_FLAG);
|
||||
}
|
||||
let mut f = MvField { mv: [[0; 2]; 2], ref_idx };
|
||||
for l in 0..2 {
|
||||
if ref_idx[l] < 0 {
|
||||
continue;
|
||||
}
|
||||
let mvp = self.derive_amvp(PbGeom { xc, yc, ncb, xp, yp, w, h, part_idx }, l, ref_idx[l] as usize, mvp_flag[l] as usize);
|
||||
// mvLX = (mvp + mvd) wrapped to 16 bits (8-94)
|
||||
f.mv[l] = [(mvp[0] as i32 + mvd[l][0]) as i16, (mvp[1] as i32 + mvd[l][1]) as i16];
|
||||
}
|
||||
f
|
||||
self.amvp_motion(g)?
|
||||
};
|
||||
for l in 0..2 {
|
||||
if mvf.ref_idx[l] >= 0 {
|
||||
ensure!((mvf.ref_idx[l] as usize) < self.refs[l].len(), "reference index out of range");
|
||||
}
|
||||
}
|
||||
// store motion
|
||||
self.store_motion(g.xp, g.yp, g.w, g.h, mvf);
|
||||
self.motion_compensate(g.xp, g.yp, g.w as usize, g.h as usize, &mvf);
|
||||
Ok(merge)
|
||||
}
|
||||
|
||||
/// merge_idx(): the merge candidate index, truncated unary (0 when there is one candidate).
|
||||
fn merge_idx(&mut self) -> u32 {
|
||||
if self.sh.max_num_merge_cand <= 1 {
|
||||
return 0;
|
||||
}
|
||||
let mut i = 0;
|
||||
if self.dd(MERGE_IDX) == 1 {
|
||||
i = 1;
|
||||
while i < self.sh.max_num_merge_cand - 1 && self.bypass() == 1 {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
i
|
||||
}
|
||||
|
||||
/// inter_pred_idc: 0 = list 0, 1 = list 1, 2 = both.
|
||||
fn inter_pred_idc(&mut self, xc: i32, yc: i32, w: i32, h: i32) -> u32 {
|
||||
if !self.sh.is_b() {
|
||||
return 0;
|
||||
}
|
||||
if w + h != 12 {
|
||||
let depth = self.pic.blk_at(xc, yc).depth as usize;
|
||||
if self.dd(INTER_PRED_IDC + depth) == 1 { 2 } else { self.dd(INTER_PRED_IDC + 4) }
|
||||
} else {
|
||||
self.dd(INTER_PRED_IDC + 4)
|
||||
}
|
||||
}
|
||||
|
||||
/// ref_idx_lX: truncated unary, the first two bins context-coded.
|
||||
fn ref_idx(&mut self, l: usize) -> u32 {
|
||||
let nref = self.sh.num_ref_idx[l];
|
||||
let mut ri = 0;
|
||||
if nref > 1 {
|
||||
while ri < nref - 1 {
|
||||
let bin = if ri < 2 { self.dd(REF_IDX + ri as usize) } else { self.bypass() };
|
||||
if bin == 0 {
|
||||
break;
|
||||
}
|
||||
ri += 1;
|
||||
}
|
||||
}
|
||||
ri
|
||||
}
|
||||
|
||||
/// The AMVP branch: parse each used list's reference index, mvd and predictor flag, then add
|
||||
/// every mvd to its predictor.
|
||||
fn amvp_motion(&mut self, g: PbGeom) -> Result<MvField> {
|
||||
let pred_idc = self.inter_pred_idc(g.xc, g.yc, g.w, g.h);
|
||||
let mut ref_idx = [-1i8; 2];
|
||||
let mut mvd = [[0i32; 2]; 2];
|
||||
let mut mvp_flag = [0u32; 2];
|
||||
for l in 0..2 {
|
||||
let used = if l == 0 { pred_idc != 1 } else { pred_idc != 0 };
|
||||
if !used {
|
||||
continue;
|
||||
}
|
||||
ref_idx[l] = self.ref_idx(l) as i8;
|
||||
if l == 1 && self.sh.mvd_l1_zero && pred_idc == 2 {
|
||||
mvd[1] = [0, 0];
|
||||
} else {
|
||||
mvd[l] = self.mvd_coding()?;
|
||||
}
|
||||
mvp_flag[l] = self.dd(MVP_FLAG);
|
||||
}
|
||||
let mut f = MvField { mv: [[0; 2]; 2], ref_idx };
|
||||
for l in 0..2 {
|
||||
if ref_idx[l] < 0 {
|
||||
continue;
|
||||
}
|
||||
let mvp = self.derive_amvp(g, l, ref_idx[l] as usize, mvp_flag[l] as usize);
|
||||
// mvLX = (mvp + mvd) wrapped to 16 bits (8-94)
|
||||
f.mv[l] = [(mvp[0] as i32 + mvd[l][0]) as i16, (mvp[1] as i32 + mvd[l][1]) as i16];
|
||||
}
|
||||
Ok(f)
|
||||
}
|
||||
|
||||
/// Writes a prediction block's motion into the picture's 4x4 motion field.
|
||||
fn store_motion(&mut self, xp: i32, yp: i32, w: i32, h: i32, mvf: MvField) {
|
||||
let w4 = self.pic.w4;
|
||||
for by in (yp >> 2)..((yp + h) >> 2) {
|
||||
let o = by as usize * w4;
|
||||
self.pic.mvf[o + (xp >> 2) as usize..o + ((xp + w) >> 2) as usize].fill(mvf);
|
||||
}
|
||||
self.motion_compensate(xp, yp, w as usize, h as usize, &mvf);
|
||||
Ok(merge)
|
||||
}
|
||||
|
||||
fn mvd_coding(&mut self) -> Result<[i32; 2]> {
|
||||
|
||||
@@ -58,9 +58,15 @@
|
||||
<Component Id="DeckcraftApp" Guid="D87EA444-41A4-4C19-922E-66D1A29461FF">
|
||||
<File Id="DeckcraftNotice" Source="$(var.BinDir)\NOTICE" KeyPath="no" />
|
||||
<File Id="DeckcraftExe" Source="$(var.BinDir)\deckcraft.exe" KeyPath="yes">
|
||||
<!-- No Icon here, so the shortcut shows the icon embedded in deckcraft.exe (build.rs). An
|
||||
MSI Icon is cached under %APPDATA%\Microsoft\Installer\{ProductCode}\, and every
|
||||
update is a major upgrade with a new ProductCode that deletes the old folder. A
|
||||
taskbar pin is a copy of this shortcut that the update never touches, so it kept
|
||||
pointing at the deleted file and lost its icon (#1114). The exe's path survives
|
||||
updates; the shortcut is not advertised, so it targets that path and its icon is the
|
||||
exe's own. The Icon above stays for ARPPRODUCTICON (Apps and features). -->
|
||||
<Shortcut Id="StartMenuShortcut" Directory="ProgramMenuFolder" Name="DeckCraft"
|
||||
Description="DeckCraft presentations and slide shows" WorkingDirectory="INSTALLFOLDER" Advertise="yes"
|
||||
Icon="DeckcraftIcon.ico" IconIndex="0" />
|
||||
Description="DeckCraft presentations and slide shows" WorkingDirectory="INSTALLFOLDER" Advertise="no" />
|
||||
</File>
|
||||
|
||||
<!-- .deckcraft: DeckCraft's own format. -->
|
||||
|
||||
@@ -83,6 +83,15 @@ foreach ($check in @(@('deckcraft.exe', 2), @('deckcraft-cli.exe', 3))) {
|
||||
if ($h.Subsystem -ne $check[1]) { throw "$($check[0]) has PE subsystem $($h.Subsystem), expected $($check[1])" }
|
||||
Write-Output "ok $($check[0]): $Arch, PE subsystem $($h.Subsystem)"
|
||||
}
|
||||
|
||||
# The Start menu shortcut is not advertised and names no MSI icon, so it shows the icon embedded in
|
||||
# deckcraft.exe (#1114). -SkipBuild takes whatever binary is already there, which may come from a
|
||||
# build without DECKCRAFT_REQUIRE_WINRES=1 whose resource compilation failed: refuse an exe with no
|
||||
# icon instead of shipping shortcuts with a generic one. ExtractIconEx with index -1 counts them.
|
||||
Add-Type -Namespace DeckCraft -Name Shell -MemberDefinition '[DllImport("shell32.dll", CharSet = CharSet.Unicode)] public static extern uint ExtractIconEx(string file, int index, IntPtr[] large, IntPtr[] small, uint icons);'
|
||||
$Icons = [DeckCraft.Shell]::ExtractIconEx((Join-Path $Bin 'deckcraft.exe'), -1, $null, $null, 0)
|
||||
if ($Icons -eq 0) { throw 'deckcraft.exe has no embedded icon: rebuild it with DECKCRAFT_REQUIRE_WINRES=1 (omit -SkipBuild)' }
|
||||
Write-Output "ok deckcraft.exe: $Icons embedded icon group(s)"
|
||||
$Stage = Join-Path $TargetDir "windows-package\$Arch"
|
||||
Remove-Item -Recurse -Force $Stage -ErrorAction SilentlyContinue
|
||||
New-Item -ItemType Directory -Force -Path $Stage | Out-Null
|
||||
|
||||
Reference in New Issue
Block a user