Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions ui/colourpicker.css
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,25 @@
z-index: 1000;
}


.color-picker-drag-handle {
display: flex;
align-items: center;
justify-content: center;
margin: -6px -6px 8px;
padding: 4px 0;
color: #511d91;
cursor: move;
user-select: none;
touch-action: none;
}

.color-picker-drag-grip {
font-size: 14px;
line-height: 1;
letter-spacing: 2px;
opacity: 0.75;
}
.color-picker-tools-row {
display: flex;
align-items: center;
Expand Down
100 changes: 91 additions & 9 deletions ui/colourpicker.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ class CustomColorPicker {
this.targetElement = options.target || document.body;

this.isOpen = false;
this.userMovedPicker = false;
this.paintModeActiveFromOutside = false;

// Eyedropper state
this._eyedropperActive = false;
Expand Down Expand Up @@ -298,6 +300,9 @@ class CustomColorPicker {
this.container.innerHTML = `
<div class="color-picker-backdrop"></div>
<div class="color-picker-content">
<div class="color-picker-drag-handle" aria-label="${translate('move_color_picker') || 'Move color picker'}" title="${translate('move_color_picker') || 'Move color picker'}">
<span class="color-picker-drag-grip" aria-hidden="true">⋮⋮</span>
</div>
<div class="color-picker-body">
<div class="color-picker-left">
<div class="color-wheel-section">
Expand Down Expand Up @@ -406,6 +411,7 @@ class CustomColorPicker {
// Palette UI refs
this.paletteSelect = this.container.querySelector("#palette-select");
this.paletteGrid = this.container.querySelector(".color-palette");
this.dragHandle = this.container.querySelector(".color-picker-drag-handle");

// Build dropdown options + render default swatches before events bind
this._initPaletteUI();
Expand All @@ -422,6 +428,7 @@ class CustomColorPicker {
this.drawColorWheel();
this.drawHueSlider();
this.setupLightnessCanvasScaling();
this.makePopupDraggable();
}

_initPaletteUI() {
Expand Down Expand Up @@ -456,8 +463,8 @@ class CustomColorPicker {
const label = translate(c.name) || c.hex;
const hex = c.hex;
return `
<button
class="color-swatch"
<button
class="color-swatch"
style="background-color: ${hex}"
data-color="${hex}"
title="${label}"
Expand Down Expand Up @@ -638,14 +645,16 @@ class CustomColorPicker {
// Close on backdrop click
const backdrop = this.container.querySelector(".color-picker-backdrop");
if (backdrop) {
backdrop.addEventListener("click", () => this.close());
backdrop.addEventListener("click", () => {
this.activatePaintModeWithCurrentColor();
});
}

// Click outside to close (guarded during eyedropper)
this.outsideClickHandler = (e) => {
if (this._eyedropperActive) return; // don't close while eyedropper overlay is up
if (this.isOpen && !this.container.contains(e.target)) {
this.close();
this.activatePaintModeWithCurrentColor();
}
};

Expand Down Expand Up @@ -1774,17 +1783,18 @@ class CustomColorPicker {

open(color = this.currentColor) {
disableGizmos();

// Show first so layout has real sizes
this.container.style.display = "block";
this.container.style.opacity = "1";
this.container.style.pointerEvents = "auto";
this.isOpen = true;
this.paintModeActiveFromOutside = false;

// --- Positioning (unchanged) ---
const colorButton = document.getElementById("colorPickerButton");
const canvasArea = document.getElementById("canvasArea");
if (colorButton && canvasArea) {
if (colorButton && canvasArea && !this.userMovedPicker) {
const buttonRect = colorButton.getBoundingClientRect();
const canvasRect = canvasArea.getBoundingClientRect();

Expand Down Expand Up @@ -1962,19 +1972,91 @@ class CustomColorPicker {
}
}

close() {
close(options = {}) {
const { commitColor = false, triggerOnClose = false } = options;
if (commitColor) {
this.onColorChange(this.currentColor);
}
this.container.style.display = "none";
this.isOpen = false;
this.paintModeActiveFromOutside = false;
document.removeEventListener("click", this.outsideClickHandler, true);

if (triggerOnClose && this.onClose) {
setTimeout(() => this.onClose(), 100);
}
}

confirmColor() {
this.close({ commitColor: true, triggerOnClose: true });
}

activatePaintModeWithCurrentColor() {
this.onColorChange(this.currentColor);
this.close();
if (this.onClose) {

if (!this.paintModeActiveFromOutside && this.onClose) {
this.paintModeActiveFromOutside = true;
setTimeout(() => this.onClose(), 100);
}
}

makePopupDraggable() {
if (!this.dragHandle) return;

let dragging = false;
let dragOffsetX = 0;
let dragOffsetY = 0;

const updatePosition = (clientX, clientY) => {
const parent = this.container.parentElement;
if (!parent) return;

const parentRect = parent.getBoundingClientRect();
const pickerRect = this.container.getBoundingClientRect();

const minX = 0;
const minY = 0;
const maxX = Math.max(0, parentRect.width - pickerRect.width);
const maxY = Math.max(0, parentRect.height - pickerRect.height);

const nextX = Math.min(Math.max(clientX - parentRect.left - dragOffsetX, minX), maxX);
const nextY = Math.min(Math.max(clientY - parentRect.top - dragOffsetY, minY), maxY);

this.container.style.left = `${nextX}px`;
this.container.style.top = `${nextY}px`;
this.container.style.right = "auto";
this.container.style.marginLeft = "0";
this.container.style.marginRight = "0";
this.userMovedPicker = true;
};

const onPointerMove = (e) => {
if (!dragging) return;
e.preventDefault();
updatePosition(e.clientX, e.clientY);
};

const onPointerUp = (e) => {
dragging = false;
try {
this.dragHandle.releasePointerCapture?.(e.pointerId);
} catch {}
document.removeEventListener("pointermove", onPointerMove);
document.removeEventListener("pointerup", onPointerUp);
};

this.dragHandle.addEventListener("pointerdown", (e) => {
if (e.button !== 0) return;
e.preventDefault();
const pickerRect = this.container.getBoundingClientRect();
dragOffsetX = e.clientX - pickerRect.left;
dragOffsetY = e.clientY - pickerRect.top;
dragging = true;
this.dragHandle.setPointerCapture?.(e.pointerId);
document.addEventListener("pointermove", onPointerMove);
document.addEventListener("pointerup", onPointerUp);
});
}
}

// Make it global for testing
Expand Down
11 changes: 8 additions & 3 deletions ui/gizmos.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,15 @@ let _onPickMeshRef = null;

function pickMeshFromCanvas() {
const canvas = flock.scene.getEngine().getRenderingCanvas();
let ignoreActivationClick = true;

const onPickMesh = function (event) {
// Ignore exactly one click: the one that triggered paint mode activation.
if (ignoreActivationClick) {
ignoreActivationClick = false;
return;
}

const canvasRect = canvas.getBoundingClientRect();

// Exit if outside canvas
Expand All @@ -140,9 +147,7 @@ function pickMeshFromCanvas() {
document.body.style.cursor = "crosshair";
canvas.style.cursor = "crosshair";

setTimeout(() => {
window.addEventListener("click", onPickMesh);
}, 200);
window.addEventListener("click", onPickMesh);
}

function applyColorAtPosition(canvasX, canvasY) {
Expand Down