Refine BIM toolbar dock magnification
This commit is contained in:
parent
42629cb193
commit
f6ed7d18e0
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
|
|
|
||||||
|
|
@ -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`);
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue