Skip to content
Merged
31 changes: 26 additions & 5 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -1056,8 +1056,9 @@
and the renderer read the same command. A plugin swatch gallery
(`plugin_ui::is_swatch_gallery`, one predicate for `Docxy::swatch_gallery`
and `ribbon_fit::gallery_geom`) draws bare tiles, so its estimate has no
well `frame`: `n * w + (n - 1) * gap`. It is never capped, so a wide gallery
collapses honestly; `fit_with` keeps no state, so a group collapsed at a
well `frame`: `n * w + (n - 1) * gap`. In place it is bounded to
`IN_PLACE_TILES` tiles plus the More button's gap and `MORE_W` (#1823), so a
long gallery no longer collapses its group by width alone; `fit_with` keeps no state, so a group collapsed at a
narrow width is full again at a wide one (`plugin_ui` tests pin both).
- `fit_with` ends with a back-fill (#1763): once the collapse passes fit, groups
they took too early (a wider group's collapse can free more than the small
Expand Down Expand Up @@ -1716,19 +1717,39 @@
colours; proportions are by estimate, no capture of Project's tile is in
this repository), `gallery_layout` (a header row per run of equal non-empty
sections, every section on a new row) and `span_of`/`gallery_span` (in a
flyout the longest section run, in place every tile: what `ribbon_fit::model_control_w` and `Docxy::swatch_gallery`
flyout the longest section run, in place at most `IN_PLACE_TILES`, #1823: what `ribbon_fit::model_control_w` and `Docxy::swatch_gallery`
both wrap by, so the estimate is the width drawn). Height is
`layout_height` (tiles plus `HEADER_H` headers plus gaps), drawn only.
A palette tile shows no label, its label is the tooltip. Sections show in
the flyout (expanded) copy only: in place (`max_w` None) a group is one
fixed-height strip, so `gallery_span`/`gallery_rows` give every tile on one
row with no headers, exactly as before sections; a header is clipped to the
fixed-height strip, so `gallery_span`/`gallery_rows` give one row with no
headers: the `in_place_window` strip (#1823); a header is clipped to the
gallery's width.
- `ribbon-read` adds `section` and, on a palette tile, `palette` and `marks`.
The fixture's `fixture.styles` gallery (group "Fixture Styles", last on the
View tab) has two sections; `project-plugin-ui.uit` reads it and shoots it
(a `shot` is evidence, not a comparison with the plugin repo's capture).

- An in-place swatch gallery is bounded (#1823): it shows at most
`plugin_ui::IN_PLACE_TILES` (6, an estimate: no capture of Project's) tiles,
`in_place_window` (the first six, or the six ending on the checked swatch;
`gallery_rows(.., None, .., checked)` is that one strip),
and, when it has more, a `MORE_W` (16 px) More button after a gap (probe
`gallery-more-<id>`) whose press is `flyout_toggle(FlyoutTarget::Gallery)`
(a second press shuts it), the whole gallery
with its sections. `gallery_span(.., None)` and `more_w` are what
`ribbon_fit::model_control_w` and `swatch_gallery` both read, so the
estimate is the width drawn. A flyout copy (a collapsed group's, `max_w`
Some) wraps every tile and has no More button. No scroll arrows: the
flyout shows the rest. The Styles and Table Styles wells are unchanged.
`ribbon-read` adds `shown`, `first_shown` and `more` (the model's window)
and `drawn_shown`, `drawn_first` and `drawn_more` (what the last frame drew
in place, `Docxy::drawn_galleries`; null when not drawn in place) to each
`galleries` entry; `gallery-more:<id>` is the More button's region. The fixture's
`fixture.wide` (nine swatches, group "Fixture Wide" on the Report tab,
which has room) is the bounded one in
`project-plugin-ui.uit`.

- A gallery has a KeyTip (#1762): `ribbonspec::Gallery::key_tip` (`""` for every
built-in), filled from a manifest gallery's `keytip`. With the key tips up
`swatch_gallery` draws the badge on the one in the ribbon (not a flyout's
Expand Down
15 changes: 13 additions & 2 deletions docs/ui-test-harness.md
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,13 @@ reported like the built-ins, marked `"plugin": true`: `ribbon-read`'s
commands and gallery tiles (a contributed control with a built-in's id
replaces it in place, so the built-in's slot then reads `plugin: true`; a
group with a contributed dialog launcher reads `launcher: true` and, when the
plugin names them, `launcher_tip` and `launcher_key_tip`, #1669),
plugin names them, `launcher_tip` and `launcher_key_tip`, #1669; each
`galleries` entry reads `shown`, `first_shown` and `more`, the model's
in-place window of a swatch gallery whatever its group's fit state: the tile
count, the first tile and whether a More button follows, and `drawn_shown`,
`drawn_first` and `drawn_more`, what the last frame drew in place, null when
the gallery was not drawn in place then (another tab, a collapsed group),
#1823),
`menu-read`'s items (the row, bar, column and chart menus; `menu-open
{"target":"chart"}` opens the chart's own menu beside the bars: Timescale... and
Zoom... are live and open their dialogs, the rest of Project's items are drawn greyed), and `dialog-read`'s controls (a dialog a
Expand Down Expand Up @@ -1666,7 +1672,8 @@ have. `word-header-footer.uit` drives these over a three-section document.
clipped as the heading is; #1533), `project-hbar-table`, `project-hbar-chart`,
`project-vbar`, `project-timeline`, `project-timeline-box-left`,
`project-timeline-box-right`, `project-timeline-grip`, `project-split`, `gallery`, `ribbon`,
`ribbon-flyout`, `ribbon-group:<index>`, `ribbon-launch:<group title>`, `title-tabs`,
`ribbon-flyout`, `ribbon-group:<index>`, `ribbon-launch:<group title>`,
`gallery-more:<gallery id>`, `title-tabs`,
`tab-prev`, `tab-next`, `tab-more`, `tab-more-item:<index>` (the last exists while
the more-tabs list is open), `tab-chip:<index>` — a visible title-bar chip by
absolute tab index; absent while the chip is scrolled out of the strip. `gantt`
Expand All @@ -1691,6 +1698,10 @@ title on the tab shown, while the group is drawn in place (#1164): a sheet
group's (↘), a document or Project group's (⤢), a UI plugin's group's
included (#1669). It is an error when the group draws no launcher, and when the
group is not drawn in place (collapsed into a flyout, or on another tab).
`gallery-more:<gallery id>` (for example `gallery-more:fixture.wide`) is the
More button of a UI plugin's swatch gallery drawn in place with more tiles
than it shows (#1823); a click opens the whole gallery as a flyout. It is an
error when the gallery draws no More button in place.
`filter-button:<column>` (for example
`filter-button:B`) is the AutoFilter button on that column's header cell
(#690), an error while the sheet has no filter there or the button is
Expand Down
17 changes: 16 additions & 1 deletion projcore/tests/plugin_fixture/src/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
//! On the Gantt Chart Format tab a second group "Fixture Percent" after
//! "Fixture Format" declares `priority` 120 and holds five small labelled
//! buttons (`fixture.pct0` to `fixture.pct100`, #1685).
//! - On the Report tab a group "Fixture Wide" holds `fixture.wide`, nine
//! colour swatches: in place a window of six and a More button (#1823). Its
//! swatches are not commands; the state checks the eighth
//! (`fixture.wide.7`), so the window shows tiles 2 to 7.
//! - Menus: a row-menu item `fixture.rename` after "Notes...", and
//! `fixture.mark` after it (#1667).
//! - Dialogs: a Task Information page "Fixture" with a multiline, a grid and
Expand Down Expand Up @@ -105,6 +109,17 @@ const MANIFEST: &str = r##"{
{"id":"fixture.pct50","kind":"button","label":"50%"},
{"id":"fixture.pct75","kind":"button","label":"75%"},
{"id":"fixture.pct100","kind":"button","label":"100% Complete"}]}]},
{"tab":"Report","groups":[{"title":"Fixture Wide","priority":1,"controls":[
{"id":"fixture.wide","kind":"gallery","label":"Wide","tip":"Nine colour swatches: in place a window of six and a More button (#1823)","swatches":[
{"id":"fixture.wide.0","label":"Wide 0","color":"#3366CC"},
{"id":"fixture.wide.1","label":"Wide 1","color":"#CC3333"},
{"id":"fixture.wide.2","label":"Wide 2","color":"#44AA66"},
{"id":"fixture.wide.3","label":"Wide 3","color":"#AA8833"},
{"id":"fixture.wide.4","label":"Wide 4","color":"#7744AA"},
{"id":"fixture.wide.5","label":"Wide 5","color":"#33AAAA"},
{"id":"fixture.wide.6","label":"Wide 6","color":"#AA3388"},
{"id":"fixture.wide.7","label":"Wide 7","color":"#555555"},
{"id":"fixture.wide.8","label":"Wide 8","color":"#88AA22"}]}]}]},
{"tab":"View","groups":[{"title":"Fixture Styles","priority":0,"controls":[
{"id":"fixture.styles","kind":"gallery","label":"Styles","keytip":"XG","tip":"Two sections of palette tiles (#1761)","swatches":[
{"id":"fixture.styles.0","label":"Blue one","section":"Scheduling Styles","preview":"fixture.blue","color":"#3366CC"},
Expand Down Expand Up @@ -327,7 +342,7 @@ pub unsafe extern "C" fn yppx_ui_state(input: *const u8, len: usize, out: *mut Y
};
let selected = first_uid(&req).is_some();
let mut reply = format!(
r#"{{"controls":{{"fixture.flag":{{"enabled":true,"checked":{}}},"fixture.plain":{{"enabled":true,"checked":{}}},"fixture.flat":{{"enabled":true,"checked":{}}},"fixture.bold":{{"enabled":{selected},"checked":{}}},"fixture.rename":{{"enabled":{selected}}},"fixture.mark":{{"enabled":{selected}}}}},"gallery":{{"fixture.palette":{},"fixture.styles":"fixture.styles.1"}}"#,
r#"{{"controls":{{"fixture.flag":{{"enabled":true,"checked":{}}},"fixture.plain":{{"enabled":true,"checked":{}}},"fixture.flat":{{"enabled":true,"checked":{}}},"fixture.bold":{{"enabled":{selected},"checked":{}}},"fixture.rename":{{"enabled":{selected}}},"fixture.mark":{{"enabled":{selected}}}}},"gallery":{{"fixture.palette":{},"fixture.styles":"fixture.styles.1","fixture.wide":"fixture.wide.7"}}"#,
flag(&req),
switch_on(&req, "fixture.plain"),
switch_on(&req, "fixture.flat"),
Expand Down
69 changes: 69 additions & 0 deletions suite/docxy/src/harness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -980,6 +980,8 @@ pub enum Region {
/// place on the tab shown (#1164; any ribbon's group, a plugin's
/// included, #1669).
RibbonLaunch(&'static str),
/// A bounded in-place swatch gallery's More button, by gallery id (#1823).
GalleryMore(&'static str),
/// The AutoFilter button on column `.0`'s header cell (#690).
FilterButton(u32),
/// The File screen's scrolling content pane (#1028).
Expand Down Expand Up @@ -1081,6 +1083,12 @@ pub fn parse_region(name: &str) -> Result<Region, String> {
// measured (`launch_title`).
Ok(Region::RibbonLaunch(crate::plugin_ui::intern(a)))
}
"gallery-more" => {
let a = arg
.filter(|a| !a.is_empty())
.ok_or("'gallery-more' needs a gallery id, e.g. gallery-more:fixture.wide")?;
Ok(Region::GalleryMore(crate::plugin_ui::intern(a)))
}
"backstage-content" if arg.is_none() => Ok(Region::BackstageContent),
"backstage-rail" if arg.is_none() => Ok(Region::BackstageRail),
"doc-line-start" | "doc-line-end" => {
Expand Down Expand Up @@ -1232,6 +1240,7 @@ pub fn region_name(region: Region) -> String {
Region::RibbonFlyout => "ribbon-flyout".into(),
Region::RibbonGroup(i) => format!("ribbon-group:{i}"),
Region::RibbonLaunch(title) => format!("ribbon-launch:{title}"),
Region::GalleryMore(id) => format!("gallery-more:{id}"),
Region::BackstageContent => "backstage-content".into(),
Region::BackstageRail => "backstage-rail".into(),
Region::Qat(name) => name.into(),
Expand Down Expand Up @@ -2936,11 +2945,25 @@ fn tab_json(
let mut commands = Vec::new();
let mut galleries = Vec::new();
for control in &group.items {
let first = commands.len();
control_commands(control, &mut commands);
if let crate::Control::Gallery(g) = control {
// A swatch gallery in place shows a window of its tiles,
// the checked one in it, and a More button (#1823).
let swatch = crate::plugin_ui::is_swatch_gallery(g);
let checked_at = commands[first..].iter().position(checked);
let window = if swatch {
crate::plugin_ui::in_place_window(g.items.len(), checked_at)
} else {
0..g.items.len()
};
let more = swatch && crate::plugin_ui::in_place_more(g.items.len());
galleries.push(Json::obj(vec![
("id", Json::Str(g.id.into())),
("key_tip", Json::Str(g.key_tip.into())),
("shown", Json::Num(window.len() as f64)),
("first_shown", Json::Num(window.start as f64)),
("more", Json::Bool(more)),
(
"items",
Json::Arr(g.items.iter().map(|i| Json::Str(i.label.into())).collect()),
Expand Down Expand Up @@ -3100,9 +3123,54 @@ fn ribbon_json(app: &crate::Docxy) -> Json {
);
add_combo_values(&mut json, app);
set_live_qat(&mut json, app.qat_state());
add_drawn_galleries(&mut json, &app.drawn_galleries.borrow());
json
}

/// What the last frame drew of each swatch gallery in place (#1823):
/// `drawn_first`, `drawn_shown` and `drawn_more` beside the model's window,
/// null for a gallery not drawn in place then (another tab, a collapsed group).
fn add_drawn_galleries(json: &mut Json, drawn: &crate::DrawnGalleries) {
let Json::Obj(top) = json else { return };
let Some((_, Json::Arr(tabs))) = top.iter_mut().find(|(k, _)| k == "tabs") else {
return;
};
for tab in tabs {
let Json::Obj(tab) = tab else { continue };
let Some((_, Json::Arr(groups))) = tab.iter_mut().find(|(k, _)| k == "groups") else {
continue;
};
for group in groups {
let Json::Obj(group) = group else { continue };
let Some((_, Json::Arr(gals))) = group.iter_mut().find(|(k, _)| k == "galleries")
else {
continue;
};
for gal in gals {
let Json::Obj(gal) = gal else { continue };
let id = gal.iter().find_map(|(k, v)| match (k.as_str(), v) {
("id", Json::Str(id)) => Some(id.clone()),
_ => None,
});
let seen = id.and_then(|id| drawn.get(id.as_str()).cloned());
let num = |n: usize| Json::Num(n as f64);
gal.push((
"drawn_first".into(),
seen.as_ref().map_or(Json::Null, |(w, _)| num(w.start)),
));
gal.push((
"drawn_shown".into(),
seen.as_ref().map_or(Json::Null, |(w, _)| num(w.len())),
));
gal.push((
"drawn_more".into(),
seen.as_ref().map_or(Json::Null, |(_, m)| Json::Bool(*m)),
));
}
}
}
}

/// Give the Header from Top / Footer from Bottom boxes the value they show
/// (inches, as drawn: `0.5"`).
fn add_combo_values(json: &mut Json, app: &crate::Docxy) {
Expand Down Expand Up @@ -9867,6 +9935,7 @@ mod tests {
Region::RibbonGroup(6),
Region::RibbonLaunch("Number"),
Region::RibbonLaunch("Page Setup"),
Region::GalleryMore("fixture.wide"),
Region::BackstageContent,
Region::BackstageRail,
Region::FilterButton(1),
Expand Down
Loading
Loading