mirror of
https://github.com/storytold/deckcraft.git
synced 2026-10-31 05:56:55 -06:00
Picture Format: the Picture Styles row leaves room for Crop and Size
The gallery put all 24 picture styles in the ribbon row, about 1370 px, so Alt Text, Arrange, Crop and Size sat past the ribbon's edge in any window narrower than about 2500 px, the default 1440 px window and a maximized 1920 px one included. The row now shows as many styles as leave room for the groups after it (at least two), sized from the width the tab left free on the last frame, and an arrow opens all 24 in a menu, as the Transitions and Animations galleries do.
This commit is contained in:
+3
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
**Stage: alpha** · next: beta, ~20 points and ~120–210 h away
|
||||
|
||||
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-24 · **Change:** trivial (progress log: Edit Points handle drags with Shift, Ctrl/Cmd or Alt/Option set the point type; previous: trivial, progress log: scroll bars on the zoomed slide pane, #1157; previous: minor, native chart payloads stored in bounded ZIP members; previous: major, community PR wave 2026-10-14: ~60 landed items in the progress log, among them the Font, Paragraph, Photo Album, theme colour and font and Date and Time dialogs, presenter and show controls, the SmartArt Text Pane and PPTX corpus slices; breadth 84% after `cargo xtask parity`, ready stays ~55%, stage stays alpha; duplicated rows and headers folded; previous: minor, hours to ~95% per audience added) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
|
||||
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-29 · **Change:** trivial (progress log: the Picture Styles row leaves room for Crop and Size, #1232; previous: trivial, progress log: Edit Points handle drags with Shift, Ctrl/Cmd or Alt/Option set the point type; previous: trivial, progress log: scroll bars on the zoomed slide pane, #1157; previous: minor, native chart payloads stored in bounded ZIP members; previous: major, community PR wave 2026-10-14: ~60 landed items in the progress log, among them the Font, Paragraph, Photo Album, theme colour and font and Date and Time dialogs, presenter and show controls, the SmartArt Text Pane and PPTX corpus slices; breadth 84% after `cargo xtask parity`, ready stays ~55%, stage stays alpha; duplicated rows and headers folded; previous: minor, hours to ~95% per audience added) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
|
||||
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-24 · **Change:** trivial (progress log: Ctrl+wheel zoom during the slide show, #1286; previous: trivial, scroll bars on the zoomed slide pane, #1157; previous: minor, native chart payloads stored in bounded ZIP members; previous: major, community PR wave 2026-10-14: ~60 landed items in the progress log, among them the Font, Paragraph, Photo Album, theme colour and font and Date and Time dialogs, presenter and show controls, the SmartArt Text Pane and PPTX corpus slices; breadth 84% after `cargo xtask parity`, ready stays ~55%, stage stays alpha; duplicated rows and headers folded; previous: minor, hours to ~95% per audience added) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
|
||||
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-24 · **Change:** trivial (progress log: picture and embedded object fills; previous: trivial, scroll bars on the zoomed slide pane, #1157; previous: minor, native chart payloads stored in bounded ZIP members; previous: major, community PR wave 2026-10-14: ~60 landed items in the progress log, among them the Font, Paragraph, Photo Album, theme colour and font and Date and Time dialogs, presenter and show controls, the SmartArt Text Pane and PPTX corpus slices; breadth 84% after `cargo xtask parity`, ready stays ~55%, stage stays alpha; duplicated rows and headers folded; previous: minor, hours to ~95% per audience added) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
|
||||
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-22 · **Change:** trivial (fill and outline edits reach group members, #1079; stage and scores unchanged) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
|
||||
@@ -138,6 +138,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-29 | Picture Format: the Picture Styles row shows as many styles as leave room for the groups after it, and its arrow opens all 24. With every style in the row, Crop and Size sat past the ribbon's edge in any window narrower than about 2500 px, so Crop could not be seen without scrolling the ribbon (#1232). In the default 1440 px window Crop, Height and Width are now in view, and from 1600 px the whole tab fits |
|
||||
| 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) |
|
||||
@@ -294,6 +295,7 @@ app's JSON control channel (`deckcraft --control PORT`).
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-29 | trivial | Progress log: the Picture Styles row leaves room for Crop and Size (#1232). Scores and milestone names unchanged |
|
||||
| 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 |
|
||||
|
||||
@@ -237,6 +237,7 @@ pub fn show(app: &mut SlideApp, ui: &mut Ui) {
|
||||
});
|
||||
});
|
||||
let r = scroll.inner_rect;
|
||||
ui.ctx().data_mut(|d| d.insert_temp(spare_room_id(&tab), r.width() - scroll.content_size.x));
|
||||
let overflow = (scroll.content_size.x - r.width()).max(0.0);
|
||||
let offset = scroll.state.clamped_offset().x;
|
||||
// Record the rendered offset for `InputTarget`: the same window point hits a different
|
||||
@@ -263,6 +264,17 @@ pub fn show(app: &mut SlideApp, ui: &mut Ui) {
|
||||
});
|
||||
}
|
||||
|
||||
fn spare_room_id(tab: &str) -> egui::Id {
|
||||
egui::Id::unique(("ribbon_spare_room", tab))
|
||||
}
|
||||
|
||||
/// The width the tab's groups left free in the ribbon on the last frame it was drawn; negative
|
||||
/// when they ran past its edge. A gallery that gives way to the groups after it sizes itself
|
||||
/// from this.
|
||||
pub(super) fn spare_room(ctx: &egui::Context, tab: &str) -> Option<f32> {
|
||||
ctx.data(|d| d.get_temp::<f32>(spare_room_id(tab)))
|
||||
}
|
||||
|
||||
/// A chevron over a ribbon edge that scrolls it; `true` on the frame it is clicked.
|
||||
fn edge_cue(ui: &mut Ui, rect: Rect, glyph: &str, hint: &str, t: &Tokens) -> bool {
|
||||
let resp = ui.interact(rect, ui.scope_id().with(glyph), Sense::click()).on_hover_text(hint);
|
||||
@@ -434,3 +446,6 @@ mod drawtable_tests;
|
||||
|
||||
#[cfg(test)]
|
||||
mod inktoshape_tests;
|
||||
|
||||
#[cfg(test)]
|
||||
mod picture_styles_tests;
|
||||
|
||||
@@ -100,15 +100,31 @@ pub(super) fn picture_format(app: &mut SlideApp, ui: &mut Ui) {
|
||||
group(ui, |ui| {
|
||||
use deckcraft_engine::cmd::picture_style::{STYLES, look};
|
||||
let names: Vec<String> = STYLES.iter().map(|(_, l)| l.to_string()).collect();
|
||||
if let Some(i) = widgets::gallery(
|
||||
ui,
|
||||
STYLES.len(),
|
||||
vec2(56.0, 44.0),
|
||||
None,
|
||||
|p, r, i| paint_picture_style(p, r, STYLES.get(i).and_then(|(id, _)| look(id))),
|
||||
|i| names.get(i).cloned().unwrap_or_default(),
|
||||
) && let Some((id, _)) = STYLES.get(i)
|
||||
{
|
||||
let tiles = |ui: &mut Ui, n: usize| {
|
||||
widgets::gallery(
|
||||
ui,
|
||||
n,
|
||||
STYLE_TILE,
|
||||
None,
|
||||
|p, r, i| paint_picture_style(p, r, STYLES.get(i).and_then(|(id, _)| look(id))),
|
||||
|i| names.get(i).cloned().unwrap_or_default(),
|
||||
)
|
||||
};
|
||||
let mut picked = tiles(ui, inline_styles(ui));
|
||||
// Every style, in rows, behind the gallery's arrow (PowerPoint's More).
|
||||
let more = widgets::drop_button(ui, "", vec2(14.0, 52.0), true);
|
||||
more.widget_info(|| egui::WidgetInfo::labeled(egui::Role::Button, true, "More Picture Styles"));
|
||||
// Six to a row: the 24 styles in four rows.
|
||||
egui::Popup::menu(&more).width(6.0 * (STYLE_TILE.x + 2.0) + 16.0).show(|ui| {
|
||||
ui.spacing_mut().item_spacing = vec2(2.0, 2.0);
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
if let Some(i) = tiles(ui, STYLES.len()) {
|
||||
picked = Some(i);
|
||||
ui.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
if let Some((id, _)) = picked.and_then(|i| STYLES.get(i)) {
|
||||
run(app, "picture.style", json!({"style": id}));
|
||||
}
|
||||
});
|
||||
@@ -172,6 +188,32 @@ pub(super) fn picture_format(app: &mut SlideApp, ui: &mut Ui) {
|
||||
size_group(app, ui);
|
||||
}
|
||||
|
||||
const STYLE_TILE: egui::Vec2 = vec2(56.0, 44.0);
|
||||
/// Styles in the ribbon row before the tab has been measured once.
|
||||
const DEFAULT_INLINE_STYLES: usize = 6;
|
||||
const MIN_INLINE_STYLES: usize = 2;
|
||||
|
||||
/// How many Picture Styles the ribbon row shows: as many as leave room for the groups after the
|
||||
/// gallery, never fewer than [`MIN_INLINE_STYLES`]. With all 24 in the row, Crop and Size sat
|
||||
/// past the ribbon's edge in any window narrower than about 2500 px.
|
||||
fn inline_styles(ui: &Ui) -> usize {
|
||||
use deckcraft_engine::cmd::picture_style::STYLES;
|
||||
// A tile plus the group's 1 pt spacing.
|
||||
let step = STYLE_TILE.x + 1.0;
|
||||
let id = egui::Id::unique("picture_styles_inline");
|
||||
let last = ui.ctx().data(|d| d.get_temp::<usize>(id)).unwrap_or(DEFAULT_INLINE_STYLES);
|
||||
let n = match super::spare_room(ui.ctx(), "Picture Format") {
|
||||
// The groups around the gallery keep their width, so this settles in one frame.
|
||||
Some(spare) if spare.is_finite() => (last as f32 + (spare / step).floor()).clamp(MIN_INLINE_STYLES as f32, STYLES.len() as f32) as usize,
|
||||
_ => last,
|
||||
};
|
||||
if n != last {
|
||||
ui.ctx().data_mut(|d| d.insert_temp(id, n));
|
||||
ui.ctx().request_repaint();
|
||||
}
|
||||
n
|
||||
}
|
||||
|
||||
/// A Picture Styles gallery tile: a landscape "picture" in the style's shape and frame.
|
||||
fn paint_picture_style(p: &egui::Painter, r: Rect, lk: Option<deckcraft_engine::cmd::picture_style::Look>) {
|
||||
let Some(lk) = lk else { return };
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
//! Picture Format ▸ Picture Styles: the ribbon row gives way to Crop and Size, and its arrow opens
|
||||
//! every style.
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used)]
|
||||
|
||||
use deckcraft_engine::Session;
|
||||
use deckcraft_engine::cmd::picture_style::STYLES;
|
||||
use deckcraft_model::{Geom, ShapeId};
|
||||
use egui_kittest::Harness;
|
||||
use egui_kittest::kittest::Queryable;
|
||||
use serde_json::json;
|
||||
|
||||
use crate::{Services, SlideApp};
|
||||
|
||||
/// A blank slide with one selected picture, on the Picture Format tab, in a `width` px window.
|
||||
fn picture_format(width: f32) -> (Harness<'static, SlideApp>, ShapeId) {
|
||||
let mut s = Session::with_new();
|
||||
s.execute("slide.layout", &json!({"layout": "blank"})).unwrap();
|
||||
let pixels = (0..4).flat_map(|_| [40, 120, 200, 255]).collect();
|
||||
let png = deckcraft_render::Image { width: 2, height: 2, pixels }.to_png();
|
||||
let data = deckcraft_engine::cmd::base64_encode(&png);
|
||||
let id = s.execute("insert.picture", &json!({"data": data, "name": "a.png", "rect": [200, 100, 400, 250]})).unwrap()["id"].as_u64().unwrap();
|
||||
s.execute("edit.select", &json!({"ids": [id]})).unwrap();
|
||||
let mut app = SlideApp::new(s, Services::default());
|
||||
app.ui.tab = "Picture Format".into();
|
||||
let mut h = Harness::builder().with_size(egui::vec2(width, 900.0)).build_ui_state(
|
||||
|ui, app: &mut SlideApp| {
|
||||
app.logic(ui.ctx());
|
||||
app.ui(ui);
|
||||
},
|
||||
app,
|
||||
);
|
||||
h.run_steps(4);
|
||||
(h, ShapeId(u32::try_from(id).unwrap()))
|
||||
}
|
||||
|
||||
/// Picture Styles tiles in the ribbon row (the More menu is closed).
|
||||
fn inline_styles(h: &Harness<'_, SlideApp>) -> usize {
|
||||
STYLES.iter().filter(|(_, label)| h.query_by_label(label).is_some()).count()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn crop_and_size_stay_on_screen_beside_the_picture_styles() {
|
||||
// The desktop app opens 1440 px wide. With all 24 styles in the row, Crop and Size sat past
|
||||
// the ribbon's edge there, and at 1920 px too.
|
||||
for width in [1440.0, 1920.0] {
|
||||
let (h, _) = picture_format(width);
|
||||
let edge = h.query_by_label("Scroll ribbon right").map_or(width, |n| n.rect().min.x);
|
||||
for label in ["Crop", "Height value", "Width value"] {
|
||||
let r = h.get_by_label(label).rect();
|
||||
assert!(r.max.x <= edge, "{label} at {r:?} is past the ribbon's edge ({edge}) in a {width} px window");
|
||||
}
|
||||
}
|
||||
let (h, _) = picture_format(1920.0);
|
||||
assert!(h.query_by_label("Scroll ribbon right").is_none(), "the whole tab fits at 1920 px");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_styles_row_grows_with_the_window_and_keeps_two_when_it_is_narrow() {
|
||||
let shown: Vec<usize> = [1280.0, 1440.0, 1920.0, 2600.0, 3200.0].map(|w| inline_styles(&picture_format(w).0)).to_vec();
|
||||
assert!(shown.windows(2).all(|p| p[0] <= p[1]), "{shown:?}");
|
||||
assert_eq!(shown[0], 2, "at least two styles in a narrow window: {shown:?}");
|
||||
assert!(shown[2] > shown[1], "{shown:?}");
|
||||
assert_eq!(shown[4], STYLES.len(), "a wide window shows every style: {shown:?}");
|
||||
|
||||
// Narrow, the ribbon scrolls, but Crop stays in view next to the scroll arrow.
|
||||
let (h, _) = picture_format(1280.0);
|
||||
let arrow = h.get_by_label("Scroll ribbon right").rect();
|
||||
assert!(h.get_by_label("Crop").rect().max.x <= arrow.min.x, "Crop hides behind the scroll arrow at 1280 px");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn more_picture_styles_lists_every_style_and_applies_the_one_clicked() {
|
||||
let (mut h, id) = picture_format(1440.0);
|
||||
let (_, last) = STYLES[STYLES.len() - 1];
|
||||
assert!(h.query_by_label(last).is_none(), "{last} is not in the row at 1440 px");
|
||||
let undo = h.state().session.active().unwrap().history.undo.len();
|
||||
|
||||
h.get_by_label("More Picture Styles").click();
|
||||
h.run_steps(2);
|
||||
// The menu has every style, also the ones the row shows.
|
||||
for (_, label) in STYLES {
|
||||
assert!(h.query_all_by_label(label).count() >= 1, "{label} is missing from More Picture Styles");
|
||||
}
|
||||
h.get_by_label(last).click();
|
||||
h.run_steps(2);
|
||||
|
||||
let doc = h.state().session.active().unwrap();
|
||||
assert!(matches!(&doc.shape(id).unwrap().geom, Geom::Preset { name, .. } if name == "ellipse"), "Metal Oval makes the picture an oval");
|
||||
assert_eq!(doc.history.undo.len(), undo + 1, "one undo step");
|
||||
assert!(h.query_by_label(last).is_none(), "the menu closes after a pick");
|
||||
}
|
||||
+2
-1
@@ -1,6 +1,6 @@
|
||||
# UI parity with PowerPoint
|
||||
|
||||
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-24 · **Change:** trivial (Edit Points: Shift, Ctrl/Cmd or Alt/Option on a handle drag sets the point type; previous: trivial, scroll bars on the zoomed slide pane, #1157; previous: major, community PR wave 2026-10-14: 14 new dialogs (41 in all), Eyedropper, Export button, New Window, Grid Options, WordArt gallery and handles, Slide Sorter sections, Quick Access Toolbar, Format Background and Format Shape depth, rich-text clipboard; duplicated rows and headers folded; previous: minor, File tab / Backstage, #76) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
|
||||
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-29 · **Change:** trivial (Picture Format: the Picture Styles row leaves room for Crop and Size, #1232; previous: trivial, Edit Points: Shift, Ctrl/Cmd or Alt/Option on a handle drag sets the point type; previous: trivial, scroll bars on the zoomed slide pane, #1157; previous: major, community PR wave 2026-10-14: 14 new dialogs (41 in all), Eyedropper, Export button, New Window, Grid Options, WordArt gallery and handles, Slide Sorter sections, Quick Access Toolbar, Format Background and Format Shape depth, rich-text clipboard; duplicated rows and headers folded; previous: minor, File tab / Backstage, #76) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
|
||||
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-22 · **Change:** trivial (group fill and outline edits reach members, #1079; scores unchanged) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
|
||||
|
||||
How DeckCraft looks and feels next to PowerPoint: the shell, the ribbon, menus, panes, dialogs,
|
||||
@@ -169,6 +169,7 @@ consistency is open (#73, #56).
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-29 | trivial | Picture Format: the Picture Styles row shows as many styles as fit beside the groups after it (at least two) and a More arrow with all 24, as the Transitions and Animations galleries do; Crop and Size are in view in the default window (#1232) |
|
||||
| 2026-10-24 | trivial | Table cells: dragging across cells selects a range of cells |
|
||||
| 2026-10-24 | trivial | Edit Points: Shift, Ctrl/Cmd or Alt/Option held while dragging a handle makes the point smooth, straight or corner |
|
||||
| 2026-10-24 | trivial | Columns dialog: the number of text columns and the spacing between them. Dialog count 41 → 42 |
|
||||
|
||||
Reference in New Issue
Block a user