Fixed centering of icons. Disabled zooming in/out on Windows. Added default icons

This commit is contained in:
Lam Wei Lun
2026-09-16 12:15:27 +08:00
parent 435336a3cf
commit f76e4b1e02
7 changed files with 76 additions and 37 deletions
+2
View File
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M14.5,1.5v12a1,1,0,0,1-1,1H1.5a1,1,0,0,1-1-1V1.5a1,1,0,0,1,1-1h12a1,1,0,0,1,1,1Z" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><polyline points="4,5 6.5,7.5 4,10" style="fill:none;stroke:#009688;stroke-linecap:round;stroke-linejoin:round"/><line x1="8" y1="10" x2="11" y2="10" style="fill:none;stroke:#009688;stroke-linecap:round;stroke-linejoin:round"/></svg>

After

Width:  |  Height:  |  Size: 524 B

+13 -5
View File
@@ -294,13 +294,21 @@ function actionIcon(a) {
return (a && a.icon) ? a.icon : ""; return (a && a.icon) ? a.icon : "";
} }
// Put a pictogram into a tile (search row, favourites tile, or tab row). No icon leaves the tile // Pictogram shown when an action carries none (plugins, icon-less commands/settings). A dedicated
// blank. `mono` marks the white tab-strip glyphs, which the CSS recolors to the shared gray. // 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) { function fillTile(tile, a, mono) {
tile.textContent = ""; tile.textContent = "";
var icon = actionIcon(a); var icon = tileIcon(a);
if (!icon)
return;
var img = document.createElement("img"); var img = document.createElement("img");
img.className = mono ? "tile-icon tab-mono" : "tile-icon"; img.className = mono ? "tile-icon tab-mono" : "tile-icon";
img.src = ICON_BASE + icon + ".svg"; img.src = ICON_BASE + icon + ".svg";
@@ -281,6 +281,17 @@ assert.equal(ctx.actionIcon({ id: "x", title: "Plugin action" }), "",
assert.equal(ctx.actionIcon(null), "", assert.equal(ctx.actionIcon(null), "",
"a null action (tab row) renders a blank tile"); "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. // 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: "advanced" }, "simple"), true, "Advanced is gated in Simple mode");
assert.equal(ctx.needsModeSwitch({ mode: "expert" }, "simple"), true, "Expert is gated in Simple mode"); assert.equal(ctx.needsModeSwitch({ mode: "expert" }, "simple"), true, "Expert is gated in Simple mode");
+4 -1
View File
@@ -331,12 +331,15 @@ body {
border: 1px solid var(--speed-tile-border, #d8d8d8); 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 { .tile-icon {
width: 16px; width: 16px;
height: 16px; height: 16px;
display: block; display: block;
pointer-events: none; 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 /* Tab-strip glyphs are drawn white for the dark tab bar; recolor to the shared #949494 gray so
+29 -27
View File
@@ -236,12 +236,12 @@ std::vector<NativeCommand> build_command_catalog()
}); });
// ---- Export pipeline ---- // ---- 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()) if (Plater* plater = wxGetApp().plater())
plater->export_gcode(false); plater->export_gcode(false);
return AppActionRunResult{AppActionRunResult::Level::Success}; 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()) if (Plater* plater = wxGetApp().plater())
plater->export_stl(); plater->export_stl();
return AppActionRunResult{AppActionRunResult::Level::Success}; return AppActionRunResult{AppActionRunResult::Level::Success};
@@ -251,35 +251,37 @@ std::vector<NativeCommand> build_command_catalog()
plater->export_core_3mf(); plater->export_core_3mf();
return AppActionRunResult{AppActionRunResult::Level::Success}; 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()) if (Plater* plater = wxGetApp().plater())
plater->export_gcode_3mf(false); plater->export_gcode_3mf(false);
return AppActionRunResult{AppActionRunResult::Level::Success}; 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()) if (Plater* plater = wxGetApp().plater())
plater->export_gcode_3mf(true); plater->export_gcode_3mf(true);
return AppActionRunResult{AppActionRunResult::Level::Success}; return AppActionRunResult{AppActionRunResult::Level::Success};
}); });
// ---- Calibration ---- // ---- 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); }); [](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); }); [](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); }); [](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); }); [](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); }); [](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); }); [](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); }); [](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); }); [](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 ---- // ---- View ----
// Titles are built with _u8L here (not via a variable) so xgettext can extract them. // Titles are built with _u8L here (not via a variable) so xgettext can extract them.
@@ -322,10 +324,10 @@ std::vector<NativeCommand> build_command_catalog()
}); });
// ---- Object ---- // ---- 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(); }); 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( return object_op(
wxGetApp().plater(), [](Plater* p) { return p->can_delete_all(); }, [](Plater* p) { p->delete_all_objects_from_model(); }); wxGetApp().plater(), [](Plater* p) { return p->can_delete_all(); }, [](Plater* p) { p->delete_all_objects_from_model(); });
}); });
@@ -440,7 +442,7 @@ std::vector<NativeCommand> build_command_catalog()
plater->duplicate_plate(); plater->duplicate_plate();
return AppActionRunResult{AppActionRunResult::Level::Success}; 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(); Plater* plater = wxGetApp().plater();
if (!is_fff_plater(plater)) if (!is_fff_plater(plater))
return plate_unavailable(); return plate_unavailable();
@@ -511,7 +513,7 @@ std::vector<NativeCommand> build_command_catalog()
}); });
// ---- Import ---- // ---- 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()) { if (Plater* plater = wxGetApp().plater()) {
#ifdef __APPLE__ #ifdef __APPLE__
plater->add_model(); plater->add_model();
@@ -521,39 +523,39 @@ std::vector<NativeCommand> build_command_catalog()
} }
return AppActionRunResult{AppActionRunResult::Level::Success}; 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()) if (Plater* plater = wxGetApp().plater())
plater->import_zip_archive(); plater->import_zip_archive();
return AppActionRunResult{AppActionRunResult::Level::Success}; 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) if (MainFrame* mf = wxGetApp().mainframe)
mf->load_config_file(); mf->load_config_file();
return AppActionRunResult{AppActionRunResult::Level::Success}; return AppActionRunResult{AppActionRunResult::Level::Success};
}); });
// ---- Export extras ---- // ---- 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()) if (Plater* plater = wxGetApp().plater())
plater->export_stl(false, false, true); plater->export_stl(false, false, true);
return AppActionRunResult{AppActionRunResult::Level::Success}; 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()) if (Plater* plater = wxGetApp().plater())
plater->export_stl(false, false, false, FT_DRC); plater->export_stl(false, false, false, FT_DRC);
return AppActionRunResult{AppActionRunResult::Level::Success}; 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()) if (Plater* plater = wxGetApp().plater())
plater->export_stl(false, false, true, FT_DRC); plater->export_stl(false, false, true, FT_DRC);
return AppActionRunResult{AppActionRunResult::Level::Success}; 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()) if (Plater* plater = wxGetApp().plater())
plater->export_toolpaths_to_obj(); plater->export_toolpaths_to_obj();
return AppActionRunResult{AppActionRunResult::Level::Success}; 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) if (MainFrame* mf = wxGetApp().mainframe)
mf->export_config(); mf->export_config();
return AppActionRunResult{AppActionRunResult::Level::Success}; return AppActionRunResult{AppActionRunResult::Level::Success};
@@ -568,7 +570,7 @@ std::vector<NativeCommand> build_command_catalog()
wxGetApp().ShowUserGuide(); wxGetApp().ShowUserGuide();
return AppActionRunResult{AppActionRunResult::Level::Success}; 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(); Slic3r::GUI::desktop_open_datadir_folder();
return AppActionRunResult{AppActionRunResult::Level::Success}; return AppActionRunResult{AppActionRunResult::Level::Success};
}); });
@@ -589,11 +591,11 @@ std::vector<NativeCommand> build_command_catalog()
} }
return AppActionRunResult{AppActionRunResult::Level::Success}; 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); wxGetApp().check_new_version_sf(true, 1);
return AppActionRunResult{AppActionRunResult::Level::Success}; 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(); Slic3r::GUI::about();
return AppActionRunResult{AppActionRunResult::Level::Success}; return AppActionRunResult{AppActionRunResult::Level::Success};
}); });
+4
View File
@@ -144,6 +144,10 @@ SpeedDialWebDialog::SpeedDialWebDialog(wxWindow* parent)
SetSizer(sizer); SetSizer(sizer);
SetClientSize(FromDIP(wxSize(kPopupWidth, kPopupMinHeight))); 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 // Re-cut the shape region whenever layout changes the client size; SetShape itself
// does not generate size events, so this cannot recurse. // does not generate size events, so this cannot recurse.
Bind(wxEVT_SIZE, [this](wxSizeEvent& event) { Bind(wxEVT_SIZE, [this](wxSizeEvent& event) {
+13 -4
View File
@@ -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 // Every command's tile pictogram is a theme-neutral SVG (the matching GUI control's icon, or the
// means a blank tile (like the tab picker). Guard representative names and that every non-empty // equivalent settings-group icon); an absent icon gets the page's generic placeholder. Guard
// value resolves to a shipped file, so a rename/typo cannot leave broken images in the palette. // 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]") TEST_CASE("Native command icons resolve to shipped SVGs", "[ActionSource][SpeedDial]")
{ {
const std::vector<Slic3r::GUI::NativeCommand>& commands = Slic3r::GUI::NativeCommands::catalog(); const std::vector<Slic3r::GUI::NativeCommand>& 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{"save_project", "menu_save"},
Expected{"sync_ams", "ams_fila_sync"}, Expected{"sync_ams", "ams_fila_sync"},
Expected{"mode_simple", "advanced"}, 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{"plate_add", "toolbar_add_plate"},
Expected{"add_primitive_cube", "menu_obj_cube"}, 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", ""}}) { Expected{"go_to_tab", ""}}) {
const std::string* icon = icon_of(e.key); const std::string* icon = icon_of(e.key);
INFO(e.key); INFO(e.key);