mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-27 10:51:22 +00:00
Speed Dial: replace tile monograms with native SVG icons
Tiles previously rendered a colored monogram (title/source initials plus an ordinal) with a per-id hue. Show the matching native SVG icon instead: - AppAction/NativeCommand gain an `icon` field; commands get a curated key->icon table and settings inherit their group header's icon. - Notebook tracks each page's resource icon name and reports it in tab_options(), so the tab picker can show it too. - Searcher records the group icon so settings keep it through search. - Web tile rendering swaps monogramFor/hue for an <img>; drop the now-unused hue/text CSS vars. Add a test asserting every non-empty icon resolves to a shipped SVG.
This commit is contained in:
@@ -222,42 +222,29 @@ function shouldRenderActionList(query) {
|
||||
return !!((query || "").trim());
|
||||
}
|
||||
|
||||
// Monogram code for a tile: title initial, escalated on collision by PREPENDING the source
|
||||
// initial (pi+ti, e.g. "GC"), then a 1-based ordinal - so same-titled items stay distinct.
|
||||
// why: ordinal is assigned by id, not by list order - list order is frecency-sorted and
|
||||
// reshuffles as usage changes, which would otherwise flip who's "1" and who's "2" across runs.
|
||||
function monogramFor(item, list, titleOf, sourceOf, idOf) {
|
||||
var items = list || [];
|
||||
var title = titleOf(item) || " ";
|
||||
var ti = title.charAt(0).toUpperCase();
|
||||
var sameTitle = items.filter(function (o) { return (titleOf(o) || " ").charAt(0).toUpperCase() === ti; });
|
||||
if (sameTitle.length <= 1)
|
||||
return ti;
|
||||
var source = sourceOf(item) || " ";
|
||||
var pi = source.charAt(0).toUpperCase();
|
||||
var sameSource = sameTitle.filter(function (o) { return (sourceOf(o) || " ").charAt(0).toUpperCase() === pi; });
|
||||
if (sameSource.length <= 1)
|
||||
return pi + ti;
|
||||
sameSource.sort(function (a, b) { return idOf(a) < idOf(b) ? -1 : idOf(a) > idOf(b) ? 1 : 0; });
|
||||
for (var i = 0; i < sameSource.length; i++)
|
||||
if (sameSource[i] === item || idOf(sameSource[i]) === idOf(item))
|
||||
return pi + ti + (i + 1);
|
||||
return pi + ti;
|
||||
// Tile pictogram base path. The page lives at resources/web/dialog/SpeedDial/, so this climbs to
|
||||
// resources/images/ where the same SVG icons the native GUI controls use are shipped.
|
||||
var ICON_BASE = "../../../images/";
|
||||
|
||||
// SVG base name for an action's tile pictogram, or "" when it has none (commands without a GUI
|
||||
// icon, plugins). Pure so the node-vm test can exercise it.
|
||||
function actionIcon(a) {
|
||||
return (a && a.icon) ? a.icon : "";
|
||||
}
|
||||
|
||||
// Action tile code - see monogramFor for the escalation ladder. Settings are actions now, so
|
||||
// they share this ladder (title initial, then source, then a stable ordinal).
|
||||
function tileCode(action, actions) {
|
||||
return monogramFor(action, actions,
|
||||
function (o) { return o.title; },
|
||||
function (o) { return o.source; },
|
||||
function (o) { return o.id; });
|
||||
}
|
||||
|
||||
// Put an action's monogram into a tile (search row or favourites tile). A null action (a tab row
|
||||
// with no backing action) renders an empty tile.
|
||||
function fillTile(tile, a) {
|
||||
tile.textContent = a ? tileCode(a, ACTIONS) : "";
|
||||
// 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.
|
||||
function fillTile(tile, a, mono) {
|
||||
tile.textContent = "";
|
||||
var icon = actionIcon(a);
|
||||
if (!icon)
|
||||
return;
|
||||
var img = document.createElement("img");
|
||||
img.className = mono ? "tile-icon tab-mono" : "tile-icon";
|
||||
img.src = ICON_BASE + icon + ".svg";
|
||||
img.alt = "";
|
||||
img.setAttribute("aria-hidden", "true");
|
||||
tile.appendChild(img);
|
||||
}
|
||||
|
||||
// The active list for the main phase. A typed query ranks every action (commands/plugins/settings)
|
||||
@@ -461,13 +448,6 @@ function currentList() {
|
||||
return []; // percent - the input itself is the only field
|
||||
}
|
||||
|
||||
function hue(id) {
|
||||
var h = 0;
|
||||
for (var i = 0; i < id.length; i++)
|
||||
h = (h * 31 + id.charCodeAt(i)) >>> 0;
|
||||
return h % 360;
|
||||
}
|
||||
|
||||
// Build a <div class=className> with the search-match ranges wrapped in <mark>. Used for both the
|
||||
// title and the source eyebrow. Pure (only touches the document factory), so the node-vm test never
|
||||
// calls it and load-time stays DOM-free.
|
||||
@@ -528,7 +508,6 @@ function renderFav() {
|
||||
var a = byId(id);
|
||||
var tile = document.createElement("button");
|
||||
tile.className = "fav-tile" + (sel.zone === "fav" && sel.i === i ? " sel" : "");
|
||||
tile.style.setProperty("--h", hue(id));
|
||||
fillTile(tile, a);
|
||||
var tileBadge = modeBadge(a, USER_MODE);
|
||||
tile.title = a.title + (tileBadge ? " (" + tileBadge + ")" : "");
|
||||
@@ -629,7 +608,6 @@ function renderActionRow(a, i) {
|
||||
|
||||
var tile = document.createElement("div");
|
||||
tile.className = "tile";
|
||||
tile.style.setProperty("--h", hue(a.id));
|
||||
fillTile(tile, a);
|
||||
|
||||
var left = document.createElement("div");
|
||||
@@ -805,8 +783,7 @@ function renderTabRow(t, i) {
|
||||
|
||||
var tile = document.createElement("div");
|
||||
tile.className = "tile";
|
||||
tile.style.setProperty("--h", hue(t.id));
|
||||
fillTile(tile, null);
|
||||
fillTile(tile, t, true);
|
||||
|
||||
var left = document.createElement("div");
|
||||
left.className = "row-left";
|
||||
|
||||
@@ -195,21 +195,15 @@ assert.deepEqual(ctx.visibleFavourites(["b", "a", "b"], [{ id: "a" }, { id: "b"
|
||||
assert.deepEqual(ctx.visibleFavourites([], [{ id: "a" }]), [], "no pins renders an empty quick-bar");
|
||||
assert.deepEqual(ctx.visibleFavourites(["a"], []), [], "a stale config with no actions renders nothing");
|
||||
|
||||
// tileCode: monogram ladder - title initial, then title+source initials, then a stable ordinal
|
||||
// by id. The ordinal is keyed by id, not by list order, so frecency reshuffles never renumber tiles.
|
||||
const monoPool = [
|
||||
{ id: "z", title: "Repair", source: "Mesh Tools" },
|
||||
{ id: "a", title: "Repair", source: "Mesh Tools" },
|
||||
{ id: "b", title: "Repair", source: "Filament" }
|
||||
];
|
||||
assert.equal(ctx.tileCode(monoPool[0], monoPool), "MR2",
|
||||
"same title+source resolves to source+title initials with an id-keyed ordinal (id z sorts after id a)");
|
||||
assert.equal(ctx.tileCode(monoPool[1], monoPool), "MR1",
|
||||
"the earlier id is numbered first among same-title+source tiles");
|
||||
assert.equal(ctx.tileCode(monoPool[2], monoPool), "FR",
|
||||
"same title but different source resolves to source+title initials");
|
||||
assert.equal(ctx.tileCode({ id: "x", title: "Slice", source: "OrcaSlicer" }, [{ id: "x", title: "Slice", source: "OrcaSlicer" }]),
|
||||
"S", "a unique title resolves to the bare title initial");
|
||||
// actionIcon: the SVG base name for a tile's pictogram, or "" when the action has none (blank tile).
|
||||
assert.equal(ctx.actionIcon({ id: "x", title: "Slice", icon: "media_play" }), "media_play",
|
||||
"an action's icon base name is returned verbatim");
|
||||
assert.equal(ctx.actionIcon({ id: "x", title: "Go to tab...", icon: "" }), "",
|
||||
"an empty icon renders a blank tile");
|
||||
assert.equal(ctx.actionIcon({ id: "x", title: "Plugin action" }), "",
|
||||
"a missing icon field renders a blank tile");
|
||||
assert.equal(ctx.actionIcon(null), "",
|
||||
"a null action (tab row) renders a blank tile");
|
||||
|
||||
// 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");
|
||||
|
||||
@@ -60,9 +60,7 @@ body {
|
||||
border: 0;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
color: hsl(var(--h) var(--speed-tile-text-s, 72%) var(--speed-tile-text-l, 38%));
|
||||
font-weight: 700;
|
||||
/* why: mirror .tile centering - icons/placeholder are 18px glyphs, and a bare <button> inherits
|
||||
/* why: mirror .tile centering - icons/placeholder are 16px, and a bare <button> inherits
|
||||
13px + UA padding, which would clip them. inline-flex + pad:0 + overflow:hidden fits them. */
|
||||
font-size: 12px;
|
||||
padding: 0;
|
||||
@@ -310,9 +308,6 @@ body {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
border-radius: 6px;
|
||||
color: hsl(var(--h) var(--speed-tile-text-s, 72%) var(--speed-tile-text-l, 38%));
|
||||
font-weight: 700;
|
||||
font-size: 12px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -320,6 +315,20 @@ body {
|
||||
border: 1px solid var(--speed-tile-border, #d8d8d8);
|
||||
}
|
||||
|
||||
/* Native SVG pictogram in a tile; blank tiles (no icon) have no child. */
|
||||
.tile-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
display: block;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Tab-strip glyphs are drawn white for the dark tab bar; recolor to the shared #949494 gray so
|
||||
they read on both the light and dark tile backgrounds. */
|
||||
.tile-icon.tab-mono {
|
||||
filter: brightness(0) invert(.58);
|
||||
}
|
||||
|
||||
.row-left {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
|
||||
@@ -55,8 +55,6 @@
|
||||
/* Speed dial icon tile treatment */
|
||||
--speed-tile-bg: #efefef;
|
||||
--speed-tile-border: #d8d8d8;
|
||||
--speed-tile-text-s: 74%;
|
||||
--speed-tile-text-l: 34%;
|
||||
}
|
||||
|
||||
:root[data-orca-theme="dark"] {
|
||||
@@ -95,8 +93,6 @@
|
||||
/* Speed dial icon tile treatment */
|
||||
--speed-tile-bg: #34343b;
|
||||
--speed-tile-border: #50505a;
|
||||
--speed-tile-text-s: 82%;
|
||||
--speed-tile-text-l: 84%;
|
||||
}
|
||||
|
||||
/* Re-theme the shared common.css chrome through variables (replaces dark.css's
|
||||
|
||||
Reference in New Issue
Block a user