Refine BIM toolbar dock magnification

This commit is contained in:
CODEX 2026-06-20 23:06:11 +03:00
parent 42629cb193
commit f6ed7d18e0
6 changed files with 390 additions and 18 deletions

View File

@ -90,6 +90,10 @@
--section-toolbar-left: calc(12px + var(--tb-size, 25px) + var(--tb-gap, 10px)); --section-toolbar-left: calc(12px + var(--tb-size, 25px) + var(--tb-gap, 10px));
--section-toolbar-bottom: 123px; --section-toolbar-bottom: 123px;
--tb-size: 25px; --tb-size: 25px;
--tb-min-size: 25px;
--tb-max-size: 56px;
--tb-lens-count: 7;
--tb-autohide: 0;
--tb-gap: 10px; --tb-gap: 10px;
--tb-radius: 7px; --tb-radius: 7px;
--tb-pad-y: 6px; --tb-pad-y: 6px;
@ -2998,17 +3002,41 @@ header {
.bottom-toolbar { .bottom-toolbar {
position: fixed; position: fixed;
left: 12px; left: 12px;
bottom: 12px; top: 50%;
display: flex; display: flex;
flex-direction: column-reverse; flex-direction: column-reverse;
align-items: flex-start;
gap: var(--tb-gap, 8px); gap: var(--tb-gap, 8px);
width: var(--tb-min-size, var(--tb-size, 25px));
padding: var(--tb-pad-y, 8px) 0; padding: var(--tb-pad-y, 8px) 0;
transform: translateY(-50%);
transform-origin: left center;
z-index: 15; z-index: 15;
opacity: 1;
transition: transform 220ms ease, opacity 180ms ease;
will-change: transform, opacity;
}
.bottom-toolbar.toolbar-dock-active {
width: var(--tb-max-size, var(--tb-min-size, 56px));
}
.bottom-toolbar.toolbar-autohide {
pointer-events: none;
opacity: 0;
transform: translate(calc(-1 * (var(--tb-max-size, 56px) + 24px)), -50%);
}
.bottom-toolbar.toolbar-autohide.is-visible {
pointer-events: auto;
opacity: 1;
transform: translateY(-50%);
} }
.tb-btn { .tb-btn {
width: var(--tb-size, 52px); width: var(--tb-min-size, var(--tb-size, 52px));
height: var(--tb-size, 52px); height: var(--tb-min-size, var(--tb-size, 52px));
flex: 0 0 auto;
border-radius: var(--tb-radius, 12px); border-radius: var(--tb-radius, 12px);
background: var(--toolbar-bg); background: var(--toolbar-bg);
border: 1px solid var(--toolbar-border); border: 1px solid var(--toolbar-border);
@ -3020,13 +3048,16 @@ header {
justify-content: center; justify-content: center;
padding: var(--tb-icon-pad, 10px); padding: var(--tb-icon-pad, 10px);
line-height: 1; line-height: 1;
transition: transform 120ms ease, border-color 120ms ease, box-shadow 120ms ease; transform: translateY(var(--tb-shift-y, 0px)) scale(var(--tb-scale, 1));
transform-origin: left center;
transition: border-color 120ms ease, box-shadow 120ms ease, background 120ms ease, color 120ms ease;
will-change: transform;
} }
.tb-btn svg { .tb-btn svg {
display: block; display: block;
width: var(--tb-svg-size, 24px); width: var(--tb-icon-size, 24px);
height: var(--tb-svg-size, 24px); height: var(--tb-icon-size, 24px);
fill: none; fill: none;
stroke: currentColor; stroke: currentColor;
stroke-width: 1.8; stroke-width: 1.8;
@ -3035,7 +3066,6 @@ header {
} }
.tb-btn:hover { .tb-btn:hover {
transform: translateY(-1px);
border-color: var(--toolbar-outline, rgba(76, 224, 210, 0.7)); border-color: var(--toolbar-outline, rgba(76, 224, 210, 0.7));
box-shadow: 0 10px 20px color-mix(in srgb, var(--toolbar-outline, rgba(76, 224, 210, 0.7)) 30%, transparent); box-shadow: 0 10px 20px color-mix(in srgb, var(--toolbar-outline, rgba(76, 224, 210, 0.7)) 30%, transparent);
} }
@ -3049,7 +3079,6 @@ header {
.tb-btn:disabled { .tb-btn:disabled {
cursor: default; cursor: default;
opacity: 0.45; opacity: 0.45;
transform: none;
box-shadow: none; box-shadow: none;
} }

View File

@ -2,13 +2,13 @@ const SDK_VERSION = "2.6.78"; // соответствует package.json
const SDK_URL = "./lib/dc-camera-context.es.js?v=3"; const SDK_URL = "./lib/dc-camera-context.es.js?v=3";
import { createLogo } from "./src/ui/logo.js"; import { createLogo } from "./src/ui/logo.js";
import { toolbarConfig, applyToolbarVars } from "./src/ui/toolbar.js"; import { applyToolbarVars } from "./src/ui/toolbar.js?v=2";
import { createTemplatesMenu } from "./src/ui/templatesMenu.js?v=3"; import { createTemplatesMenu } from "./src/ui/templatesMenu.js?v=3";
import { createSettingsMenu } from "./src/ui/settingsMenu.js?v=11"; import { createSettingsMenu } from "./src/ui/settingsMenu.js?v=12";
import { createMeasurementModal } from "./src/ui/measurementModal.js"; import { createMeasurementModal } from "./src/ui/measurementModal.js";
import { createGlassSelect } from "./src/ui/glassSelect.js"; import { createGlassSelect } from "./src/ui/glassSelect.js";
import { createSliderControl } from "./src/ui/controls/slider.js"; import { createSliderControl } from "./src/ui/controls/slider.js";
import "./theme.js"; import "./theme.js?v=2";
import { TransformGizmo } from "./transformGizmo.js"; import { TransformGizmo } from "./transformGizmo.js";
import { createObjectModal } from "./src/ui/objectModal.js"; import { createObjectModal } from "./src/ui/objectModal.js";
@ -2124,6 +2124,238 @@ const clampNumber = (value, min, max, fallback) => {
return Math.max(min, Math.min(max, num)); return Math.max(min, Math.min(max, num));
}; };
let toolbarDockHideTimer = null;
let toolbarDockAnimationFrame = null;
let toolbarDockLastTs = 0;
const toolbarDockState = new WeakMap();
const TOOLBAR_DOCK_BASE_SCALE = 2.25;
const TOOLBAR_DOCK_BASE_DISTANCE = 110;
const TOOLBAR_DOCK_BASE_NUDGE = 40;
const TOOLBAR_DOCK_NUDGE_TIGHTNESS = 0.82;
const TOOLBAR_DOCK_SPRING = {
mass: 0.1,
stiffness: 170,
damping: 12,
};
const TOOLBAR_DOCK_MAX_STEP = 1 / 240;
const getVisibleToolbarButtons = () => {
if (!bottomToolbar) return [];
return Array.from(bottomToolbar.querySelectorAll(".tb-btn")).filter((btn) => {
if (btn.hidden || btn.classList.contains("hidden")) return false;
return getComputedStyle(btn).display !== "none";
});
};
const readToolbarDockSettings = () => {
const minSize = clampNumber(parseFloat(cssValue("--tb-min-size", "25")), 18, 88, 25);
const maxSize = Math.max(minSize, clampNumber(parseFloat(cssValue("--tb-max-size", "56")), 18, 96, 56));
const rawLens = Math.round(clampNumber(parseFloat(cssValue("--tb-lens-count", "7")), 1, 15, 7));
const lensCount = rawLens % 2 === 0 ? rawLens + 1 : rawLens;
return {
minSize,
maxSize,
lensCount,
autoHide: cssValue("--tb-autohide", "0") === "1",
};
};
const getToolbarDockState = (btn) => {
let state = toolbarDockState.get(btn);
if (!state) {
state = {
scale: 1,
targetScale: 1,
scaleVelocity: 0,
shiftY: 0,
targetShiftY: 0,
shiftVelocity: 0,
};
toolbarDockState.set(btn, state);
}
return state;
};
const stepToolbarSpring = (value, velocity, target, dt) => {
let nextValue = value;
let nextVelocity = velocity;
const steps = Math.max(1, Math.ceil(dt / TOOLBAR_DOCK_MAX_STEP));
const step = dt / steps;
for (let i = 0; i < steps; i += 1) {
const displacement = nextValue - target;
const springForce = -TOOLBAR_DOCK_SPRING.stiffness * displacement;
const dampingForce = -TOOLBAR_DOCK_SPRING.damping * nextVelocity;
const acceleration = (springForce + dampingForce) / TOOLBAR_DOCK_SPRING.mass;
nextVelocity += acceleration * step;
nextValue += nextVelocity * step;
}
return [nextValue, nextVelocity];
};
const renderToolbarDockFrame = (timestamp = performance.now()) => {
const buttons = getVisibleToolbarButtons();
if (!buttons.length) {
toolbarDockAnimationFrame = null;
return;
}
const dt = toolbarDockLastTs ? Math.min(0.032, Math.max(0.001, (timestamp - toolbarDockLastTs) / 1000)) : 0.016;
toolbarDockLastTs = timestamp;
let active = false;
buttons.forEach((btn) => {
const state = getToolbarDockState(btn);
[state.scale, state.scaleVelocity] = stepToolbarSpring(state.scale, state.scaleVelocity, state.targetScale, dt);
[state.shiftY, state.shiftVelocity] = stepToolbarSpring(state.shiftY, state.shiftVelocity, state.targetShiftY, dt);
if (
Math.abs(state.scale - state.targetScale) < 0.001 &&
Math.abs(state.scaleVelocity) < 0.001 &&
Math.abs(state.shiftY - state.targetShiftY) < 0.05 &&
Math.abs(state.shiftVelocity) < 0.05
) {
state.scale = state.targetScale;
state.scaleVelocity = 0;
state.shiftY = state.targetShiftY;
state.shiftVelocity = 0;
} else {
active = true;
}
btn.style.setProperty("--tb-scale", state.scale.toFixed(4));
btn.style.setProperty("--tb-shift-y", `${state.shiftY.toFixed(2)}px`);
});
if (active) {
toolbarDockAnimationFrame = window.requestAnimationFrame(renderToolbarDockFrame);
} else {
toolbarDockAnimationFrame = null;
toolbarDockLastTs = 0;
}
};
const startToolbarDockAnimation = () => {
if (toolbarDockAnimationFrame) return;
toolbarDockLastTs = 0;
toolbarDockAnimationFrame = window.requestAnimationFrame(renderToolbarDockFrame);
};
const setToolbarButtonTarget = (btn, size, shiftY, settings) => {
const state = getToolbarDockState(btn);
state.targetScale = settings.minSize > 0 ? size / settings.minSize : 1;
state.targetShiftY = shiftY;
startToolbarDockAnimation();
};
const resetToolbarDockMagnification = () => {
const settings = readToolbarDockSettings();
bottomToolbar?.classList.remove("toolbar-dock-active");
getVisibleToolbarButtons().forEach((btn) => setToolbarButtonTarget(btn, settings.minSize, 0, settings));
if (sectionModeActive) {
positionSectionToolbar();
}
};
const updateToolbarDockMagnification = (clientY) => {
const buttons = getVisibleToolbarButtons();
if (!buttons.length) return;
const settings = readToolbarDockSettings();
const toolbarRect = bottomToolbar.getBoundingClientRect();
const entries = buttons.map((btn) => {
return {
btn,
centerY: toolbarRect.top + btn.offsetTop + btn.offsetHeight / 2,
};
}).sort((a, b) => a.centerY - b.centerY);
const gap = parseFloat(cssValue("--tb-gap", "10")) || 10;
const step = settings.minSize + gap;
const lensRadius = Math.max(
settings.minSize,
Math.floor((settings.lensCount - 1) / 2) * step + gap / 2
);
const maxScale = settings.minSize > 0 ? settings.maxSize / settings.minSize : 1;
const scaledNudge = TOOLBAR_DOCK_BASE_NUDGE *
(lensRadius / TOOLBAR_DOCK_BASE_DISTANCE) *
Math.max(0, maxScale - 1) / (TOOLBAR_DOCK_BASE_SCALE - 1) *
TOOLBAR_DOCK_NUDGE_TIGHTNESS;
entries.forEach((entry) => {
const distance = clientY - entry.centerY;
const absDistance = Math.abs(distance);
const scale = absDistance >= lensRadius
? 1
: 1 + (maxScale - 1) * (1 - absDistance / lensRadius);
const size = settings.minSize * scale;
const shiftY = absDistance > lensRadius
? Math.sign(distance || 1) * -1 * scaledNudge
: (-distance / lensRadius) * scaledNudge * scale;
setToolbarButtonTarget(entry.btn, size, shiftY, settings);
});
if (sectionModeActive) {
positionSectionToolbar();
}
};
const requestToolbarDockMagnification = (clientY) => {
updateToolbarDockMagnification(clientY);
};
const showToolbarDock = () => {
if (!bottomToolbar) return;
window.clearTimeout(toolbarDockHideTimer);
toolbarDockHideTimer = null;
bottomToolbar.classList.add("is-visible");
};
const scheduleToolbarDockHide = () => {
if (!bottomToolbar) return;
const settings = readToolbarDockSettings();
if (!settings.autoHide) return;
if (toolbarDockHideTimer) return;
toolbarDockHideTimer = window.setTimeout(() => {
toolbarDockHideTimer = null;
bottomToolbar.classList.remove("is-visible");
resetToolbarDockMagnification();
}, 5000);
};
const updateToolbarDockMode = () => {
if (!bottomToolbar) return;
const settings = readToolbarDockSettings();
bottomToolbar.classList.toggle("toolbar-autohide", settings.autoHide);
bottomToolbar.classList.toggle("is-visible", !settings.autoHide);
if (!settings.autoHide) {
window.clearTimeout(toolbarDockHideTimer);
toolbarDockHideTimer = null;
}
resetToolbarDockMagnification();
};
const setupToolbarDock = () => {
if (!bottomToolbar) return;
bottomToolbar.addEventListener("pointermove", (event) => {
showToolbarDock();
bottomToolbar.classList.add("toolbar-dock-active");
requestToolbarDockMagnification(event.clientY);
});
bottomToolbar.addEventListener("pointerenter", () => {
showToolbarDock();
bottomToolbar.classList.add("toolbar-dock-active");
});
bottomToolbar.addEventListener("pointerleave", () => {
resetToolbarDockMagnification();
scheduleToolbarDockHide();
});
window.addEventListener("pointermove", (event) => {
const settings = readToolbarDockSettings();
if (!settings.autoHide || isGuestMode()) return;
if (event.clientX <= Math.max(32, settings.maxSize * 0.75)) {
showToolbarDock();
} else if (!bottomToolbar.matches(":hover")) {
scheduleToolbarDockHide();
}
}, { passive: true });
window.addEventListener("resize", () => {
resetToolbarDockMagnification();
updateToolbarDockMode();
});
updateToolbarDockMode();
};
const hexToRgb01 = (hex) => { const hexToRgb01 = (hex) => {
if (!hex) return [1, 1, 1]; if (!hex) return [1, 1, 1];
const clean = hex.trim(); const clean = hex.trim();
@ -4097,7 +4329,8 @@ const buildProjectSnapshot = async (name) => {
const applyDesignState = (design) => { const applyDesignState = (design) => {
if (!design || !settingsMenu?.applyState) return; if (!design || !settingsMenu?.applyState) return;
settingsMenu.applyState(design); settingsMenu.applyState(design);
applyToolbarVars(); applyToolbarVars(settingsMenu?.getState?.());
updateToolbarDockMode();
applyHighlightTheme(); applyHighlightTheme();
applyMeasureTheme(); applyMeasureTheme();
if (sdkRef) { if (sdkRef) {
@ -4481,7 +4714,8 @@ const initViewer = async () => {
rebuildNavCube(sdk); rebuildNavCube(sdk);
resetTreeView(sdk); resetTreeView(sdk);
applyToolbarVars(); applyToolbarVars(settingsMenu?.getState?.());
updateToolbarDockMode();
applyHighlightTheme(); applyHighlightTheme();
updatePanelsVisibility(); updatePanelsVisibility();
ensureCameraControlActive(); ensureCameraControlActive();
@ -5330,6 +5564,10 @@ const loadProjectSnapshot = async (project) => {
if (key && key.startsWith("commentTarget")) { if (key && key.startsWith("commentTarget")) {
renderCommentTargets(); renderCommentTargets();
} }
if (key && key.startsWith("toolbar")) {
applyToolbarVars(settingsMenu?.getState?.());
updateToolbarDockMode();
}
focusCanvasAndControls(); focusCanvasAndControls();
}, },
onShow: () => { onShow: () => {
@ -5343,6 +5581,8 @@ const loadProjectSnapshot = async (project) => {
} }
}); });
// ensure persisted settings are applied to cube/highlight on first load // ensure persisted settings are applied to cube/highlight on first load
applyToolbarVars(settingsMenu?.getState?.());
updateToolbarDockMode();
rebuildNavCube(sdk); rebuildNavCube(sdk);
applyHighlightTheme(); applyHighlightTheme();
applyMeasureTheme(); applyMeasureTheme();
@ -5944,6 +6184,7 @@ const loadProjectSnapshot = async (project) => {
// нижняя тулбар // нижняя тулбар
if (bottomToolbar) { if (bottomToolbar) {
setupToolbarDock();
bottomToolbar.addEventListener("click", (e) => { bottomToolbar.addEventListener("click", (e) => {
const btn = e.target.closest(".tb-btn"); const btn = e.target.closest(".tb-btn");
if (!btn) return; if (!btn) return;

View File

@ -9,7 +9,7 @@
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Manrope:wght@500;600;700&display=swap"> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Manrope:wght@500;600;700&display=swap">
<script src="./theme.js"></script> <script src="./theme.js?v=2"></script>
<script> <script>
// Apply persisted theme ASAP to avoid initial flash of defaults // Apply persisted theme ASAP to avoid initial flash of defaults
(function () { (function () {
@ -143,6 +143,11 @@
set("--toolbar-bg-active-contrast", s.toolbarBgActive ? readableTextColor(s.toolbarBgActive) : undefined); set("--toolbar-bg-active-contrast", s.toolbarBgActive ? readableTextColor(s.toolbarBgActive) : undefined);
set("--toolbar-border", s.toolbarBorder); set("--toolbar-border", s.toolbarBorder);
set("--toolbar-outline", s.toolbarOutline); set("--toolbar-outline", s.toolbarOutline);
set("--tb-size", s.toolbarMinSize !== undefined ? `${s.toolbarMinSize}px` : undefined);
set("--tb-min-size", s.toolbarMinSize !== undefined ? `${s.toolbarMinSize}px` : undefined);
set("--tb-max-size", s.toolbarMaxSize !== undefined ? `${s.toolbarMaxSize}px` : undefined);
set("--tb-lens-count", s.toolbarLensCount);
set("--tb-autohide", s.toolbarAutoHide ? "1" : "0");
set("--modal-bg", s.modalBg); set("--modal-bg", s.modalBg);
set("--modal-bg-alpha", s.modalBgAlpha !== undefined ? `${s.modalBgAlpha * 100}%` : undefined); set("--modal-bg-alpha", s.modalBgAlpha !== undefined ? `${s.modalBgAlpha * 100}%` : undefined);
set("--modal-focus", s.modalFocus); set("--modal-focus", s.modalFocus);
@ -198,7 +203,7 @@
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Manrope:wght@500;600;700&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Manrope:wght@500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="./dcViewer.css?v=32"> <link rel="stylesheet" href="./dcViewer.css?v=34">
</head> </head>
<body> <body>
<header> <header>
@ -636,6 +641,6 @@
<input id="fileInput" class="hidden" type="file" <input id="fileInput" class="hidden" type="file"
accept=".xkt,.glb,.gltf,.bim,.las,.laz,.obj,.stl"> accept=".xkt,.glb,.gltf,.bim,.las,.laz,.obj,.stl">
<script type="module" src="./dcViewer.js?v=45"></script> <script type="module" src="./dcViewer.js?v=48"></script>
</body> </body>
</html> </html>

View File

@ -24,6 +24,10 @@ const defaultSettings = {
toolbarBgActive: "#c4ff67", toolbarBgActive: "#c4ff67",
toolbarBorder: "#292929", toolbarBorder: "#292929",
toolbarOutline: "#292929", toolbarOutline: "#292929",
toolbarMinSize: 25,
toolbarMaxSize: 56,
toolbarLensCount: 7,
toolbarAutoHide: false,
modalBg: "#1c1c1c", modalBg: "#1c1c1c",
modalBgAlpha: 1, modalBgAlpha: 1,
modalFocus: "#292929", modalFocus: "#292929",
@ -213,6 +217,17 @@ const applySettings = (s) => {
root.style.setProperty("--toolbar-bg-active-contrast", readableTextColor(s.toolbarBgActive)); root.style.setProperty("--toolbar-bg-active-contrast", readableTextColor(s.toolbarBgActive));
root.style.setProperty("--toolbar-border", s.toolbarBorder); root.style.setProperty("--toolbar-border", s.toolbarBorder);
root.style.setProperty("--toolbar-outline", s.toolbarOutline); root.style.setProperty("--toolbar-outline", s.toolbarOutline);
const toolbarMinRaw = Number(s.toolbarMinSize ?? defaultSettings.toolbarMinSize);
const toolbarMaxRaw = Number(s.toolbarMaxSize ?? defaultSettings.toolbarMaxSize);
const toolbarLensRaw = Number(s.toolbarLensCount ?? defaultSettings.toolbarLensCount);
const toolbarMinSize = clamp(Number.isFinite(toolbarMinRaw) ? toolbarMinRaw : defaultSettings.toolbarMinSize, 18, 88);
const toolbarMaxSize = Math.max(toolbarMinSize, clamp(Number.isFinite(toolbarMaxRaw) ? toolbarMaxRaw : defaultSettings.toolbarMaxSize, 18, 96));
const toolbarLensCount = Math.max(1, Math.min(15, Math.round(Number.isFinite(toolbarLensRaw) ? toolbarLensRaw : defaultSettings.toolbarLensCount)));
root.style.setProperty("--tb-size", `${toolbarMinSize}px`);
root.style.setProperty("--tb-min-size", `${toolbarMinSize}px`);
root.style.setProperty("--tb-max-size", `${toolbarMaxSize}px`);
root.style.setProperty("--tb-lens-count", `${toolbarLensCount % 2 === 0 ? toolbarLensCount + 1 : toolbarLensCount}`);
root.style.setProperty("--tb-autohide", s.toolbarAutoHide ? "1" : "0");
const bgAlphaPct = `${(s.modalBgAlpha ?? 1) * 100}%`; const bgAlphaPct = `${(s.modalBgAlpha ?? 1) * 100}%`;
const focusAlphaPct = `${(s.modalFocusAlpha ?? 1) * 100}%`; const focusAlphaPct = `${(s.modalFocusAlpha ?? 1) * 100}%`;
root.style.setProperty("--modal-bg", s.modalBg); root.style.setProperty("--modal-bg", s.modalBg);
@ -442,6 +457,24 @@ export function createSettingsMenu({ panel, controls = {}, onChange = null, onSh
return input; return input;
}; };
const addBoolean = (parent, label, key) => {
const row = document.createElement("label");
row.className = "checkbox-row";
const text = document.createElement("span");
text.className = "checkbox-row__label";
text.textContent = label;
const chk = document.createElement("input");
chk.type = "checkbox";
chk.checked = !!state[key];
chk.addEventListener("change", () => {
setVal(key, chk.checked);
});
row.appendChild(text);
row.appendChild(chk);
parent.appendChild(row);
return chk;
};
const font = section("Фон"); const font = section("Фон");
addColor(font, "Фон внешний", "bgOuter"); addColor(font, "Фон внешний", "bgOuter");
addColor(font, "Фон холста верх", "canvasTop"); addColor(font, "Фон холста верх", "canvasTop");
@ -484,6 +517,49 @@ export function createSettingsMenu({ panel, controls = {}, onChange = null, onSh
addColor(toolbar, "Актив", "toolbarBgActive"); addColor(toolbar, "Актив", "toolbarBgActive");
addColor(toolbar, "Бордер", "toolbarBorder"); addColor(toolbar, "Бордер", "toolbarBorder");
addColor(toolbar, "Outline", "toolbarOutline"); addColor(toolbar, "Outline", "toolbarOutline");
addSettingsSlider({
parent: toolbar,
label: "Минимальный размер",
value: Math.max(18, Math.min(42, Number(state.toolbarMinSize ?? defaultSettings.toolbarMinSize))),
min: 18,
max: 42,
step: 1,
formatValue: (val) => `${Math.round(val)}px`,
onChange: (val) => {
const next = Math.max(18, Math.min(42, Math.round(Number(val))));
setVal("toolbarMinSize", next);
if (Number(state.toolbarMaxSize) < next) {
setVal("toolbarMaxSize", next);
}
},
});
addSettingsSlider({
parent: toolbar,
label: "Максимальный размер",
value: Math.max(28, Math.min(88, Number(state.toolbarMaxSize ?? defaultSettings.toolbarMaxSize))),
min: 28,
max: 88,
step: 1,
formatValue: (val) => `${Math.round(val)}px`,
onChange: (val) => {
const minSize = Math.max(18, Math.min(42, Number(state.toolbarMinSize ?? defaultSettings.toolbarMinSize)));
setVal("toolbarMaxSize", Math.max(minSize, Math.min(88, Math.round(Number(val)))));
},
});
addSettingsSlider({
parent: toolbar,
label: "Линза",
value: Math.max(1, Math.min(13, Number(state.toolbarLensCount ?? defaultSettings.toolbarLensCount))),
min: 1,
max: 13,
step: 2,
formatValue: (val) => `${Math.round(val)} иконок`,
onChange: (val) => {
const next = Math.max(1, Math.min(13, Math.round(Number(val))));
setVal("toolbarLensCount", next % 2 === 0 ? next + 1 : next);
},
});
addBoolean(toolbar, "Автоскрытие", "toolbarAutoHide");
body.appendChild(toolbar); body.appendChild(toolbar);
const navCube = section("NavCube"); const navCube = section("NavCube");

View File

@ -1,4 +1,8 @@
const toolbarConfig = { const toolbarConfig = {
minSize: 25,
maxSize: 56,
lensCount: 7,
autoHide: false,
size: 25, size: 25,
gap: 10, gap: 10,
radius: 7, radius: 7,
@ -7,9 +11,22 @@ const toolbarConfig = {
iconSize: 13, iconSize: 13,
}; };
const applyToolbarVars = () => { const numericSetting = (settings, key, fallback) => {
const value = Number(settings?.[key]);
return Number.isFinite(value) ? value : fallback;
};
const applyToolbarVars = (settings = {}) => {
const root = document.documentElement; const root = document.documentElement;
root.style.setProperty("--tb-size", `${toolbarConfig.size}px`); const minSize = numericSetting(settings, "toolbarMinSize", toolbarConfig.minSize);
const maxSize = Math.max(minSize, numericSetting(settings, "toolbarMaxSize", toolbarConfig.maxSize));
const lensCount = Math.max(1, Math.round(numericSetting(settings, "toolbarLensCount", toolbarConfig.lensCount)));
const autoHide = settings?.toolbarAutoHide ?? toolbarConfig.autoHide;
root.style.setProperty("--tb-size", `${minSize}px`);
root.style.setProperty("--tb-min-size", `${minSize}px`);
root.style.setProperty("--tb-max-size", `${maxSize}px`);
root.style.setProperty("--tb-lens-count", `${lensCount}`);
root.style.setProperty("--tb-autohide", autoHide ? "1" : "0");
root.style.setProperty("--tb-gap", `${toolbarConfig.gap}px`); root.style.setProperty("--tb-gap", `${toolbarConfig.gap}px`);
root.style.setProperty("--tb-radius", `${toolbarConfig.radius}px`); root.style.setProperty("--tb-radius", `${toolbarConfig.radius}px`);
root.style.setProperty("--tb-pad-y", `${toolbarConfig.paddingY}px`); root.style.setProperty("--tb-pad-y", `${toolbarConfig.paddingY}px`);

View File

@ -17,6 +17,10 @@ const PROJECT_THEME = {
toolbarBgActive: "#c4ff67", toolbarBgActive: "#c4ff67",
toolbarBorder: "#292929", toolbarBorder: "#292929",
toolbarOutline: "#292929", toolbarOutline: "#292929",
toolbarMinSize: 25,
toolbarMaxSize: 56,
toolbarLensCount: 7,
toolbarAutoHide: false,
modalBg: "#1c1c1c", modalBg: "#1c1c1c",
modalBgAlpha: 1, modalBgAlpha: 1,
modalFocus: "#292929", modalFocus: "#292929",