diff --git a/resources/images/action_default.svg b/resources/images/action_default.svg
new file mode 100644
index 0000000000..118f4792a6
--- /dev/null
+++ b/resources/images/action_default.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/resources/web/dialog/SpeedDial/speeddial.js b/resources/web/dialog/SpeedDial/speeddial.js
index 64550c9e5c..96725c8a8c 100644
--- a/resources/web/dialog/SpeedDial/speeddial.js
+++ b/resources/web/dialog/SpeedDial/speeddial.js
@@ -294,13 +294,21 @@ function actionIcon(a) {
return (a && a.icon) ? a.icon : "";
}
-// Put a pictogram into a tile (search row, favourites tile, or tab row). No icon leaves the tile
-// blank. `mono` marks the white tab-strip glyphs, which the CSS recolors to the shared gray.
+// Pictogram shown when an action carries none (plugins, icon-less commands/settings). A dedicated
+// theme-neutral glyph (resources/images/action_default.svg: frame + ">_" prompt), so no tile is
+// blank and no existing action's icon is borrowed.
+var DEFAULT_ICON = "action_default";
+
+// SVG base name a tile actually renders: the action's own icon, else the placeholder. Pure.
+function tileIcon(a) {
+ return actionIcon(a) || DEFAULT_ICON;
+}
+
+// Put a pictogram into a tile (search row, favourites tile, or tab row). `mono` marks the white
+// tab-strip glyphs, which the CSS recolors to the shared gray.
function fillTile(tile, a, mono) {
tile.textContent = "";
- var icon = actionIcon(a);
- if (!icon)
- return;
+ var icon = tileIcon(a);
var img = document.createElement("img");
img.className = mono ? "tile-icon tab-mono" : "tile-icon";
img.src = ICON_BASE + icon + ".svg";
diff --git a/resources/web/dialog/SpeedDial/speeddial.test.js b/resources/web/dialog/SpeedDial/speeddial.test.js
index 38f7c343ae..4c2f606843 100644
--- a/resources/web/dialog/SpeedDial/speeddial.test.js
+++ b/resources/web/dialog/SpeedDial/speeddial.test.js
@@ -281,6 +281,17 @@ assert.equal(ctx.actionIcon({ id: "x", title: "Plugin action" }), "",
assert.equal(ctx.actionIcon(null), "",
"a null action (tab row) renders a blank tile");
+// tileIcon: the base name a tile renders - the action's own icon when present, else the placeholder.
+assert.equal(ctx.tileIcon({ id: "x", title: "Slice", icon: "media_play" }), "media_play",
+ "an action with an icon keeps it");
+assert.equal(ctx.tileIcon({ id: "x", title: "Go to tab...", icon: "" }), "action_default",
+ "an empty icon falls back to the placeholder");
+assert.equal(ctx.tileIcon({ id: "x", title: "Plugin action" }), "action_default",
+ "a missing icon falls back to the placeholder");
+assert.equal(ctx.DEFAULT_ICON, "action_default", "the placeholder is the dedicated default glyph");
+assert.ok(fs.existsSync(__dirname + "/../../../images/action_default.svg"),
+ "the placeholder SVG ships alongside the page's other icons");
+
// needsModeSwitch: a setting is gated only when its required mode outranks the user's current mode.
assert.equal(ctx.needsModeSwitch({ mode: "advanced" }, "simple"), true, "Advanced is gated in Simple mode");
assert.equal(ctx.needsModeSwitch({ mode: "expert" }, "simple"), true, "Expert is gated in Simple mode");
diff --git a/resources/web/dialog/SpeedDial/style.css b/resources/web/dialog/SpeedDial/style.css
index 5c29e68cdb..4373ff7067 100644
--- a/resources/web/dialog/SpeedDial/style.css
+++ b/resources/web/dialog/SpeedDial/style.css
@@ -331,12 +331,15 @@ body {
border: 1px solid var(--speed-tile-border, #d8d8d8);
}
-/* Native SVG pictogram in a tile; blank tiles (no icon) have no child. */
+/* Native SVG pictogram in a tile. The Orca icon grid draws 1px strokes from 0.5..14.5 of the 16
+ viewBox, so the visible glyph is 0..15 and, centered as-is, leaves one extra pixel on the right/
+ bottom (the "Save Project leans left" look). Shift by half a pixel to center the visible artwork. */
.tile-icon {
width: 16px;
height: 16px;
display: block;
pointer-events: none;
+ transform: translate(.5px, .5px);
}
/* Tab-strip glyphs are drawn white for the dark tab bar; recolor to the shared #949494 gray so
diff --git a/src/slic3r/GUI/NativeCommands.cpp b/src/slic3r/GUI/NativeCommands.cpp
index ea833acae5..2e24f14326 100644
--- a/src/slic3r/GUI/NativeCommands.cpp
+++ b/src/slic3r/GUI/NativeCommands.cpp
@@ -236,12 +236,12 @@ std::vector build_command_catalog()
});
// ---- Export pipeline ----
- add_with_icon("export_gcode", _u8L("Export G-code"), _u8L("Slice & Export"), "menu_export_gcode", [](const std::string&) {
+ add_with_icon("export_gcode", _u8L("Export G-code"), _u8L("Slice & Export"), "custom-gcode_gcode", [](const std::string&) {
if (Plater* plater = wxGetApp().plater())
plater->export_gcode(false);
return AppActionRunResult{AppActionRunResult::Level::Success};
});
- add_with_icon("export_stl", _u8L("Export STL"), _u8L("Slice & Export"), "menu_export_stl", [](const std::string&) {
+ add_with_icon("export_stl", _u8L("Export STL"), _u8L("Slice & Export"), "save", [](const std::string&) {
if (Plater* plater = wxGetApp().plater())
plater->export_stl();
return AppActionRunResult{AppActionRunResult::Level::Success};
@@ -251,35 +251,37 @@ std::vector build_command_catalog()
plater->export_core_3mf();
return AppActionRunResult{AppActionRunResult::Level::Success};
});
- add_with_icon("export_sliced_file", _u8L("Export Sliced File"), _u8L("Slice & Export"), "menu_export_sliced_file", [](const std::string&) {
+ add_with_icon("export_sliced_file", _u8L("Export Sliced File"), _u8L("Slice & Export"), "save", [](const std::string&) {
if (Plater* plater = wxGetApp().plater())
plater->export_gcode_3mf(false);
return AppActionRunResult{AppActionRunResult::Level::Success};
});
- add_with_icon("export_all_sliced_file", _u8L("Export All Sliced Files"), _u8L("Slice & Export"), "menu_export_sliced_file", [](const std::string&) {
+ add_with_icon("export_all_sliced_file", _u8L("Export All Sliced Files"), _u8L("Slice & Export"), "save", [](const std::string&) {
if (Plater* plater = wxGetApp().plater())
plater->export_gcode_3mf(true);
return AppActionRunResult{AppActionRunResult::Level::Success};
});
// ---- Calibration ----
- add_with_icon("calib_temperature", _u8L("Temperature Calibration"), _u8L("Calibration"), "calib_sf",
+ // The tab-strip calib_sf glyph is drawn white for the dark tab bar and vanishes on the palette's
+ // light tile, so each wizard borrows the matching settings-group icon instead (gray + accent green).
+ add_with_icon("calib_temperature", _u8L("Temperature Calibration"), _u8L("Calibration"), "param_temperature",
[](const std::string&) { return calib_command(CalibKind::Temperature); });
- add_with_icon("calib_max_volumetric", _u8L("Max Volumetric Speed Calibration"), _u8L("Calibration"), "calib_sf",
+ add_with_icon("calib_max_volumetric", _u8L("Max Volumetric Speed Calibration"), _u8L("Calibration"), "param_volumetric_speed",
[](const std::string&) { return calib_command(CalibKind::MaxVolumetric); });
- add_with_icon("calib_pressure_advance", _u8L("Pressure Advance Calibration"), _u8L("Calibration"), "calib_sf",
+ add_with_icon("calib_pressure_advance", _u8L("Pressure Advance Calibration"), _u8L("Calibration"), "param_flow_ratio_and_pressure_advance",
[](const std::string&) { return calib_command(CalibKind::PressureAdvance); });
- add_with_icon("calib_flow_ratio", _u8L("Flow Ratio Calibration"), _u8L("Calibration"), "calib_sf",
+ add_with_icon("calib_flow_ratio", _u8L("Flow Ratio Calibration"), _u8L("Calibration"), "param_flow_ratio_and_pressure_advance",
[](const std::string&) { return calib_command(CalibKind::FlowRatio); });
- add_with_icon("calib_retraction", _u8L("Retraction Calibration"), _u8L("Calibration"), "calib_sf",
+ add_with_icon("calib_retraction", _u8L("Retraction Calibration"), _u8L("Calibration"), "param_retraction",
[](const std::string&) { return calib_command(CalibKind::Retraction); });
- add_with_icon("calib_cornering", _u8L("Cornering Calibration"), _u8L("Calibration"), "calib_sf",
+ add_with_icon("calib_cornering", _u8L("Cornering Calibration"), _u8L("Calibration"), "param_precision",
[](const std::string&) { return calib_command(CalibKind::Cornering); });
- add_with_icon("calib_input_shaping_freq", _u8L("Input Shaping Frequency Calibration"), _u8L("Calibration"), "calib_sf",
+ add_with_icon("calib_input_shaping_freq", _u8L("Input Shaping Frequency Calibration"), _u8L("Calibration"), "param_resonance_avoidance",
[](const std::string&) { return calib_command(CalibKind::InputShapingFreq); });
- add_with_icon("calib_input_shaping_damp", _u8L("Input Shaping Damping Calibration"), _u8L("Calibration"), "calib_sf",
+ add_with_icon("calib_input_shaping_damp", _u8L("Input Shaping Damping Calibration"), _u8L("Calibration"), "param_resonance_avoidance",
[](const std::string&) { return calib_command(CalibKind::InputShapingDamp); });
- add_with_icon("calib_vfa", _u8L("VFA Calibration"), _u8L("Calibration"), "calib_sf", [](const std::string&) { return calib_command(CalibKind::VFA); });
+ add_with_icon("calib_vfa", _u8L("VFA Calibration"), _u8L("Calibration"), "param_speed", [](const std::string&) { return calib_command(CalibKind::VFA); });
// ---- View ----
// Titles are built with _u8L here (not via a variable) so xgettext can extract them.
@@ -322,10 +324,10 @@ std::vector build_command_catalog()
});
// ---- Object ----
- add_with_icon("obj_delete", _u8L("Delete Selected"), _u8L("Object"), "menu_delete", [](const std::string&) {
+ add_with_icon("obj_delete", _u8L("Delete Selected"), _u8L("Object"), "delete", [](const std::string&) {
return object_op(wxGetApp().plater(), [](Plater* p) { return !p->is_selection_empty(); }, [](Plater* p) { p->remove_selected(); });
});
- add_with_icon("obj_delete_all", _u8L("Delete All Objects"), _u8L("Object"), "menu_remove", [](const std::string&) {
+ add_with_icon("obj_delete_all", _u8L("Delete All Objects"), _u8L("Object"), "delete", [](const std::string&) {
return object_op(
wxGetApp().plater(), [](Plater* p) { return p->can_delete_all(); }, [](Plater* p) { p->delete_all_objects_from_model(); });
});
@@ -440,7 +442,7 @@ std::vector build_command_catalog()
plater->duplicate_plate();
return AppActionRunResult{AppActionRunResult::Level::Success};
});
- add_with_icon("plate_delete", _u8L("Delete Plate"), _u8L("Plate"), "menu_delete", [](const std::string&) {
+ add_with_icon("plate_delete", _u8L("Delete Plate"), _u8L("Plate"), "delete", [](const std::string&) {
Plater* plater = wxGetApp().plater();
if (!is_fff_plater(plater))
return plate_unavailable();
@@ -511,7 +513,7 @@ std::vector build_command_catalog()
});
// ---- Import ----
- add_with_icon("import_file", _u8L("Import 3MF/STL/STEP/SVG/OBJ/AMF"), _u8L("Import"), "menu_import", [](const std::string&) {
+ add_with_icon("import_file", _u8L("Import 3MF/STL/STEP/SVG/OBJ/AMF"), _u8L("Import"), "menu_open", [](const std::string&) {
if (Plater* plater = wxGetApp().plater()) {
#ifdef __APPLE__
plater->add_model();
@@ -521,39 +523,39 @@ std::vector build_command_catalog()
}
return AppActionRunResult{AppActionRunResult::Level::Success};
});
- add_with_icon("import_zip_archive", _u8L("Import ZIP Archive"), _u8L("Import"), "menu_import", [](const std::string&) {
+ add_with_icon("import_zip_archive", _u8L("Import ZIP Archive"), _u8L("Import"), "menu_open", [](const std::string&) {
if (Plater* plater = wxGetApp().plater())
plater->import_zip_archive();
return AppActionRunResult{AppActionRunResult::Level::Success};
});
- add_with_icon("import_configs", _u8L("Import Configs"), _u8L("Import"), "menu_import", [](const std::string&) {
+ add_with_icon("import_configs", _u8L("Import Configs"), _u8L("Import"), "menu_open", [](const std::string&) {
if (MainFrame* mf = wxGetApp().mainframe)
mf->load_config_file();
return AppActionRunResult{AppActionRunResult::Level::Success};
});
// ---- Export extras ----
- add_with_icon("export_stl_multi", _u8L("Export All Objects as STLs"), _u8L("Export"), "menu_export_stl", [](const std::string&) {
+ add_with_icon("export_stl_multi", _u8L("Export All Objects as STLs"), _u8L("Export"), "save", [](const std::string&) {
if (Plater* plater = wxGetApp().plater())
plater->export_stl(false, false, true);
return AppActionRunResult{AppActionRunResult::Level::Success};
});
- add_with_icon("export_drc_single", _u8L("Export All Objects as DRC (one file)"), _u8L("Export"), "menu_export_stl", [](const std::string&) {
+ add_with_icon("export_drc_single", _u8L("Export All Objects as DRC (one file)"), _u8L("Export"), "save", [](const std::string&) {
if (Plater* plater = wxGetApp().plater())
plater->export_stl(false, false, false, FT_DRC);
return AppActionRunResult{AppActionRunResult::Level::Success};
});
- add_with_icon("export_drc_multi", _u8L("Export All Objects as DRCs"), _u8L("Export"), "menu_export_stl", [](const std::string&) {
+ add_with_icon("export_drc_multi", _u8L("Export All Objects as DRCs"), _u8L("Export"), "save", [](const std::string&) {
if (Plater* plater = wxGetApp().plater())
plater->export_stl(false, false, true, FT_DRC);
return AppActionRunResult{AppActionRunResult::Level::Success};
});
- add_with_icon("export_toolpaths_obj", _u8L("Export Toolpaths as OBJ"), _u8L("Export"), "menu_export_toolpaths", [](const std::string&) {
+ add_with_icon("export_toolpaths_obj", _u8L("Export Toolpaths as OBJ"), _u8L("Export"), "custom-gcode_gcode", [](const std::string&) {
if (Plater* plater = wxGetApp().plater())
plater->export_toolpaths_to_obj();
return AppActionRunResult{AppActionRunResult::Level::Success};
});
- add_with_icon("export_config", _u8L("Export Preset Bundle"), _u8L("Export"), "menu_export_config", [](const std::string&) {
+ add_with_icon("export_config", _u8L("Export Preset Bundle"), _u8L("Export"), "save", [](const std::string&) {
if (MainFrame* mf = wxGetApp().mainframe)
mf->export_config();
return AppActionRunResult{AppActionRunResult::Level::Success};
@@ -568,7 +570,7 @@ std::vector build_command_catalog()
wxGetApp().ShowUserGuide();
return AppActionRunResult{AppActionRunResult::Level::Success};
});
- add_with_icon("help_open_config_folder", _u8L("Show Configuration Folder"), _u8L("Help"), "folder-closed", [](const std::string&) {
+ add_with_icon("help_open_config_folder", _u8L("Show Configuration Folder"), _u8L("Help"), "open_project", [](const std::string&) {
Slic3r::GUI::desktop_open_datadir_folder();
return AppActionRunResult{AppActionRunResult::Level::Success};
});
@@ -589,11 +591,11 @@ std::vector build_command_catalog()
}
return AppActionRunResult{AppActionRunResult::Level::Success};
});
- add_with_icon("help_check_updates", _u8L("Check for Updates"), _u8L("Help"), "ams_refresh_normal", [](const std::string&) {
+ add_with_icon("help_check_updates", _u8L("Check for Updates"), _u8L("Help"), "refresh", [](const std::string&) {
wxGetApp().check_new_version_sf(true, 1);
return AppActionRunResult{AppActionRunResult::Level::Success};
});
- add_with_icon("help_about", _u8L("About OrcaSlicer"), _u8L("Help"), "OrcaSlicer_about", [](const std::string&) {
+ add_with_icon("help_about", _u8L("About OrcaSlicer"), _u8L("Help"), "OrcaSlicer_gradient_circle", [](const std::string&) {
Slic3r::GUI::about();
return AppActionRunResult{AppActionRunResult::Level::Success};
});
diff --git a/src/slic3r/GUI/SpeedDialDialog.cpp b/src/slic3r/GUI/SpeedDialDialog.cpp
index 08f2c45b86..3de0c137bb 100644
--- a/src/slic3r/GUI/SpeedDialDialog.cpp
+++ b/src/slic3r/GUI/SpeedDialDialog.cpp
@@ -144,6 +144,10 @@ SpeedDialWebDialog::SpeedDialWebDialog(wxWindow* parent)
SetSizer(sizer);
SetClientSize(FromDIP(wxSize(kPopupWidth, kPopupMinHeight)));
}
+ // WebView2's browser accelerator keys include Ctrl +/-/0 and Ctrl+wheel zoom, which would resize
+ // the page inside the fixed-size popup. No-op on the other backends (wxWidgets 3.3 base virtual).
+ if (wxWebView* wv = browser())
+ wv->EnableBrowserAcceleratorKeys(false);
// Re-cut the shape region whenever layout changes the client size; SetShape itself
// does not generate size events, so this cannot recurse.
Bind(wxEVT_SIZE, [this](wxSizeEvent& event) {
diff --git a/tests/slic3rutils/test_action_source.cpp b/tests/slic3rutils/test_action_source.cpp
index 1743f09ab3..0eb5edde41 100644
--- a/tests/slic3rutils/test_action_source.cpp
+++ b/tests/slic3rutils/test_action_source.cpp
@@ -171,9 +171,10 @@ TEST_CASE("Native command catalog has unique keys and present titles", "[ActionS
}
}
-// Every command's tile pictogram is the SVG the matching GUI control already uses; an absent icon
-// means a blank tile (like the tab picker). Guard representative names and that every non-empty
-// value resolves to a shipped file, so a rename/typo cannot leave broken images in the palette.
+// Every command's tile pictogram is a theme-neutral SVG (the matching GUI control's icon, or the
+// equivalent settings-group icon); an absent icon gets the page's generic placeholder. Guard
+// representative names and that every non-empty value resolves to a shipped file, so a rename/typo
+// cannot leave broken images in the palette.
TEST_CASE("Native command icons resolve to shipped SVGs", "[ActionSource][SpeedDial]")
{
const std::vector& commands = Slic3r::GUI::NativeCommands::catalog();
@@ -193,9 +194,17 @@ TEST_CASE("Native command icons resolve to shipped SVGs", "[ActionSource][SpeedD
Expected{"save_project", "menu_save"},
Expected{"sync_ams", "ams_fila_sync"},
Expected{"mode_simple", "advanced"},
- Expected{"calib_temperature", "calib_sf"},
+ Expected{"calib_temperature", "param_temperature"},
+ Expected{"calib_cornering", "param_precision"},
Expected{"plate_add", "toolbar_add_plate"},
Expected{"add_primitive_cube", "menu_obj_cube"},
+ // These previously pointed at blank placeholder SVGs or theme-broken ones.
+ Expected{"obj_delete", "delete"},
+ Expected{"export_gcode", "custom-gcode_gcode"},
+ Expected{"import_file", "menu_open"},
+ Expected{"help_open_config_folder", "open_project"},
+ Expected{"help_check_updates", "refresh"},
+ Expected{"help_about", "OrcaSlicer_gradient_circle"},
Expected{"go_to_tab", ""}}) {
const std::string* icon = icon_of(e.key);
INFO(e.key);