diff --git a/resources/web/dialog/SpeedDial/speeddial.js b/resources/web/dialog/SpeedDial/speeddial.js
index 65d2e24430..78ba8a138d 100644
--- a/resources/web/dialog/SpeedDial/speeddial.js
+++ b/resources/web/dialog/SpeedDial/speeddial.js
@@ -37,18 +37,6 @@ var tabOptions = []; // [{id,title}] - notebook pages, fetched on entering
// element handles, assigned in OnInit (kept null so load-time touches no DOM)
var qEl = null, listEl = null, favEl = null, clearEl = null, eyeEl = null, countEl = null, headEl = null;
-// ---- inline setting editor state ---------------------------------------------
-// The "setting" phase (opened by activating a setting action) replaces the list with an editor card
-// for one option. phase transitions: commands -> setting -> (apply / open-in-sidebar) -> closed, or
-// Esc back to commands. settingDesc is the C++ descriptor; settingRows are the per-index control
-// descriptors (1 row for scalars, one per index for vectors); settingFieldEls hold the live controls.
-var settingId = ""; // the setting action id being edited
-var settingDesc = null; // {id,opt_key,type,title,breadcrumb,category,unit,tooltip,editable,control,cardinality,value|values,index_labels,enum_options,min,max,is_int}
-var settingRows = []; // [{index,kind,value,label,enum_options,min,max,unit,is_int}]
-var settingFieldEls = []; // [Element...] parallel to settingRows
-var settingPreviewIcon = null; // beside the editor title, updated live on dropdown pick
-var openDropDownEl = null; // the custom dropdown toggle button whose option list is expanded
-
// ---- pure helpers (no DOM; unit-tested) -------------------------------------
// Pre-normalized haystacks, cached on the action object. The fold is length-preserving (1:1 per
// char) so the ranges FuzzyRangesNorm returns slice the ORIGINAL title/source text correctly. The
@@ -176,26 +164,6 @@ function shouldRenderActionList(query) {
return !!((query || "").trim());
}
-// Label for a closed-enum entry, looked up from its enum_options by value; falls back to the value.
-// Pure so the node-vm test can exercise the dropdown label mapping. `value` is the current int value.
-function dropdownLabel(options, value) {
- var want = String(value == null ? "" : value);
- for (var i = 0; i < (options || []).length; i++)
- if (String(options[i].value) === want)
- return options[i].label != null && options[i].label !== "" ? String(options[i].label) : String(options[i].key != null ? options[i].key : options[i].value);
- return want;
-}
-
-// Pure: the data:URI pictogram for a dropdown's selected value, or "" when none of the options has
-// one (most settings have no pattern icon). Mirrors dropdownLabel so tests can drive it without DOM.
-function dropdownIcon(options, value) {
- var want = String(value == null ? "" : value);
- for (var i = 0; i < (options || []).length; i++)
- if (String(options[i].value) === want && options[i].icon)
- return options[i].icon;
- return "";
-}
-
// Put an action's pattern pictogram into a tile (search row or favourites tile) when it has one,
// otherwise fall back to the monogram. Toggles the has-icon class so CSS neutralises the hue.
function fillTile(tile, a) {
@@ -213,83 +181,6 @@ function fillTile(tile, a) {
}
}
-// Per-index control descriptors for the inline setting editor, derived from the C++ descriptor.
-// Pure so the node-vm test can exercise the scalar/vector + control mapping without a DOM.
-// Values are the current config value(s); vector options get one row per index, each labelled.
-function settingControlRows(desc) {
- if (!desc || !desc.editable) return [];
- var rows = [];
- var values = desc.cardinality === "vector" ? (desc.values || []) : [desc.value];
- var labels = desc.cardinality === "vector" ? (desc.index_labels || []) : [];
- for (var i = 0; i < values.length; i++) {
- rows.push({
- index: i,
- kind: desc.control,
- value: values[i],
- label: labels[i] != null ? String(labels[i]) : (desc.cardinality === "vector" ? String(i + 1) : null),
- enum_options: desc.enum_options || [],
- min: typeof desc.min === "number" ? desc.min : null,
- max: typeof desc.max === "number" ? desc.max : null,
- unit: desc.unit || "",
- is_int: !!desc.is_int
- });
- }
- return rows;
-}
-
-// Pure: read the value a control would submit back for a setting. `el` is a DOM element (never
-// passed in tests). Returns undefined for an unusable value (empty/invalid number, out of range),
-// boolean for toggles, number for numeric, string otherwise.
-function settingControlValue(row, el) {
- if (!row || !el) return undefined;
- switch (row.kind) {
- case "toggle": return !!el.checked;
- case "number": {
- var raw = String(el.value || "").trim();
- if (raw === "") return undefined;
- var n = row.is_int ? parseInt(raw, 10) : parseFloat(raw);
- if (!isFinite(n)) return undefined;
- if (row.min != null && n < row.min) return undefined;
- if (row.max != null && n > row.max) return undefined;
- return n;
- }
- case "dropdown": {
- // value is stored on the toggle button's dataset (set when an option is picked).
- var v = parseInt(el.dataset ? el.dataset.value : "", 10);
- return isFinite(v) ? v : undefined;
- }
- case "combo": {
- // Open enum: free text field (never a select), so read it as the seeded integer value.
- var v = parseInt(el.value, 10);
- return isFinite(v) ? v : undefined;
- }
- case "color":
- case "text":
- case "percent": {
- // percent submission is a string ("10%", "0.5"); C++ parses + clamps it. Empty is invalid.
- var raw = String(el.value || "").trim();
- return raw === "" ? undefined : raw;
- }
- default: return undefined;
- }
-}
-
-// Pure: assemble the value payload for a setting from its edited control rows. Returns the scalar
-// for scalar settings, an array for vector settings, or undefined when any control is invalid.
-function settingCollectedValue(desc, rows, values) {
- if (!desc || !desc.editable) return undefined;
- if (desc.cardinality === "vector") {
- var out = [];
- for (var i = 0; i < rows.length; i++) {
- var v = settingControlValue(rows[i], values[i]);
- if (v === undefined) return undefined;
- out.push(v);
- }
- return out;
- }
- return settingControlValue(rows[0], values[0]);
-}
-
// The active list for the main phase. A typed query ranks every action (commands/plugins/settings)
// by relevance; an empty query shows the recent list (recents are a mixed bag - no discrimination).
function commandList(actions, recents, query) {
@@ -439,13 +330,9 @@ window.HandleStudio = function (payload) {
lastResizeHeight = next.lastResizeHeight;
phase = next.phase;
tabOptions = next.tabOptions;
- // Reset any half-open setting editor (the dialog was closed/reopened), restoring the search.
- settingId = ""; settingDesc = null; settingRows = []; settingFieldEls = [];
- settingPreviewIcon = null;
- openDropDownEl = null;
- // why: builtKey caches phase|query so renderCommandsList can skip a rebuild on arrow-nav. It
- // survives an apply-then-reopen (which never goes through exitPhase), so without a reset the
- // leftover editor card would be mistaken for the empty-query commands list and never rebuilt.
+ // why: builtKey caches phase|query so renderCommandsList can skip a rebuild on arrow-nav.
+ // It survives a re-open (which never goes through exitPhase), so without a reset the cached
+ // empty-query key would skip the rebuild and leave stale list content.
builtKey = "";
if (headEl) headEl.hidden = false;
if (qEl) {
@@ -455,16 +342,6 @@ window.HandleStudio = function (payload) {
}
render({ resize: true, resetScroll: true });
focusInput();
- } else if (payload.command === "setting_descriptor") {
- // Inline editor loaded: render the card. Guard against a stale response for a different id.
- if (payload.descriptor && payload.descriptor.id === settingId)
- settingDesc = payload.descriptor;
- render({ resize: true });
- // why: keyboard focus must land on the field after the card is built, not stay on the hidden
- // search input. fire on a timeout so the element is attached and its content selectable.
- focusSettingEditor();
- } else if (payload.command === "apply_failed") {
- flashHint("Couldn't apply that value");
} else if (payload.command === "tab_results") {
tabOptions = payload.tabs || [];
if (sel.zone === "list")
@@ -649,7 +526,6 @@ function updateFavEyebrow(favs) {
eyeEl.hidden = !a;
}
-// One settings row; has no star/tile because settings aren't pinnable.
// A command/action row - used for search results, recents, and (because settings are actions now)
// the setting options too. All rows are pinnable, so every row carries a star.
function renderActionRow(a, i) {
@@ -864,349 +740,11 @@ function renderPercentList() {
listEl.appendChild(ph);
}
-// ---- inline setting editor (DOM stage) ---------------------------------------
-
-// Collapse every open custom dropdown except `keep` (null collapses all). The menu list elements
-// are the .ed-dropdown-menu siblings of the toggle buttons we track via openDropDownEl.
-function closeOtherDropDowns(keep) {
- if (openDropDownEl && openDropDownEl !== keep && openDropDownEl.parentNode) {
- var m = openDropDownEl.parentNode.querySelector(".ed-dropdown-menu");
- if (m) m.hidden = true;
- openDropDownEl.classList.remove("open");
- }
- if (!keep)
- openDropDownEl = null;
-}
-
-// Collapse the currently open dropdown, if any (kept for the editor's export/import-adjacent helpers).
-function closeEditorDropDown() { closeOtherDropDowns(null); }
-
-// Place an open dropdown menu as a fixed overlay just under its toggle, so the menu floats over the
-// card (never resizing it) and is clamped to the popup's bottom edge with an internal scrollbar for
-// long option lists. position:fixed escapes the card/launcher overflow clipping that an absolute
-// menu would otherwise hit, keeping every option reachable within the window.
-function positionDropDownMenu(btn, menu) {
- var lrect = (document.querySelector(".launcher") || { getBoundingClientRect: function () { return { top: 0, bottom: window.innerHeight }; } }).getBoundingClientRect();
- var rect = btn.getBoundingClientRect();
- // Available room above and below the toggle, within the popup. Opening the menu must not push it
- // past the window edge (that's the unreachable-overflow bug) - pick whichever side has more room
- // and clamp the box to it. Overflow-y:auto scrolls any long list inside the menu itself.
- var spaceBelow = lrect.bottom - (rect.bottom + 8);
- var spaceAbove = (rect.top - 8) - lrect.top;
- var openUp = spaceBelow < spaceAbove;
- var maxH = Math.max(0, Math.min(openUp ? spaceAbove : spaceBelow, 200));
- menu.style.position = "fixed";
- menu.style.width = rect.width + "px";
- menu.style.left = rect.left + "px";
- menu.style.maxHeight = maxH + "px";
- if (openUp) {
- // bottom edge sits just above the toggle; the box grows upward to content height.
- menu.style.top = "auto";
- menu.style.bottom = (lrect.bottom - rect.top + 4) + "px";
- } else {
- menu.style.top = (rect.bottom + 4) + "px";
- menu.style.bottom = "auto";
- }
-}
-
-// Build the control element for one row (toggle/number/dropdown/combo/text/color) and seed it with
-// the current value. Returns {el, node, extra} - node is what is appended, extra carries a datalist.
-function settingInputFor(row) {
- var el;
- if (row.kind === "toggle") {
- el = document.createElement("input");
- el.type = "checkbox";
- el.checked = !!row.value;
- var sw = document.createElement("label");
- sw.className = "ed-switch";
- sw.appendChild(el);
- var slider = document.createElement("span");
- slider.className = "ed-slider";
- sw.appendChild(slider);
- return { el: el, node: sw };
- }
- if (row.kind === "number") {
- el = document.createElement("input");
- el.type = "number";
- el.step = row.is_int ? 1 : "any";
- if (row.min != null) el.min = row.min;
- if (row.max != null) el.max = row.max;
- if (row.value != null && row.value !== "") el.value = row.value;
- return { el: el, node: el };
- }
- if (row.kind === "dropdown") {
- // Native popups are unreliable inside this wxWebView (a click synthesizes a
- // keydown that can reach the global Enter handler and apply+close). Build a custom
- // dropdown: a toggle button that expands an in-flow option list. Selection only updates
- // local state; nothing applies until Enter/Apply. The value lives on the toggle button's
- // dataset so settingControlValue can read it back without the DOM copy.
- var wrap = document.createElement("div");
- wrap.className = "ed-dropdown";
- var btn = document.createElement("button");
- btn.type = "button";
- btn.className = "ed-dropdown-toggle";
- btn.dataset.value = row.value != null ? String(row.value) : "";
- // The selected value's pattern pictogram (hidden when the value has none).
- var toggleIcon = document.createElement("img");
- toggleIcon.className = "ed-dropdown-icon";
- toggleIcon.setAttribute("aria-hidden", "true");
- toggleIcon.alt = "";
- var tIcon = dropdownIcon(row.enum_options || [], row.value);
- toggleIcon.src = tIcon || "";
- toggleIcon.hidden = !tIcon;
- btn.appendChild(toggleIcon);
- var label = document.createElement("span");
- label.className = "ed-dropdown-label";
- label.textContent = dropdownLabel(row.enum_options || [], row.value);
- btn.appendChild(label);
- var caret = document.createElement("span");
- caret.className = "ed-dropdown-caret";
- caret.textContent = "▾";
- btn.appendChild(caret);
- var listEl = document.createElement("div");
- listEl.className = "ed-dropdown-menu";
- listEl.hidden = true;
- (row.enum_options || []).forEach(function (o, oi) {
- var opt = document.createElement("button");
- opt.type = "button";
- opt.className = "ed-dropdown-option";
- if (o.icon) {
- var img = document.createElement("img");
- img.className = "ed-option-icon";
- img.src = o.icon;
- img.alt = "";
- img.setAttribute("aria-hidden", "true");
- opt.appendChild(img);
- }
- var optLabel = document.createElement("span");
- optLabel.className = "ed-option-label";
- optLabel.textContent = o.label;
- opt.appendChild(optLabel);
- if (String(o.value) === String(row.value))
- opt.classList.add("sel");
- opt.onclick = function (ev) {
- ev.stopPropagation();
- btn.dataset.value = String(o.value);
- label.textContent = o.label;
- toggleIcon.src = o.icon || "";
- toggleIcon.hidden = !o.icon;
- listEl.hidden = true;
- btn.classList.remove("open");
- openDropDownEl = null;
- onSettingValueChanged(settingDesc, o);
- };
- listEl.appendChild(opt);
- });
- btn.onclick = function (ev) {
- ev.stopPropagation();
- if (openDropDownEl === btn) {
- // clicking the open toggle closes it
- listEl.hidden = true;
- btn.classList.remove("open");
- openDropDownEl = null;
- return;
- }
- closeOtherDropDowns(null); // collapse any other open dropdown
- positionDropDownMenu(btn, listEl);
- listEl.hidden = false;
- btn.classList.add("open");
- openDropDownEl = btn;
- };
- wrap.appendChild(btn);
- wrap.appendChild(listEl);
- return { el: btn, node: wrap };
- }
- if (row.kind === "combo") {
- el = document.createElement("input");
- el.type = "text";
- var dl = document.createElement("datalist");
- el.setAttribute("list", dl.id = "ed-combo-" + row.index);
- (row.enum_options || []).forEach(function (o) {
- var op = document.createElement("option");
- op.value = o.value;
- op.textContent = o.label;
- dl.appendChild(op);
- });
- el.value = row.value != null ? String(row.value) : "";
- return { el: el, node: el, extra: dl };
- }
- if (row.kind === "color") {
- el = document.createElement("input");
- el.type = "color";
- el.value = row.value && /^#[0-9a-fA-F]{6}$/.test(row.value) ? row.value : "#000000";
- return { el: el, node: el };
- }
- if (row.kind === "percent") {
- // "mm or %" (coFloatOrPercent/coFloatsOrPercents): a free-text field showing the serialized
- // value (e.g. "10%" or "0.5"). The unit hints at the sidebar semantics (mm or %), so it's
- // not shown here - the value itself carries the % when applicable.
- el = document.createElement("input");
- el.type = "text";
- el.value = row.value != null ? String(row.value) : "";
- return { el: el, node: el };
- }
- // text
- el = document.createElement("input");
- el.type = "text";
- el.value = row.value != null ? String(row.value) : "";
- return { el: el, node: el };
-}
-
-// One labeled control row in the editor card.
-function renderControlRow(row, i) {
- var wrap = document.createElement("div");
- wrap.className = "editor-row";
- if (row.label != null) {
- var lab = document.createElement("label");
- lab.className = "editor-label";
- lab.textContent = row.label;
- wrap.appendChild(lab);
- }
- var ctrl = settingInputFor(row);
- if (ctrl.extra)
- wrap.appendChild(ctrl.extra); // datalist for open-enum combos
- wrap.appendChild(ctrl.node);
- if (row.unit) {
- var unit = document.createElement("span");
- unit.className = "editor-unit";
- unit.textContent = row.unit;
- wrap.appendChild(unit);
- }
- settingFieldEls[i] = ctrl.el;
- return wrap;
-}
-
-// Render the editor card into listEl (phase === "setting"). Keeps the search head hidden so the
-// card owns the layout.
-function renderSettingStage() {
- listEl.innerHTML = "";
- listEl.className = "dial-list setting";
- if (countEl) countEl.hidden = true;
- if (!settingDesc || !settingDesc.opt_key) {
- var ph = document.createElement("div");
- ph.className = "dial-empty";
- ph.textContent = "Loading…";
- listEl.appendChild(ph);
- return;
- }
- var card = document.createElement("div");
- card.className = "dial-editor";
- if (settingDesc.breadcrumb) {
- var crumb = document.createElement("div");
- crumb.className = "row-eyebrow";
- crumb.textContent = settingDesc.breadcrumb;
- card.appendChild(crumb);
- }
- var titleRow = document.createElement("div");
- titleRow.className = "editor-title-row";
- var titleIcon = document.createElement("img");
- titleIcon.className = "editor-preview-icon";
- titleIcon.setAttribute("aria-hidden", "true");
- titleIcon.alt = "";
- var pIcon = dropdownIcon(settingDesc.enum_options || [], settingDesc.value);
- titleIcon.src = pIcon || "";
- titleIcon.hidden = !pIcon;
- titleRow.appendChild(titleIcon);
- settingPreviewIcon = titleIcon;
- var title = document.createElement("div");
- title.className = "editor-title";
- title.textContent = settingDesc.title || "";
- titleRow.appendChild(title);
- card.appendChild(titleRow);
- if (settingDesc.tooltip) {
- var tt = document.createElement("div");
- tt.className = "editor-tooltip";
- tt.textContent = settingDesc.tooltip;
- card.appendChild(tt);
- }
-
- var actions = document.createElement("div");
- actions.className = "editor-actions";
- if (settingDesc.editable) {
- settingRows = settingControlRows(settingDesc);
- settingFieldEls = [];
- if (settingRows.length) {
- settingRows.forEach(function (row, i) { card.appendChild(renderControlRow(row, i)); });
- } else {
- var empty = document.createElement("div");
- empty.className = "dial-empty";
- empty.textContent = "Nothing editable here";
- card.appendChild(empty);
- }
- var apply = document.createElement("button");
- apply.className = "ed-btn ed-btn-primary";
- apply.textContent = "Apply";
- apply.onclick = applySetting;
- actions.appendChild(apply);
- } else {
- var ro = document.createElement("div");
- ro.className = "editor-readonly";
- ro.textContent = "This setting can't be edited here";
- card.appendChild(ro);
- var open = document.createElement("button");
- open.className = "ed-btn";
- open.textContent = "Open in sidebar";
- open.onclick = openSettingInSidebar;
- actions.appendChild(open);
- }
- var cancel = document.createElement("button");
- cancel.className = "ed-btn";
- cancel.textContent = "Cancel";
- cancel.onclick = exitPhase;
- actions.appendChild(cancel);
- card.appendChild(actions);
- var hint = document.createElement("div");
- hint.className = "editor-hint";
- hint.textContent = "Enter to apply · Esc to cancel";
- card.appendChild(hint);
- listEl.appendChild(card);
-}
-
-function applySetting() {
- if (!settingDesc || !settingDesc.editable) return;
- var value = settingCollectedValue(settingDesc, settingRows, settingFieldEls);
- if (value === undefined) {
- flashHint("Enter a valid value");
- return;
- }
- SendMessage({ command: "set_setting", id: settingId, value: value });
-}
-
-function openSettingInSidebar() {
- if (!settingDesc) return;
- SendMessage({ command: "open_setting_in_sidebar", opt_key: settingDesc.opt_key, type: settingDesc.type, category: settingDesc.category || "" });
-}
-
-// When a dropdown option is picked, mirror its pattern pictogram onto the editor title's preview so
-// the current selection is visible without opening the menu. Non-enum / icon-less rows no-op.
-function onSettingValueChanged(desc, option) {
- if (!settingPreviewIcon) return;
- var icon = (option && option.icon) || "";
- settingPreviewIcon.src = icon;
- settingPreviewIcon.hidden = !icon;
-}
-
-function enterSettingPhase(a) {
- if (!a) return;
- phase = "setting";
- query = ""; qEl.value = ""; syncClearButton();
- sel = { zone: "list", i: 0 };
- settingId = a.id;
- settingDesc = null;
- settingRows = [];
- settingFieldEls = [];
- if (headEl) headEl.hidden = true;
- render({ resetScroll: true });
- SendMessage({ command: "setting_descriptor", id: a.id });
-}
-
function renderList() {
if (phase === "tab")
renderTabList();
else if (phase === "percent")
renderPercentList();
- else if (phase === "setting")
- renderSettingStage();
else
renderCommandsList();
}
@@ -1284,7 +822,6 @@ function activateEntry(a) {
if (!a) return;
if (a.input === "percent") { enterPercentPhase(); return; }
if (a.input === "tab") { enterTabsPhase(); return; }
- if (a.input === "setting") { enterSettingPhase(a); return; }
run(a);
}
@@ -1337,14 +874,11 @@ function enterTabsPhase() {
function exitPhase() {
phase = "commands"; tabOptions = []; query = ""; qEl.value = "";
- settingId = ""; settingDesc = null; settingRows = []; settingFieldEls = [];
- settingPreviewIcon = null;
- closeOtherDropDowns(null);
if (headEl) headEl.hidden = false;
sel = { zone: "list", i: 0 };
// why: builtKey caches phase|query so renderCommandsList can skip a rebuild on arrow-nav/click.
- // Leftover from the setting phase it matches the (empty-query) commands key, which would skip
- // the rebuild and leave the editor card in the list. Reset it so the commands view is rebuilt.
+ // It survives a second-phase exit (which never goes through exitPhase from the commands view),
+ // so without a reset the cached empty-query key would skip the rebuild and leave stale content.
builtKey = "";
qEl.placeholder = "Search " + ACTIONS.length + " actions";
syncClearButton();
@@ -1354,19 +888,6 @@ function exitPhase() {
function focusInput() { setTimeout(function () { if (qEl) qEl.focus(); }, 0); }
-// Move keyboard focus onto the first editable field of the setting editor card. Deferred so the
-// element is attached and its text is selectable by the time we focus it. For text-editable inputs
-// also select the existing value so the user can type straight over it.
-function focusSettingEditor() {
- setTimeout(function () {
- var el = settingFieldEls && settingFieldEls[0];
- if (!el) return;
- if (el.focus) el.focus();
- if ((el.tagName === "INPUT") && el.select)
- el.select();
- }, 0);
-}
-
// ---- init --------------------------------------------------------------------
function OnInit() {
qEl = $("q"); listEl = $("list"); favEl = $("favBar"); clearEl = $("clear"); eyeEl = $("favEyebrow"); countEl = $("count");
@@ -1397,21 +918,10 @@ function OnInit() {
// why: dismiss the fav context menu on any click/scroll away from it (capture scroll to catch nested scrollers).
document.addEventListener("click", hideFavMenu);
- // Dismiss an open editor dropdown on any outside click. Toggle/option clicks stopPropagation
- // so they don't immediately close the menu they just opened/picked from.
- document.addEventListener("click", function () {
- if (openDropDownEl) closeEditorDropDown();
- });
document.addEventListener("scroll", hideFavMenu, true);
document.addEventListener("keydown", function (e) {
if (favMenuEl && !favMenuEl.hidden && e.key === "Escape") { e.preventDefault(); hideFavMenu(); return; }
- // While an editor dropdown menu is open it owns the keys: Escape closes the menu (a second
- // Esc exits the phase), Enter/arrows select options natively, and we must not apply/exit.
- if (phase === "setting" && openDropDownEl && openDropDownEl.parentNode) {
- if (e.key === "Escape") { e.preventDefault(); closeEditorDropDown(); }
- return;
- }
// Quick-launch a numbered favourite: Alt/Option + digit (0 = the 10th). Only in the
// commands phase, where the pinned bar is shown.
if (phase === "commands" && e.altKey && !e.ctrlKey && !e.metaKey) {
@@ -1432,8 +942,8 @@ function OnInit() {
// let Left/Right fall through so they move the caret in the focused search field.
var lr = e.key === "ArrowLeft" || e.key === "ArrowRight";
if (e.key === "ArrowDown" || e.key === "ArrowUp" || (lr && sel.zone === "fav")) {
- // In the percent/setting phases the input controls own the caret - arrows edit text, not rows.
- if (phase === "percent" || phase === "setting") return;
+ // In the percent phase the input control owns the caret - arrows edit text, not rows.
+ if (phase === "percent") return;
e.preventDefault();
sel = nextSel(sel, e.key, list.length, favs.length);
// why: entering/leaving the fav zone toggles the eyebrow line, changing launcher height;
@@ -1442,7 +952,6 @@ function OnInit() {
} else if (e.key === "Enter") {
e.preventDefault();
if (phase === "percent") runJumpToLayer(query.trim());
- else if (phase === "setting") applySetting();
else runSelected();
} else if (e.key === "Escape") {
e.preventDefault();
diff --git a/resources/web/dialog/SpeedDial/speeddial.test.js b/resources/web/dialog/SpeedDial/speeddial.test.js
index cb7ef0c179..da306255b1 100644
--- a/resources/web/dialog/SpeedDial/speeddial.test.js
+++ b/resources/web/dialog/SpeedDial/speeddial.test.js
@@ -159,73 +159,4 @@ assert.equal(ctx.revealTarget(100, -5, 50), 50, "negative start is clamped to th
assert.equal(ctx.revealTarget(200, 50, 100), 150, "a scroll viewpoint reveals a window past the current rows");
assert.equal(ctx.revealTarget(10, 0, 50), 10, "a list shorter than one window stays fully materialized");
-// ---- inline setting editor helpers (settingControlRows / Value / CollectedValue) ----
-const scalarBoolDesc = {
- editable: true, control: "toggle", cardinality: "scalar", value: true,
- min: undefined, max: undefined, is_int: false, unit: ""
-};
-const scalarRows = ctx.settingControlRows(scalarBoolDesc);
-assert.equal(scalarRows.length, 1, "a scalar setting yields exactly one control row");
-assert.equal(scalarRows[0].kind, "toggle", "the control kind is carried through");
-assert.equal(scalarRows[0].index, 0, "the single row is indexed 0");
-assert.deepEqual(ctx.settingControlRows({ editable: false }), [], "a non-editable setting yields no control rows");
-
-const vectorDesc = {
- editable: true, control: "number", cardinality: "vector", values: [0.2, 0.4],
- index_labels: ["1", "2"], min: 0, max: 1, is_int: false, unit: "mm"
-};
-const vectorRows = ctx.settingControlRows(vectorDesc);
-assert.equal(vectorRows.length, 2, "a vector setting yields one row per value");
-assert.deepEqual(vectorRows.map(function (r) { return r.value; }), [0.2, 0.4], "each row carries its current value");
-assert.deepEqual(vectorRows.map(function (r) { return r.label; }), ["1", "2"], "vector rows use the per-index labels");
-assert.equal(vectorRows[0].unit, "mm", "the unit is carried to each row");
-
-// settingControlValue reads a control element (checked/value) as the shipped value.
-assert.equal(ctx.settingControlValue({ kind: "toggle" }, { checked: true }), true, "a toggle submits its checked state");
-assert.equal(ctx.settingControlValue({ kind: "toggle" }, { checked: false }), false, "an off toggle submits false");
-assert.equal(ctx.settingControlValue({ kind: "number", is_int: false, min: 0, max: 1 }, { value: "0.5" }), 0.5, "a float number parses");
-assert.equal(ctx.settingControlValue({ kind: "number", is_int: true, min: 0, max: 10 }, { value: "5" }), 5, "an int number parses");
-assert.equal(ctx.settingControlValue({ kind: "number", is_int: false, min: 0, max: 1 }, { value: "2" }), undefined, "an out-of-range number is rejected");
-assert.equal(ctx.settingControlValue({ kind: "number", is_int: false, min: 0, max: 1 }, { value: "" }), undefined, "an empty number is rejected");
-assert.equal(ctx.settingControlValue({ kind: "dropdown" }, { dataset: { value: "3" } }), 3, "an enum dropdown submits its stored int value");
-assert.equal(ctx.settingControlValue({ kind: "dropdown" }, { dataset: { value: "nope" } }), undefined, "a non-numeric dropdown value is rejected");
-assert.equal(ctx.settingControlValue({ kind: "dropdown" }, {}), undefined, "a dropdown with no value is rejected");
-// dropdownLabel maps the current int value to its human label, falling back to the value.
-const seamOptions = [
- { value: 0, key: "nearest", label: "Nearest" },
- { value: 1, key: "aligned", label: "Aligned" },
- { value: 2, key: "random", label: "Random" }
-];
-assert.equal(ctx.dropdownLabel(seamOptions, 1), "Aligned", "the dropdown label for a known value is its label");
-assert.equal(ctx.dropdownLabel(seamOptions, 9), "9", "an unknown value falls back to the raw value");
-assert.equal(ctx.dropdownLabel([], 3), "3", "empty options fall back to the raw value");
-assert.equal(ctx.dropdownLabel(
- [{ value: 1, key: "aligned", label: "" }], 1), "aligned", "a blank label falls back to the key");
-// dropdownIcon maps the current int value to its pattern pictogram, empty when there is none.
-const patternOptions = [
- { value: 0, key: "rectilinear", label: "Rectilinear" },
- { value: 3, key: "gyroid", label: "Gyroid", icon: "data:image/svg+xml;base64,AAA" },
- { value: 5, key: "grid", label: "Grid", icon: "data:image/svg+xml;base64,BBB" }
-];
-assert.equal(ctx.dropdownIcon(patternOptions, 3), "data:image/svg+xml;base64,AAA", "a known value returns its icon");
-assert.equal(ctx.dropdownIcon(patternOptions, 0), "", "a value with no icon returns empty");
-assert.equal(ctx.dropdownIcon(patternOptions, 9), "", "an unknown value returns empty");
-assert.equal(ctx.dropdownIcon([], 3), "", "empty options return empty");
-assert.equal(ctx.settingControlValue({ kind: "text" }, { value: "hello" }), "hello", "text submits as a string");
-// percent (coFloatOrPercent / coFloatsOrPercents): submits the raw typed string; empty is invalid.
-assert.equal(ctx.settingControlValue({ kind: "percent" }, { value: "10%" }), "10%", "a percent value submits as its string");
-assert.equal(ctx.settingControlValue({ kind: "percent" }, { value: "0.5" }), "0.5", "an mm value submits as a plain string");
-assert.equal(ctx.settingControlValue({ kind: "percent" }, { value: " " }), undefined, "a blank percent value is rejected");
-assert.equal(ctx.settingControlValue({ kind: "percent" }, {}), undefined, "a percent field with no value is rejected");
-// A percent scalar-to-scalar payload carries the raw string through unchanged.
-assert.equal(ctx.settingCollectedValue(
- { editable: true, control: "percent", cardinality: "scalar", value: "10%" },
- [{ kind: "percent", value: "10%" }], [{ value: "10%" }]), "10%", "a percent scalar assembles its string");
-
-// settingCollectedValue assembles the payload (scalar vs vector) for the set_setting message.
-assert.equal(ctx.settingCollectedValue({ editable: false }, [], []), undefined, "a non-editable setting yields no payload");
-assert.equal(ctx.settingCollectedValue(scalarBoolDesc, scalarRows, [{ checked: true }]), true, "a scalar assembles a single value");
-assert.deepEqual(ctx.settingCollectedValue(vectorDesc, vectorRows, [{ value: "0.3" }, { value: "0.7" }]), [0.3, 0.7], "a vector assembles an array");
-assert.equal(ctx.settingCollectedValue(vectorDesc, vectorRows, [{ value: "0.3" }, { value: "2" }]), undefined, "an invalid vector element aborts the whole payload");
-
console.log("ok");
diff --git a/resources/web/dialog/SpeedDial/style.css b/resources/web/dialog/SpeedDial/style.css
index b5108462dd..2467e88749 100644
--- a/resources/web/dialog/SpeedDial/style.css
+++ b/resources/web/dialog/SpeedDial/style.css
@@ -294,145 +294,3 @@ kbd {
color: var(--muted, var(--orca-muted, #6b7280));
text-align: center;
}
-
-/* ---- inline setting editor card (phase "setting") --------------------------- */
-.dial-list.setting { overflow-y: auto; }
-.dial-editor {
- display: flex;
- flex-direction: column;
- gap: 6px;
- padding: 8px;
-}
-.editor-title { font-size: 14px; font-weight: 600; line-height: 1.3; }
-.editor-title-row { display: flex; align-items: center; gap: 8px; }
-.editor-preview-icon {
- flex: 0 0 auto;
- width: 24px;
- height: 24px;
-}
-.editor-preview-icon[hidden] { display: none; }
-.editor-tooltip { font-size: 11px; line-height: 1.4; color: var(--muted, var(--orca-muted, #6b7280)); }
-.editor-row { display: flex; align-items: center; gap: 8px; min-height: 32px; }
-.editor-label {
- flex: 0 0 auto;
- min-width: 64px;
- font-size: 12px;
- color: var(--muted, var(--orca-muted, #6b7280));
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-.editor-unit { font-size: 12px; color: var(--muted, var(--orca-muted, #6b7280)); }
-.editor-row input[type="number"],
-.editor-row input[type="text"],
-.editor-row select,
-.editor-row input[type="color"] {
- flex: 1 1 auto;
- min-width: 0;
- height: 30px;
- padding: 0 8px;
- border: 1px solid var(--border, var(--orca-border, #ddd));
- border-radius: 6px;
- background: var(--panel, var(--orca-bg, #fff));
- color: var(--text, var(--orca-fg, #1b1c1e));
- font: inherit;
-}
-.editor-row input[type="color"] { padding: 2px 4px; cursor: pointer; }
-.editor-row input:focus { outline: none; border-color: var(--main-color, var(--orca-accent, #009688)); box-shadow: 0 0 0 2px rgba(0,150,136,.22); }
-/* custom dropdown (native popups are flaky in the embedded webview) */
-.ed-dropdown { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }
-.ed-dropdown-toggle {
- display: flex;
- align-items: center;
- gap: 6px;
- width: 100%;
- height: 30px;
- padding: 0 8px;
- border: 1px solid var(--border, var(--orca-border, #ddd));
- border-radius: 6px;
- background: var(--panel, var(--orca-bg, #fff));
- color: var(--text, var(--orca-fg, #1b1c1e));
- font: inherit;
- text-align: left;
- cursor: pointer;
-}
-.ed-dropdown-toggle.open { border-color: var(--main-color, var(--orca-accent, #009688)); }
-.ed-dropdown-label { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
-.ed-dropdown-caret { flex: 0 0 auto; font-size: 10px; color: var(--muted, var(--orca-muted, #6b7280)); transition: transform .12s; }
-.ed-dropdown-toggle.open .ed-dropdown-caret { transform: rotate(180deg); }
-.ed-dropdown-menu {
- /* Overlay popover, not in-flow: opening it must NOT grow the editor card or the popup window.
- position/left/top/width/max-height are set from JS on open (position:fixed escapes the
- scrollable card and launcher overflow, so the menu clips to the popup and scrolls itself).
- These are only the visual defaults. */
- position: absolute;
- z-index: 30;
- border: 1px solid var(--border, var(--orca-border, #ddd));
- border-radius: 6px;
- background: var(--panel, var(--orca-bg, #fff));
- box-shadow: 0 8px 24px rgba(0,0,0,.16);
- max-height: 200px;
- overflow-y: auto;
-}
-.ed-dropdown-option {
- display: flex;
- align-items: center;
- gap: 8px;
- width: 100%;
- padding: 6px 8px;
- border: 0;
- background: none;
- color: var(--text, var(--orca-fg, #1b1c1e));
- font: inherit;
- text-align: left;
- cursor: pointer;
-}
-.ed-dropdown-option:hover { background: var(--row-hover, rgba(127,127,127,.16)); }
-.ed-dropdown-option.sel { font-weight: 600; color: var(--main-color, var(--orca-accent, #009688)); }
-.ed-option-label { flex: 1 1 auto; min-width: 0; }
-/* Pattern pictograms: filled with the option's icon, and (for values with none) absent. */
-.ed-option-icon,
-.ed-dropdown-icon {
- flex: 0 0 auto;
- width: 16px;
- height: 16px;
-}
-.ed-dropdown-icon[hidden] { display: none; }
-/* toggle switch */
-.ed-switch { position: relative; flex: 0 0 auto; width: 36px; height: 20px; }
-.ed-switch input { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; opacity: 0; cursor: pointer; }
-.ed-slider {
- position: absolute;
- inset: 0;
- background: #c4c4c4;
- border-radius: 10px;
- transition: background .15s;
-}
-.ed-slider::before {
- content: "";
- position: absolute;
- width: 16px;
- height: 16px;
- left: 2px;
- top: 2px;
- background: #fff;
- border-radius: 50%;
- transition: transform .15s;
-}
-.ed-switch input:checked + .ed-slider { background: var(--main-color, var(--orca-accent, #009688)); }
-.ed-switch input:checked + .ed-slider::before { transform: translateX(16px); }
-.editor-readonly { padding: 4px 0; font-size: 12px; color: var(--muted, var(--orca-muted, #6b7280)); }
-.editor-actions { display: flex; gap: 8px; padding-top: 4px; }
-.ed-btn {
- padding: 6px 12px;
- border: 1px solid var(--border, var(--orca-border, #ddd));
- border-radius: 6px;
- background: var(--panel, var(--orca-bg, #fff));
- color: var(--text, var(--orca-fg, #1b1c1e));
- font: inherit;
- cursor: pointer;
-}
-.ed-btn:hover { background: var(--row-hover, rgba(127,127,127,.16)); }
-.ed-btn-primary { background: var(--main-color, var(--orca-accent, #009688)); border-color: var(--main-color, var(--orca-accent, #009688)); color: #fff; }
-.ed-btn-primary:hover { filter: brightness(1.05); }
-.editor-hint { font-size: 10px; text-align: center; color: var(--muted, var(--orca-muted, #6b7280)); }
diff --git a/src/slic3r/GUI/ActionRegistry.cpp b/src/slic3r/GUI/ActionRegistry.cpp
index ef31401909..b55455f45d 100644
--- a/src/slic3r/GUI/ActionRegistry.cpp
+++ b/src/slic3r/GUI/ActionRegistry.cpp
@@ -180,17 +180,15 @@ struct SettingAction : AppAction
, type(type_in)
, category(std::move(category_in))
{
- // A setting is a two-phase command: activating it opens the inline editor (the "setting"
- // phase) instead of running. Native run() is a no-op fallback; the editor applies through
- // apply_setting().
+ // A setting is a single-phase command: activating it jumps the sidebar to the option
+ // (like the sidebar's own settings search), then the dial closes. run() performs the jump.
this->kind = AppActionKind::Command;
this->group = std::move(group);
- this->input = "setting";
}
AppActionRunResult run(const std::string& /*param*/) const override
{
- // Two-phase: the palette collects the edit; native run() is a no-op fallback.
+ wxGetApp().sidebar().jump_to_option(opt_key, type, category);
return {AppActionRunResult::Level::Success};
}
@@ -693,45 +691,7 @@ std::vector> native_commands()
return out;
}
-// ---- inline setting editor helpers ------------------------------------------
-
-// The inline editor "control" kind for an option, or "" when it can't be edited inline
-// (plugin-backed values, points, serialized strings, etc.). readonly/legend options are
-// filtered out of the search entirely in materialize_setting_actions(), so they never
-// reach here.
-std::string setting_control(const ConfigOptionDef& def)
-{
- if (def.readonly || def.gui_type == ConfigOptionDef::GUIType::legend ||
- def.gui_type == ConfigOptionDef::GUIType::one_string || def.is_plugin_backed())
- return "";
- // Serialized vectors are entered as ONE semicolon-separated field (e.g. post_process), which the
- // per-index editor doesn't model - keep them in the open-in-sidebar bucket.
- if (def.gui_flags.find("serialized") != std::string::npos)
- return "";
- switch (def.gui_type) {
- case ConfigOptionDef::GUIType::color: return "color";
- case ConfigOptionDef::GUIType::i_enum_open:
- case ConfigOptionDef::GUIType::f_enum_open: return "combo";
- default: break;
- }
- switch (def.type) {
- case coBool:
- case coBools: return "toggle";
- case coEnum:
- case coEnums: return def.enum_values.empty() ? "combo" : "dropdown";
- case coInt:
- case coInts:
- case coFloat:
- case coFloats:
- case coPercent:
- case coPercents: return "number";
- case coFloatOrPercent:
- case coFloatsOrPercents: return "percent";
- case coString:
- case coStrings: return "text";
- default: return "";
- }
-}
+// ---- setting action helpers --------------------------------------------------
// Self-contained base64 encoder (for the tiny pictogram SVGs), avoiding a dependency on the exact
// wxBase64Encode overload/return type across wx versions.
@@ -790,41 +750,6 @@ std::string setting_icon_for_key(const std::string& key)
return "data:image/svg+xml;base64," + base64_encode(data);
}
-// [{value,key,label,icon}...] for an enum/combo. Ordered by enum_values when present, else by the
-// keys_map iteration. label falls back to the key when enum_labels doesn't provide one. `icon` is
-// the value's pattern pictogram when one exists, empty otherwise.
-nlohmann::json setting_enum_options(const ConfigOptionDef& def)
-{
- nlohmann::json out = nlohmann::json::array();
- auto label_at = [&](size_t i, const std::string& key) -> std::string {
- return (i < def.enum_labels.size() && !def.enum_labels[i].empty()) ? def.enum_labels[i] : key;
- };
- if (def.enum_keys_map != nullptr) {
- std::vector ordered;
- if (!def.enum_values.empty())
- ordered = def.enum_values;
- else
- for (const auto& kv : *def.enum_keys_map)
- ordered.push_back(kv.first);
- for (size_t i = 0; i < ordered.size(); ++i) {
- auto it = def.enum_keys_map->find(ordered[i]);
- if (it == def.enum_keys_map->end())
- continue;
- out.push_back({{"value", it->second},
- {"key", ordered[i]},
- {"label", label_at(i, ordered[i])},
- {"icon", setting_icon_for_key(ordered[i])}});
- }
- } else {
- for (size_t i = 0; i < def.enum_values.size(); ++i)
- out.push_back({{"value", (long long) i},
- {"key", def.enum_values[i]},
- {"label", label_at(i, def.enum_values[i])},
- {"icon", setting_icon_for_key(def.enum_values[i])}});
- }
- return out;
-}
-
// The pattern pictogram for a setting's CURRENT value (its enum int), empty when it isn't a
// pattern-style enum or the value has no icon. Used for the search-result tile.
std::string setting_action_icon(const SettingAction& a)
@@ -854,146 +779,6 @@ std::string setting_action_icon(const SettingAction& a)
std::string SettingAction::icon() const { return setting_action_icon(*this); }
-// Current value of the option at vector index `idx` as JSON (bool/number/string), or null for a
-// type the inline editor doesn't render. `config` is the tab's live config; when an option is
-// absent the def's default is shown.
-nlohmann::json setting_value_json(const DynamicPrintConfig& config, const ConfigOptionDef& def, size_t idx)
-{
- const ConfigOption* opt = config.option(def.opt_key);
- const ConfigOption* root = opt ? opt : def.default_value.get();
- if (!root)
- return nullptr;
- switch (def.type) {
- case coBool: return root->getBool();
- case coInt: return root->getInt();
- case coFloat: return root->getFloat();
- case coPercent: return root->getFloat();
- case coString: return static_cast(root)->value;
- case coEnum: return root->getInt();
- case coBools: {
- if (auto v = dynamic_cast(root))
- return bool(v->get_at(idx));
- if (auto v = dynamic_cast(root))
- return bool(v->get_at(idx) != 0);
- return nullptr;
- }
- case coInts: {
- if (auto v = dynamic_cast(root))
- return v->get_at(idx);
- if (auto v = dynamic_cast(root)) {
- const int nil = ConfigOptionIntsNullable::nil_value();
- int val = v->get_at(idx);
- return val == nil ? nlohmann::json(nullptr) : nlohmann::json(val);
- }
- return nullptr;
- }
- case coFloats: {
- if (auto v = dynamic_cast(root))
- return v->get_at(idx);
- if (auto v = dynamic_cast(root)) {
- double val = v->get_at(idx);
- return std::isnan(val) ? nlohmann::json(nullptr) : nlohmann::json(val);
- }
- return nullptr;
- }
- case coPercents: {
- if (auto v = dynamic_cast(root))
- return v->get_at(idx);
- if (auto v = dynamic_cast(root)) {
- double val = v->get_at(idx);
- return std::isnan(val) ? nlohmann::json(nullptr) : nlohmann::json(val);
- }
- return nullptr;
- }
- case coStrings: return static_cast(root)->get_at(idx);
- case coEnums: {
- if (auto v = dynamic_cast(root))
- return v->get_at(idx);
- if (auto v = dynamic_cast(root)) {
- const int nil = ConfigOptionEnumsGenericNullable::nil_value();
- int val = v->get_at(idx);
- return val == nil ? nlohmann::json(nullptr) : nlohmann::json(val);
- }
- return nullptr;
- }
- case coFloatOrPercent: return root->serialize();
- case coFloatsOrPercents: {
- if (auto v = dynamic_cast(root)) {
- auto ss = v->vserialize();
- return idx < ss.size() ? ss[idx] : nullptr;
- }
- if (auto v = dynamic_cast(root)) {
- auto ss = v->vserialize();
- return idx < ss.size() ? ss[idx] : nullptr;
- }
- return nullptr;
- }
- default: return nullptr;
- }
-}
-
-// How many scalar values the option currently has (1 for scalars, the array length for vectors).
-size_t setting_value_count(const DynamicPrintConfig& config, const ConfigOptionDef& def)
-{
- if ((int(def.type) & int(coVectorType)) == 0)
- return 1;
- // size() lives on ConfigOptionVectorBase, not ConfigOption - dynamic_cast to it covers every
- // vector type (and their nullable variants) polymorphically.
- const ConfigOption* opt = config.option(def.opt_key);
- if (opt)
- if (auto v = dynamic_cast(opt))
- return v->size();
- if (def.default_value)
- if (auto v = dynamic_cast(def.default_value.get()))
- return v->size();
- return 1;
-}
-
-// boost::any for a single element, matching what Slic3r::GUI::change_opt_value expects.
-boost::any setting_any_from_json(const ConfigOptionDef& def, const nlohmann::json& v)
-{
- if (!v.is_null()) {
- switch (def.type) {
- case coBool: return boost::any(v.is_boolean() ? v.get() : v.get() != 0);
- case coInt: return boost::any(v.get());
- case coFloat:
- case coPercent: return boost::any(v.get());
- case coString: return boost::any(v.get());
- case coEnum: return boost::any(v.get());
- case coBools: return boost::any(static_cast(v.get() ? 1 : 0));
- case coInts: return boost::any(v.get());
- case coFloats:
- case coPercents: return boost::any(v.get());
- case coStrings: return boost::any(v.get());
- case coFloatOrPercent:
- case coFloatsOrPercents: {
- // change_opt_value detects "percent" via a trailing '%', so trim whitespace first or a
- // stray space (e.g. "10% ") would be misread as mm.
- std::string s = v.is_string() ? v.get() : std::to_string(v.get());
- boost::trim(s);
- // An empty string would make change_opt_value's str.back() UB - bail out to a rejected apply.
- return s.empty() ? boost::any() : boost::any(s);
- }
- case coEnums: return boost::any(v.get());
- default: break;
- }
- }
- // Coerce numeric types that may arrive as a different JSON numeric type.
- if (v.is_number()) {
- switch (def.type) {
- case coInt:
- case coEnums: return boost::any(v.get());
- case coFloat:
- case coPercent:
- case coInts:
- case coFloats:
- case coPercents: return boost::any(v.get());
- default: break;
- }
- }
- return boost::any();
-}
-
} // namespace
ActionRegistry::~ActionRegistry() = default;
@@ -1257,16 +1042,6 @@ void ActionRegistry::materialize_setting_actions()
std::unordered_set seen;
for (const Search::Option& opt : options) {
- // Omit rows the inline editor can't represent and that aren't useful as a jump target:
- // readonly (e.g. the detected thread count) and legend (static text) GUI rows. They remain
- // in the sidebar's own search; only the Speed Dial pool drops them.
- Tab* tab = wxGetApp().get_tab(opt.type);
- if (tab && tab->get_config()) {
- const ConfigOptionDef* def = tab->get_config()->def()->get(opt.opt_key());
- if (!def || def->readonly || def->gui_type == ConfigOptionDef::GUIType::legend)
- continue;
- }
-
const std::string id = SettingAction::id_for(opt.opt_key(), opt.type);
seen.insert(id);
@@ -1468,140 +1243,4 @@ nlohmann::json ActionRegistry::tab_options() const
return out;
}
-// ---- inline setting editor (read the current value) --------------------------
-
-nlohmann::json ActionRegistry::setting_descriptor(const std::string& id) const
-{
- assert(wxThread::IsMain());
- const AppAction* a = by_id(id);
- const SettingAction* sa = dynamic_cast(a);
- if (!sa)
- return nlohmann::json::object();
- Tab* tab = wxGetApp().get_tab(sa->type);
- if (!tab)
- return nlohmann::json::object();
- DynamicPrintConfig* config = tab->get_config();
- if (!config)
- return nlohmann::json::object();
- const ConfigOptionDef* def = config->def()->get(sa->opt_key);
- if (!def)
- return nlohmann::json::object();
-
- const std::string control = setting_control(*def);
- const bool vector = (int(def->type) & int(coVectorType)) != 0;
-
- nlohmann::json d = {{"id", sa->id()},
- {"opt_key", sa->opt_key},
- {"type", int(sa->type)},
- {"title", a->title()},
- {"breadcrumb", a->source_name()},
- {"category", boost::nowide::narrow(sa->category)},
- {"unit", def->sidetext},
- {"tooltip", def->tooltip},
- {"editable", !control.empty()},
- {"control", control},
- {"cardinality", vector ? "vector" : "scalar"}};
-
- if (!control.empty()) {
- // Hide unbounded min/max so the page doesn't clamp a sane value to ±FLT_MAX.
- if (def->min > -FLT_MAX)
- d["min"] = def->min;
- if (def->max < FLT_MAX)
- d["max"] = def->max;
- if (control == "number")
- d["is_int"] = (def->type == coInt || def->type == coInts);
- if (control == "dropdown" || control == "combo")
- d["enum_options"] = setting_enum_options(*def);
- if (vector) {
- nlohmann::json values = nlohmann::json::array();
- nlohmann::json labels = nlohmann::json::array();
- const size_t n = setting_value_count(*config, *def);
- for (size_t i = 0; i < n; ++i) {
- values.push_back(setting_value_json(*config, *def, i));
- labels.push_back(std::to_string(i + 1));
- }
- d["values"] = std::move(values);
- d["index_labels"] = std::move(labels);
- } else {
- d["value"] = setting_value_json(*config, *def, 0);
- }
- }
- return d;
-}
-
-// ---- inline setting editor (write the edited value back) ---------------------
-
-bool ActionRegistry::apply_setting(const std::string& id, const nlohmann::json& value)
-{
- assert(wxThread::IsMain());
- const AppAction* a = by_id(id);
- const SettingAction* sa = dynamic_cast(a);
- if (!sa)
- return false;
- Tab* tab = wxGetApp().get_tab(sa->type);
- if (!tab)
- return false;
- DynamicPrintConfig* config = tab->get_config();
- if (!config)
- return false;
- const ConfigOptionDef* def = config->def()->get(sa->opt_key);
- if (!def)
- return false;
- const std::string control = setting_control(*def);
- if (control.empty())
- return false;
-
- const bool vector = (int(def->type) & int(coVectorType)) != 0;
- const size_t n = vector ? (value.is_array() ? value.size() : 0) : 1;
- if (vector && n == 0)
- return false;
-
- for (size_t i = 0; i < n; ++i) {
- const nlohmann::json& elem = vector ? value[i] : value;
- boost::any any = setting_any_from_json(*def, elem);
- if (any.empty())
- return false;
- if (control == "number" && elem.is_number()) {
- const double d = elem.get();
- if (d < def->min || d > def->max)
- return false;
- }
- if (control == "percent" && elem.is_string()) {
- // "mm or %" value: strip a trailing %/whitespace, clamp the numeric part to [min,max].
- // Reject anything that isn't a well-formed number (which change_opt_value would throw on).
- std::string s = elem.get();
- boost::trim(s);
- if (!s.empty() && s.back() == '%')
- s.pop_back();
- boost::trim(s);
- if (s.empty())
- return false;
- char* end = nullptr;
- const double d = std::strtod(s.c_str(), &end);
- if (end == s.c_str() || *end != '\0')
- return false;
- if (d < def->min || d > def->max)
- return false;
- }
- Slic3r::GUI::change_opt_value(*config, sa->opt_key, any, int(i));
- }
-
- // Mark the preset modified like a sidebar edit. Scalar options also get the standard
- // post-change hook so dependent settings refresh; vector options have no unambiguous scalar
- // value to pass, so on_value_change is skipped (the config write + dirty flag is still correct).
- tab->update_dirty();
- if (!vector) {
- boost::any any = setting_any_from_json(*def, value);
- if (!any.empty())
- tab->on_value_change(sa->opt_key, any);
- }
-
- // The config write is separate from the on-screen Field, so repaint the field(s) that display
- // this option (on whatever page they live, not just the active page) - otherwise the sidebar
- // shows the "modified" arrow but keeps the stale value pushed to the last edit/reload.
- if (Page* page = nullptr; tab->get_field(sa->opt_key, &page) && page)
- page->reload_config();
- return true;
-}
-
}} // namespace Slic3r::GUI
diff --git a/src/slic3r/GUI/ActionRegistry.hpp b/src/slic3r/GUI/ActionRegistry.hpp
index d7bd1fa583..6daafdde56 100644
--- a/src/slic3r/GUI/ActionRegistry.hpp
+++ b/src/slic3r/GUI/ActionRegistry.hpp
@@ -173,16 +173,6 @@ public:
// plugins) aren't separate pages and are not listed. Call on the UI thread; null-safe.
nlohmann::json tab_options() const;
- // Inline setting editor descriptor for a SettingAction id. Returns the JSON the palette
- // renders: {id, opt_key, type, title, breadcrumb, category, group, unit, tooltip, editable,
- // control ("toggle|number|dropdown|combo|text|color"), cardinality ("scalar"|"vector"),
- // value|values, index_labels[], enum_options[], min|max}. Empty object for a non-setting id.
- nlohmann::json setting_descriptor(const std::string& id) const;
- // Apply an edit submitted by the palette. `value` is the control's JSON payload (scalar, or an
- // array for vector settings). Writes the value(s) into the global preset config and marks the
- // preset dirty, exactly like a sidebar edit. Returns false on a bad id/type/value.
- bool apply_setting(const std::string& id, const nlohmann::json& value);
-
private:
void seed_state(AppAction& a) const; // favourite/stats from config
AppAction* find(const std::string& id);
diff --git a/src/slic3r/GUI/SpeedDialDialog.cpp b/src/slic3r/GUI/SpeedDialDialog.cpp
index 593d8a7344..b199d0b5ae 100644
--- a/src/slic3r/GUI/SpeedDialDialog.cpp
+++ b/src/slic3r/GUI/SpeedDialDialog.cpp
@@ -9,10 +9,6 @@
#include "Plater.hpp"
#include "Widgets/WebViewHostDialog.hpp"
-#include
-
-#include
-
#include
#include
@@ -155,27 +151,6 @@ void SpeedDialWebDialog::handle_web_command(const nlohmann::json& payload)
if (wxGetApp().mainframe)
wxGetApp().mainframe->select_tab(from_u8(tab_id));
}
- } else if (command == "setting_descriptor") {
- // Inline editor: hand the page the descriptor for the setting it's editing.
- const std::string id = payload.value("id", "");
- call_web_handler(
- {{"command", "setting_descriptor"}, {"descriptor", wxGetApp().action_registry().setting_descriptor(id)}});
- } else if (command == "set_setting") {
- // Inline editor submit. Apply the value; on success close the dialog.
- const std::string id = payload.value("id", "");
- const nlohmann::json value = payload.contains("value") ? payload["value"] : nlohmann::json(nullptr);
- if (id.empty() || !wxGetApp().action_registry().apply_setting(id, value)) {
- call_web_handler({{"command", "apply_failed"}, {"id", id}});
- return;
- }
- Hide();
- } else if (command == "open_setting_in_sidebar") {
- // Non-inline-editable setting (points, plugin-backed, float-or-percent): jump the sidebar.
- Hide();
- const std::string opt_key = payload.value("opt_key", "");
- const std::string category = payload.value("category", "");
- if (!opt_key.empty())
- wxGetApp().sidebar().jump_to_option(opt_key, Preset::Type(payload.value("type", int(Preset::TYPE_INVALID))), boost::nowide::widen(category));
} else if (command == "resize")
resize_to_content(json_int_or(payload, "height", 0));
}