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
6 changes: 5 additions & 1 deletion style.css
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,11 @@ button {
fill: #ffffff !important;
}

#colorPickerButton.paint-mode-active {
border: 2px solid var(--paint-mode-color, #ffffff);
box-shadow: 0 0 0 2px rgba(81, 29, 145, 0.45);
}

.bigbutton svg {
filter: none; /* light mode: normal */
}
Expand Down Expand Up @@ -1229,4 +1234,3 @@ details summary:focus,
list-style: none;
}


5 changes: 5 additions & 0 deletions ui/colourpicker.css
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,11 @@
background-color: var(--color-button-bg-hover);
}

.color-picker-use.paint-mode-active {
border: 2px solid var(--paint-mode-color, #ffffff);
box-shadow: 0 0 0 2px rgba(81, 29, 145, 0.45);
}

.color-picker-cancel {
background-color: var(--color-button-primary);
color: white;
Expand Down
42 changes: 35 additions & 7 deletions ui/colourpicker.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,11 @@ class CustomColorPicker {
this.currentColor = options.color || "#ff0000";
this.onColorChange = options.onColorChange || (() => {});
this.onClose = options.onClose || (() => {});
this.onPaintButtonClick = options.onPaintButtonClick || (() => {});
this.targetElement = options.target || document.body;

this.isOpen = false;
this._isClosing = false;

// Eyedropper state
this._eyedropperActive = false;
Expand Down Expand Up @@ -396,6 +398,7 @@ class CustomColorPicker {
".current-color-display",
);
this.currentColorText = this.container.querySelector(".current-color-text");
this.useButton = this.container.querySelector(".color-picker-use");

// Lightness slider refs
this.lightSlider = this.container.querySelector(".lightness-slider");
Expand Down Expand Up @@ -763,9 +766,12 @@ class CustomColorPicker {
});

// Confirm / general keyboard handling on the container (Esc/Enter/Space)
this.container
.querySelector(".color-picker-footer")
.addEventListener("click", () => this.confirmColor());
if (this.useButton) {
this.useButton.addEventListener("click", (e) => {
e.preventDefault();
this.onPaintButtonClick(this.currentColor);
});
}
this.container.addEventListener("keydown", (e) => this.handleKeydown(e));

if (this.lightSlider) {
Expand Down Expand Up @@ -1042,7 +1048,7 @@ class CustomColorPicker {
// Is the Use button (or inside it)?
if (t.closest(".color-picker-use")) {
e.preventDefault();
this.confirmColor();
this.onPaintButtonClick(this.currentColor);
return;
}

Expand Down Expand Up @@ -1188,6 +1194,8 @@ class CustomColorPicker {
colorDisplay.style.backgroundColor = this.currentColor;
}

this.updatePaintModeButtonVisual(this._paintModeButtonActive === true);

// Sync inputs + sliders
this.updateCssInput();
this.updateRgbInputs();
Expand Down Expand Up @@ -1963,17 +1971,37 @@ class CustomColorPicker {
}

close() {
if (this._isClosing) return;
this._isClosing = true;

this.container.style.display = "none";
this.isOpen = false;
document.removeEventListener("click", this.outsideClickHandler, true);

this.updatePaintModeButtonVisual(false);

if (this.onClose) {
this.onClose();
}

this._isClosing = false;
}

updatePaintModeButtonVisual(isActive) {
this._paintModeButtonActive = isActive;
if (!this.useButton) return;

this.useButton.classList.toggle("paint-mode-active", isActive);
if (isActive) {
this.useButton.style.setProperty("--paint-mode-color", this.currentColor);
} else {
this.useButton.style.removeProperty("--paint-mode-color");
}
}

confirmColor() {
this.onColorChange(this.currentColor);
this.close();
if (this.onClose) {
setTimeout(() => this.onClose(), 100);
}
}
}

Expand Down
157 changes: 129 additions & 28 deletions ui/gizmos.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ let colorPickingCallback = null;
let colorPickingCircle = null;
let colorPickingCirclePosition = { x: 0, y: 0 };

let _onPickMeshRef = null;
let paintModeActive = false;
let paintModeExplicit = false;
let pickerContentElement = null;
let colorButtonLastPointerType = "mouse";
let pickerPointerMoveBound = false;


document.addEventListener("DOMContentLoaded", function () {
const colorButton = document.getElementById("colorPickerButton");

Expand Down Expand Up @@ -90,10 +98,15 @@ document.addEventListener("DOMContentLoaded", function () {
color: window.selectedColor,
onColorChange: (newColor) => {
window.selectedColor = newColor;
updatePaintToolVisualState();
},
onPaintButtonClick: () => {
paintModeExplicit = true;
activatePaintMode();
},
onClose: () => {
// After color picker closes, start mesh selection
pickMeshFromCanvas();
deactivatePaintMode({ clearExplicit: true });
detachPickerPointerTracking();
},
target: document.body,
});
Expand All @@ -103,46 +116,134 @@ document.addEventListener("DOMContentLoaded", function () {

// Attach click event to open custom color picker
if (colorButton) {
colorButton.addEventListener("pointerdown", (event) => {
colorButtonLastPointerType = event.pointerType || "mouse";
});

colorButton.addEventListener("click", (event) => {
event.preventDefault();
if (colorPicker) {
colorButtonLastPointerType =
colorButtonLastPointerType || detectPickerPointerType();
paintModeExplicit = false;
colorPicker.open(window.selectedColor);
bindPickerPointerTracking();
updatePaintToolVisualState();
}
});
}
});

let _onPickMeshRef = null;
function detectPickerPointerType() {
return window.matchMedia?.("(pointer: coarse)").matches ? "touch" : "mouse";
}

function onPaintMeshClick(event) {
if (!paintModeActive) return;

function pickMeshFromCanvas() {
const canvas = flock.scene.getEngine().getRenderingCanvas();
if (!canvas) return;

const onPickMesh = function (event) {
const canvasRect = canvas.getBoundingClientRect();

// Exit if outside canvas
if (eventIsOutOfCanvasBounds(event, canvasRect)) {
window.removeEventListener("click", onPickMesh);
endColorPickingMode();
// restore cursors
document.body.style.cursor = "default";
canvas.style.cursor = "auto";
return;
}
const canvasRect = canvas.getBoundingClientRect();
if (eventIsOutOfCanvasBounds(event, canvasRect)) return;

const [canvasX, canvasY] = getCanvasXAndCanvasYValues(event, canvasRect);
applyColorAtPosition(canvasX, canvasY);
document.body.style.cursor = "crosshair";
canvas.style.cursor = "crosshair";
};
const [canvasX, canvasY] = getCanvasXAndCanvasYValues(event, canvasRect);
applyColorAtPosition(canvasX, canvasY);
}

startColorPickingKeyboardMode(onPickMesh);
function activatePaintMode() {
if (paintModeActive) {
updatePaintToolVisualState();
return;
}

const canvas = flock.scene.getEngine().getRenderingCanvas();
if (!canvas) return;

paintModeActive = true;
startColorPickingKeyboardMode(onPaintMeshClick);
window.addEventListener("click", onPaintMeshClick);
document.body.style.cursor = "crosshair";
canvas.style.cursor = "crosshair";

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

function deactivatePaintMode({ clearExplicit = false } = {}) {
if (!paintModeActive && !clearExplicit) return;

paintModeActive = false;
if (clearExplicit) paintModeExplicit = false;

window.removeEventListener("click", onPaintMeshClick);
endColorPickingMode();

const canvas = flock.scene.getEngine().getRenderingCanvas();
document.body.style.cursor = "default";
if (canvas) {
canvas.style.cursor = "auto";
}

updatePaintToolVisualState();
}

function bindPickerPointerTracking() {
detachPickerPointerTracking();
if (!colorPicker?.container) return;

pickerContentElement = colorPicker.container.querySelector(".color-picker-content");
if (!pickerContentElement) return;

document.addEventListener("pointermove", handlePickerPointerMove, true);
pickerPointerMoveBound = true;
}

function detachPickerPointerTracking() {
if (pickerPointerMoveBound) {
document.removeEventListener("pointermove", handlePickerPointerMove, true);
pickerPointerMoveBound = false;
}

pickerContentElement = null;
}

function handlePickerPointerMove(event) {
if (!colorPicker?.isOpen) return;

if (event.pointerType && event.pointerType !== "mouse") return;

if (!pickerContentElement) return;

const rect = pickerContentElement.getBoundingClientRect();
const isInsidePicker =
event.clientX >= rect.left &&
event.clientX <= rect.right &&
event.clientY >= rect.top &&
event.clientY <= rect.bottom;

if (isInsidePicker) {
deactivatePaintMode();
return;
}

activatePaintMode();
}

function updatePaintToolVisualState() {
const colorPickerButton = document.getElementById("colorPickerButton");
if (!colorPickerButton) return;

const shouldShowActive = paintModeExplicit || paintModeActive;
colorPickerButton.classList.toggle("paint-mode-active", shouldShowActive);

if (shouldShowActive) {
colorPickerButton.style.setProperty("--paint-mode-color", window.selectedColor);
} else {
colorPickerButton.style.removeProperty("--paint-mode-color");
}

// Keep the in-dialog paint button neutral; active indicator belongs on the canvas tool button.
colorPicker?.updatePaintModeButtonVisual?.(false);
}

function applyColorAtPosition(canvasX, canvasY) {
Expand All @@ -160,10 +261,10 @@ function applyColorAtPosition(canvasX, canvasY) {
const pickedMesh = pickLeafFromRay(pickRay, scene);

if (pickedMesh) {
updateBlockColorAndHighlight(pickedMesh, selectedColor);
updateBlockColorAndHighlight(pickedMesh, window.selectedColor);
} else {
flock.setSky(selectedColor);
updateBlockColorAndHighlight(meshMap?.["sky"], selectedColor);
flock.setSky(window.selectedColor);
updateBlockColorAndHighlight(meshMap?.["sky"], window.selectedColor);
}
}

Expand Down