diff --git a/css/dialogs.css b/css/dialogs.css
index a36b1cfd..98df387e 100644
--- a/css/dialogs.css
+++ b/css/dialogs.css
@@ -19,6 +19,7 @@
position: relative;
cursor: pointer;
overflow: hidden;
+ touch-action: none;
padding-left: 8px;
padding-top: 2px;
background: var(--color-elevated);
@@ -1311,16 +1312,20 @@
}
#overlay_message_box > div {
margin-top: 64px;
- width: 400px;
+ width: 460px;
margin-left: auto;
margin-right: auto;
}
+ #overlay_message_box > div > div {
+ margin-bottom: 15px;
+ margin-top: 32px;
+ }
#overlay_message_box > div > p {
margin-bottom: 20px;
}
#overlay_message_box h3 i {
- vertical-align: sub;
- margin: 8px;
+ vertical-align: bottom;
+ margin: 3px 10px;
font-size: 1.2em;
}
diff --git a/css/panels.css b/css/panels.css
index 64045061..9682e6ac 100644
--- a/css/panels.css
+++ b/css/panels.css
@@ -2309,6 +2309,7 @@ span.controller_state_section_info {
position: relative;
overflow: hidden;
scrollbar-color: var(--color-selected) var(--color-background);
+ touch-action: none;
}
#uv_frame {
height: 320px;
@@ -2318,6 +2319,7 @@ span.controller_state_section_info {
border: 4px solid var(--color-frame);
box-shadow: 0 0 0 1800px var(--color-background);
box-sizing: content-box;
+ touch-action: none;
--color-uv-unselected: var(--color-grid);
--color-uv-selected: white;
--color-uv-hover: var(--color-accent);
@@ -2338,6 +2340,7 @@ span.controller_state_section_info {
#uv_frame.overlay_mode .uv_face,
#uv_frame.overlay_mode .uv_face * {
pointer-events: none;
+ touch-action: none;
}
body[mode=paint] #uv_frame,
@@ -2402,6 +2405,7 @@ span.controller_state_section_info {
margin: calc(var(--radius) * -1px);
position: absolute;
pointer-events: none;
+ touch-action: none;
mix-blend-mode: difference;
z-index: 1;
}
@@ -2415,6 +2419,7 @@ span.controller_state_section_info {
margin: calc(var(--radius) * -1px);
position: absolute;
pointer-events: none;
+ touch-action: none;
mix-blend-mode: difference;
z-index: 1;
}
diff --git a/index.html b/index.html
index ea753322..51f0b078 100644
--- a/index.html
+++ b/index.html
@@ -70,16 +70,6 @@
-
-
-
keyboardkeybindings.recording
-
keybindings.press
-
-
-
-
-
-
diff --git a/js/interface/actions.js b/js/interface/actions.js
index 3c6b9d70..567cec24 100644
--- a/js/interface/actions.js
+++ b/js/interface/actions.js
@@ -651,7 +651,7 @@ export class NumSlider extends Widget {
this.interval = data.getInterval;
}
if (this.keybind) {
- this.keybind.shift = null;
+ //this.keybind.shift = null;
this.keybind.label = this.keybind.getText();
}
this.addSubKeybind('increase',
diff --git a/js/interface/dialog.ts b/js/interface/dialog.ts
index 2f1097f6..2ca37675 100644
--- a/js/interface/dialog.ts
+++ b/js/interface/dialog.ts
@@ -1,8 +1,9 @@
import { Blockbench } from "../api"
import { Prop } from "../misc"
-import { FormElementOptions, FormResultValue, InputForm, InputFormConfig } from "./form"
+import { FormResultValue, InputForm, InputFormConfig } from "./form"
import { Vue } from './../lib/libs'
import { getStringWidth } from "../util/util"
+import { dragHelper } from "../util/drag_helper"
interface ActionInterface {
name: string
@@ -94,6 +95,34 @@ function buildToolbars(dialog: Dialog) {
dialog_content.append(toolbar.node);
}
}
+function makeDraggable(dialog: Dialog | MessageBox, handle: HTMLElement) {
+ dialog.object.classList.add('draggable');
+ dialog.object.style.position = 'absolute';
+
+ let style = dialog.object.style;
+ let wrapper = Interface.page_wrapper;
+ let bounds = [
+ wrapper.offsetLeft+2,
+ wrapper.offsetTop+2,
+ wrapper.clientWidth + wrapper.offsetLeft+2,
+ wrapper.clientHeight + wrapper.offsetTop+2,
+ ]
+ handle.addEventListener('pointerdown', (e1) => {
+ let start_x: number, start_y: number;
+ dragHelper(e1, {
+ onStart() {
+ start_x = dialog.object.offsetLeft;
+ start_y = dialog.object.offsetTop;
+ },
+ onMove(context) {
+ let x = Math.clamp(start_x + context.delta.x, bounds[0], bounds[2] - dialog.object.clientWidth);
+ let y = Math.clamp(start_y + context.delta.y, bounds[1], bounds[3] - dialog.object.clientHeight);
+ style.left = x + 'px';
+ style.top = y + 'px';
+ }
+ })
+ });
+}
const toggle_sidebar = window.innerWidth < 640;
interface DialogSidebarOptions {
@@ -597,13 +626,7 @@ export class Dialog {
})
//Draggable
if (this.draggable !== false) {
- jq_dialog.addClass('draggable')
- // @ts-ignore Draggable library doesn't have types
- jq_dialog.draggable({
- handle: ".dialog_handle",
- containment: '#page_wrapper'
- })
- jq_dialog.css('position', 'absolute')
+ makeDraggable(this, handle);
}
if (this.resizable) {
this.object.classList.add('resizable')
@@ -921,9 +944,11 @@ export class MessageBox extends Dialog {
if (!options.message) options.message = tl('message.'+options.translateKey+'.message')
}
let content = Interface.createElement('div', {class: 'dialog_content'});
+ let handle = Interface.createElement('div', {class: 'dialog_handle'}, Interface.createElement('div', {class: 'dialog_title'}, tl(options.title)));
+ let close_button = Interface.createElement('div', {class: 'dialog_close_button', onclick: 'Dialog.open.cancel()'}, Blockbench.getIconNode('clear'))
this.object = Interface.createElement('dialog', {class: 'dialog', style: 'width: auto;', id: 'message_box'}, [
- Interface.createElement('div', {class: 'dialog_handle'}, Interface.createElement('div', {class: 'dialog_title'}, tl(options.title))),
- Interface.createElement('div', {class: 'dialog_close_button', onclick: 'Dialog.open.cancel()'}, Blockbench.getIconNode('clear')),
+ handle,
+ close_button,
content
]);
let jq_dialog = $(this.object);
@@ -1017,13 +1042,7 @@ export class MessageBox extends Dialog {
//Draggable
if (this.draggable !== false) {
- jq_dialog.addClass('draggable')
- // @ts-ignore
- jq_dialog.draggable({
- handle: ".dialog_handle",
- containment: '#page_wrapper'
- })
- this.object.style.position = 'absolute';
+ makeDraggable(this, handle);
}
let x = (window.innerWidth-540)/2
diff --git a/js/interface/keyboard.js b/js/interface/keyboard.js
index 4f9a50d1..8d48224b 100644
--- a/js/interface/keyboard.js
+++ b/js/interface/keyboard.js
@@ -1,6 +1,8 @@
import blender from '../../keymaps/blender.bbkeymap';
import cinema4d from '../../keymaps/cinema4d.bbkeymap';
import maya from '../../keymaps/maya.bbkeymap';
+import { dragHelper } from '../util/drag_helper';
+import { PointerTarget } from './pointer_target';
import { BARS } from './toolbars';
window.KeymapPresets = {
@@ -211,6 +213,10 @@ export class Keybind {
case 44: return 'printscreen';
case 19: return 'pause';
case 1001: return 'mousewheel';
+ case 1010: return 'slide_lmb_horizontal';
+ case 1011: return 'slide_lmb_vertical';
+ case 1012: return 'slide_rmb_horizontal';
+ case 1013: return 'slide_rmb_vertical';
case 106: return tl('keys.numpad', ['*']);
case 107: return tl('keys.numpad', ['+']);
@@ -251,20 +257,25 @@ export class Keybind {
}
return this;
}
- isTriggered(event) {
+ isTriggered(event, input_type) {
let modifiers_used = new Set();
if (this.variations) {
for (let option in this.variations) {
modifiers_used.add(this.variations[option].replace('unless_', ''));
}
}
- return (
- (this.key === event.which || (this.key == 1001 && event instanceof MouseEvent)) &&
- (this.ctrl === (event.ctrlKey || Pressing.overrides.ctrl) || this.ctrl === null || modifiers_used.has('ctrl') ) &&
- (this.shift === (event.shiftKey || Pressing.overrides.shift)|| this.shift === null || modifiers_used.has('shift') ) &&
- (this.alt === (event.altKey || Pressing.overrides.alt) || this.alt === null || modifiers_used.has('alt') ) &&
- (this.meta === event.metaKey || this.meta === null || modifiers_used.has('ctrl') )
- )
+ if ( !(this.ctrl === (event.ctrlKey || Pressing.overrides.ctrl) || this.ctrl === null || modifiers_used.has('ctrl') ) ) return false;
+ if ( !(this.shift === (event.shiftKey || Pressing.overrides.shift)|| this.shift === null || modifiers_used.has('shift') ) ) return false;
+ if ( !(this.alt === (event.altKey || Pressing.overrides.alt) || this.alt === null || modifiers_used.has('alt') ) ) return false;
+ if ( !(this.meta === event.metaKey || this.meta === null || modifiers_used.has('ctrl') ) ) return false;
+
+ if (this.key == event.which) return true;
+ if (this.key == 1001 && event instanceof MouseEvent) return true;
+ if (this.key >= 1010 && this.key < 1018 && input_type == 'pointer_slide') {
+ if ((this.key == 1010 || this.key == 1011) && event.which == 1) return true;
+ if ((this.key == 1012 || this.key == 1013) && event.which == 3) return true;
+ }
+ return false;
}
additionalModifierTriggered(event, variation) {
if (!this.variations) return;
@@ -286,20 +297,80 @@ export class Keybind {
}
}
record() {
- var scope = this;
+ let scope = this;
Keybinds.recording = this;
- var overlay = $('#overlay_message_box').show()
- var top = limitNumber(window.innerHeight/2 - 200, 30, 800)
- overlay.find('> div').css('margin-top', top+'px')
- function onActivate(event) {
- if (event.originalEvent) event = event.originalEvent;
+ let button_cancel = Interface.createElement('button', { '@click'() {
+ scope.stopRecording();
+ }}, tl('dialog.cancel'));
+ let button_empty = Interface.createElement('button', {'@click'() {
+ scope.clear().stopRecording();
+ }}, tl('keybindings.clear'));
+
+ let ui = Interface.createElement('div', {id: 'overlay_message_box'}, [
+ Interface.createElement('div', {}, [
+ Interface.createElement('h3', {}, [
+ Blockbench.getIconNode('keyboard'), tl('keybindings.recording')
+ ]),
+ Interface.createElement('p', {}, tl('keybindings.press')),
+ button_cancel, ' ', button_empty,
+ ])
+ ]);
+
+ if (BarItems[this.action] instanceof NumSlider) {
+ let slide_options = {
+ 'slide_lmb_horizontal': 1010,
+ 'slide_lmb_vertical': 1011,
+ 'slide_rmb_horizontal': 1012,
+ 'slide_rmb_vertical': 1013,
+ };
+ let list = Interface.createElement('div');
+ button_cancel.parentElement.insertBefore(list, button_cancel);
+
+ for (let key in slide_options) {
+ let button = Interface.createElement('button', { class: 'minor', '@click'(event) {
+
+ clearListeners();
+
+ scope.key = slide_options[key];
+ scope.ctrl = event.ctrlKey;
+ scope.shift = event.shiftKey;
+ scope.alt = event.altKey;
+ scope.meta = event.metaKey;
+
+ scope.label = scope.getText();
+ scope.save(true);
+ Blockbench.showQuickMessage(scope.label);
+
+ scope.stopRecording();
+
+ }}, [
+ Blockbench.getIconNode(key.endsWith('horizontal') ? 'arrow_range' : 'height'),
+ tl('keys.' + key)
+ ]);
+ list.append(button);
+ }
+ }
+
+ document.getElementById('dialog_wrapper').append(ui);
+ var overlay = $(ui);
+ var top = limitNumber(window.innerHeight/2 - 200, 30, 800)
+ overlay.find('> div').css('margin-top', top+'px');
+ function clearListeners() {
+
document.removeEventListener('keyup', onActivate)
document.removeEventListener('keydown', onActivateDown)
overlay.off('mousedown', onActivate)
overlay.off('wheel', onActivate)
overlay.off('keydown keypress keyup click click dblclick mouseup mousewheel', preventDefault)
+ }
+
+ function onActivate(event) {
+ if (event.originalEvent) event = event.originalEvent;
+
+ clearListeners();
+
if (event instanceof KeyboardEvent == false && event.target && event.target.tagName === 'BUTTON') return;
if (event instanceof WheelEvent) {
@@ -336,9 +407,8 @@ export class Keybind {
return this;
}
stopRecording() {
- Keybinds.recording = false
- $('#overlay_message_box').hide().off('mousedown mousewheel')
- $('#keybind_input_box').off('keyup keydown')
+ Keybinds.recording = false;
+ document.getElementById('overlay_message_box')?.remove();
return this;
}
toString() {
@@ -610,7 +680,7 @@ addEventListeners(document, 'keydown mousedown', function(e) {
alt.select()
Toolbox.original = orig
}
- } else if (Keybinds.extra.cancel.keybind.isTriggered(e) && (Transformer.dragging)) {
+ } else if (Keybinds.extra.cancel.keybind.isTriggered(e) && PointerTarget.active == PointerTarget.types.gizmo_transform) {
Transformer.cancelMovement(e, false);
updateSelection();
} else if (KnifeToolContext.current) {
@@ -766,6 +836,44 @@ $(document).keyup(function(e) {
}
})
+document.addEventListener('pointerdown', (e1) => {
+
+ let slider = Keybinds.actions.find(slider => {
+ if (slider instanceof NumSlider == false) return false;
+ if (!Condition(slider.condition)) return false;
+ return slider.keybind.isTriggered(e1, 'pointer_slide');
+ });
+ if (slider) {
+ let success = PointerTarget.requestTarget(PointerTarget.types.global_drag_slider);
+ if (!success) return;
+ dragHelper(e1, {
+
+ onStart(context) {
+ if (typeof slider.onBefore === 'function') {
+ slider.onBefore();
+ }
+ slider.sliding = true;
+ slider.pre = 0;
+ slider.sliding_start_pos = 0;
+ },
+ onMove(context) {
+ let distance = Math.abs(context.delta.x) > Math.abs(context.delta.y)
+ ? context.delta.x
+ : context.delta.y;
+ slider.slide(distance, context.event);
+ Preview.selected.mousemove(e1);
+ },
+ onEnd(context) {
+ Blockbench.setStatusBarText();
+ delete slider.sliding;
+ if (typeof slider.onAfter === 'function') {
+ slider.onAfter(slider.value - slider.last_value)
+ }
+ }
+ })
+ }
+}, {capture: true})
+
Object.assign(window, {
Keybind,
diff --git a/js/interface/pointer_target.ts b/js/interface/pointer_target.ts
new file mode 100644
index 00000000..845fb9c4
--- /dev/null
+++ b/js/interface/pointer_target.ts
@@ -0,0 +1,42 @@
+
+export type PointerTargetType = {
+ priority: number
+}
+
+/**
+ * Tracks which function the pointer / pointer drag is currently targeting. E. g. painting, using gizmos etc.
+ */
+export class PointerTarget {
+ static active: PointerTargetType | null = null;
+ static types = {
+ navigate: {
+ priority: 0
+ },
+ paint: {
+ priority: 1
+ },
+ gizmo_transform: {
+ priority: 2
+ },
+ global_drag_slider: {
+ priority: 3
+ }
+ };
+ static requestTarget(target: PointerTargetType): boolean {
+ if (PointerTarget.active && PointerTarget.active.priority > target.priority) {
+ return false;
+ } else {
+ PointerTarget.active = target;
+ return true;
+ }
+ }
+ static endTarget(): void {
+ PointerTarget.active = null;
+ }
+ static hasMinPriority(priority: number) {
+ return PointerTarget.active && PointerTarget.active.priority >= priority;
+ }
+}
+Object.assign(window, {
+ PointerTarget
+})
diff --git a/js/modeling/mesh_editing.js b/js/modeling/mesh_editing.js
index c2b0b21f..51ade210 100644
--- a/js/modeling/mesh_editing.js
+++ b/js/modeling/mesh_editing.js
@@ -3,6 +3,7 @@ import { ProportionalEdit } from './mesh/proportional_edit';
import './mesh/set_vertex_weights'
import './mesh/loop_cut'
import { sameMeshEdge } from './mesh/util';
+import { PointerTarget } from '../interface/pointer_target';
export class KnifeToolContext {
/**
@@ -1791,9 +1792,9 @@ BARS.defineActions(function() {
if (!data || !data.type) return;
if (data.event instanceof TouchEvent) {
// Stop controls on mobile
- Transformer.dragging = true;
+ PointerTarget.requestTarget(PointerTarget.types.gizmo_transform);
function onTouchEnd() {
- Transformer.dragging = false;
+ PointerTarget.endTarget();
document.removeEventListener('touchend', onTouchEnd);
}
document.addEventListener('touchend', onTouchEnd);
diff --git a/js/modeling/transform_gizmo.js b/js/modeling/transform_gizmo.js
index 6b113700..d4c72acd 100644
--- a/js/modeling/transform_gizmo.js
+++ b/js/modeling/transform_gizmo.js
@@ -3,6 +3,7 @@
* modified for Blockbench by jannisx11
*/
+import { PointerTarget } from "../interface/pointer_target";
import { getPivotObjects, getRotationObjects, getSelectedMovingElements, moveElementsInSpace } from "./transform";
( function () {
@@ -284,6 +285,9 @@ import { getPivotObjects, getRotationObjects, getSelectedMovingElements, moveEle
};
}
+ get dragging() {
+ return PointerTarget.active == PointerTarget.types.gizmo_transform;
+ }
};
THREE.TransformGizmo.prototype.update = function ( rotation, eye ) {
@@ -1544,7 +1548,7 @@ import { getPivotObjects, getRotationObjects, getSelectedMovingElements, moveEle
scope.was_clicked = true;
if ( scope.axis == "C1" || scope.axis == "C2" || scope.axis == "J" ) {
// Spline Gizmos cannot and should not trigger draggin states.
- scope.dragging = false;
+ PointerTarget.endTarget();
SplineGizmos.selectSplinePoints(scope);
SplineGizmos.hideOtherGizmos(_gizmo, _mode);
@@ -1559,7 +1563,7 @@ import { getPivotObjects, getRotationObjects, getSelectedMovingElements, moveEle
_dragging = false;
return;
}
- scope.dragging = true
+ PointerTarget.requestTarget(PointerTarget.types.gizmo_transform);
document.addEventListener( "touchend", onPointerUp, {passive: true} );
document.addEventListener( "touchcancel", onPointerUp, {passive: true} );
document.addEventListener( "touchleave", onPointerUp, {passive: true} );
@@ -2130,7 +2134,7 @@ import { getPivotObjects, getRotationObjects, getSelectedMovingElements, moveEle
function onPointerUp( event, keep_changes = true ) {
//event.preventDefault(); // Prevent MouseEvent on mobile
document.removeEventListener( "mouseup", onPointerUp );
- scope.dragging = false
+ PointerTarget.endTarget();
scope.was_clicked = false;
document.removeEventListener( "mousemove", onPointerMove );
diff --git a/js/outliner/outliner.js b/js/outliner/outliner.js
index 3f2fd4be..a08e0bf1 100644
--- a/js/outliner/outliner.js
+++ b/js/outliner/outliner.js
@@ -3,6 +3,7 @@ import StateMemory from "../util/state_memory"
import { OutlinerNode } from "./abstract/outliner_node"
import { OutlinerElement } from "./abstract/outliner_element"
import { radToDeg } from "three/src/math/MathUtils"
+import { PointerTarget } from "../interface/pointer_target"
export const Outliner = {
ROOT: 'root',
@@ -1117,7 +1118,7 @@ BARS.defineActions(function() {
keybind: new Keybind({key: 'i'}),
condition: {modes: ['edit', 'paint']},
click() {
- if (Painter.painting) return;
+ if (PointerTarget.hasMinPriority(2)) return;
let enabled = !Project.only_hidden_elements;
if (Project.only_hidden_elements) {
diff --git a/js/preview/OrbitControls.js b/js/preview/OrbitControls.js
index dae9b013..a9543115 100644
--- a/js/preview/OrbitControls.js
+++ b/js/preview/OrbitControls.js
@@ -1,3 +1,5 @@
+import { PointerTarget } from "../interface/pointer_target";
+
/**
* Original source: https://github.com/mrdoob/three.js, MIT
* Modified for Blockbench
@@ -606,7 +608,7 @@ constructor ( object, preview ) {
function onMouseDown( event ) {
- if (scope.isEnabled() === false || Transformer.dragging) return;
+ if (scope.isEnabled() === false || !PointerTarget.requestTarget(PointerTarget.types.navigate)) return;
event.preventDefault();
scope.hasMoved = false
@@ -652,7 +654,7 @@ constructor ( object, preview ) {
function onMouseMove( event ) {
- if (scope.isEnabled() === false || Transformer.dragging) return;
+ if (scope.isEnabled() === false || !PointerTarget.requestTarget(PointerTarget.types.navigate)) return;
event.preventDefault();
scope.hasMoved = true
@@ -754,7 +756,7 @@ constructor ( object, preview ) {
function onTouchMove( event ) {
if ( scope.isEnabled() === false ) return;
- if ( Transformer.dragging || Painter.painting ) return;
+ if ( !PointerTarget.requestTarget(PointerTarget.types.navigate) ) return;
event.preventDefault();
event.stopPropagation();
diff --git a/js/preview/canvas.js b/js/preview/canvas.js
index f5b13a40..a37f3c10 100644
--- a/js/preview/canvas.js
+++ b/js/preview/canvas.js
@@ -245,13 +245,12 @@ export const Canvas = {
var side_grid = new THREE.Object3D()
if (settings.full_grid.value === true) {
- //Grid
let size = settings.large_grid_size.value*block_size;
var grid = new THREE.GridHelper(size, size/canvasGridSize(), Canvas.gridMaterial);
if (Format.centered_grid) {
grid.position.set(0,0,0)
} else {
- grid.position.set(8,0,8)
+ grid.position.set(block_size/2,0,block_size/2)
}
grid.name = 'grid'
three_grid.add(grid)
@@ -264,7 +263,7 @@ export const Canvas = {
if (Format.centered_grid) {
north_mark.position.set(0,0, -3 - size/2)
} else {
- north_mark.position.set(8, 0, 5 - size/2)
+ north_mark.position.set(block_size/2, 0, 5 - size/2)
}
north_mark.rotation.x = Math.PI / -2
three_grid.add(north_mark)
@@ -277,7 +276,7 @@ export const Canvas = {
if (Format.centered_grid) {
grid.position.set(0,0,0)
} else {
- grid.position.set(8,0,8)
+ grid.position.set(block_size/2,0,block_size/2)
}
grid.name = 'grid'
three_grid.add(grid)
@@ -291,7 +290,7 @@ export const Canvas = {
if (Format.centered_grid) {
grid.position.set(0,0,0)
} else {
- grid.position.set(8,0,8)
+ grid.position.set(block_size/2,0,block_size/2)
}
grid.name = 'grid'
three_grid.add(grid)
@@ -304,7 +303,7 @@ export const Canvas = {
if (Format.centered_grid) {
north_mark.position.set(0,0,-0.6*north_size - block_size/2);
} else {
- north_mark.position.set(8,0,-0.6*north_size);
+ north_mark.position.set(block_size/2,0,-0.6*north_size);
}
north_mark.rotation.x = Math.PI / -2
three_grid.add(north_mark)
@@ -336,7 +335,7 @@ export const Canvas = {
Canvas.side_grids.x.name = 'side_grid_x'
Canvas.side_grids.x.visible = !Modes.display;
Canvas.side_grids.x.rotation.z = Math.PI/2;
- Canvas.side_grids.x.position.y = Format.centered_grid ? 8 : 0;
+ Canvas.side_grids.x.position.y = Format.centered_grid ? block_size/2 : 0;
Canvas.side_grids.z.position.z = 0
Canvas.side_grids.x.children.forEach(el => {
el.layers.set(1)
@@ -347,7 +346,7 @@ export const Canvas = {
Canvas.side_grids.z.visible = !Modes.display;
Canvas.side_grids.z.rotation.z = Math.PI/2;
Canvas.side_grids.z.rotation.y = Math.PI/2
- Canvas.side_grids.z.position.y = Format.centered_grid ? 8 : 0;
+ Canvas.side_grids.z.position.y = Format.centered_grid ? block_size/2 : 0;
Canvas.side_grids.z.position.z = 0
Canvas.side_grids.z.children.forEach(el => {
el.layers.set(3)
diff --git a/js/preview/preview.js b/js/preview/preview.js
index d4a52a61..07b80c17 100644
--- a/js/preview/preview.js
+++ b/js/preview/preview.js
@@ -5,6 +5,7 @@ import { ConfigDialog } from '../interface/dialog';
import { toSnakeCase } from '../util/util';
import { electron, ipcRenderer } from '../native_apis';
import { Pressing } from '../misc';
+import { PointerTarget } from '../interface/pointer_target';
window.scene = null;
window.main_preview = null;
@@ -340,7 +341,7 @@ export class Preview {
this.raycaster = new THREE.Raycaster();
this.mouse = new THREE.Vector2();
- addEventListeners(this.canvas, 'mousedown touchstart', event => { this.click(event)}, { passive: false })
+ addEventListeners(this.canvas, 'pointerdown', event => { this.click(event)}, { passive: false })
addEventListeners(this.canvas, 'mousemove touchmove', event => {
if (!this.static_rclick) return;
convertTouchEvent(event);
@@ -349,7 +350,10 @@ export class Preview {
this.static_rclick = false;
}
}, false)
- addEventListeners(this.canvas, 'mousemove touchmove', event => { this.mousemove(event)}, false)
+ addEventListeners(this.canvas, 'mousemove touchmove', event => {
+ if (PointerTarget.active == PointerTarget.types.global_drag_slider) return;
+ this.mousemove(event)
+ }, false)
addEventListeners(this.canvas, 'mouseup touchend', event => { this.mouseup(event)}, false)
addEventListeners(this.canvas, 'dblclick', event => { if (settings.double_click_switch_tools.value) Toolbox.toggleTransforms(event); }, false)
addEventListeners(this.canvas, 'mouseenter touchstart', event => { this.occupyTransformer(event)}, false)
@@ -830,7 +834,7 @@ export class Preview {
}
//Controls
click(event) {
- event.preventDefault();
+ //event.preventDefault();
$(':focus').blur();
if (open_menu) open_menu.hide();
unselectInterface(event);
@@ -847,7 +851,7 @@ export class Preview {
Transformer.dispatchPointerHover(event);
}
if (Transformer.hoverAxis !== null) return;
- let is_canvas_click = Keybinds.extra.preview_select.keybind.key == event.which || event.which === 0 || (Modes.paint && Keybinds.extra.paint_secondary_color.keybind.isTriggered(event));
+ let is_canvas_click = Keybinds.extra.preview_select.keybind.key == event.which || event.button === 0 || (Modes.paint && Keybinds.extra.paint_secondary_color.keybind.isTriggered(event));
var data = is_canvas_click && this.raycast(event);
if (data) {
diff --git a/js/texturing/painter.js b/js/texturing/painter.js
index d0989649..7a10acbb 100644
--- a/js/texturing/painter.js
+++ b/js/texturing/painter.js
@@ -1,3 +1,4 @@
+import { PointerTarget } from "../interface/pointer_target";
import { clipboard, nativeImage } from "../native_apis";
import { Dynamic2DMap } from "../util/dynamic_2d_map";
@@ -119,8 +120,8 @@ export const Painter = {
Painter.startPaintTool(texture, x, y, data.element.faces[data.face].uv, e, data)
if (Toolbox.selected.id !== 'color_picker') {
- addEventListeners(document, 'mousemove touchmove', Painter.movePaintToolCanvas, false );
- addEventListeners(document, 'mouseup touchend', Painter.stopPaintToolCanvas, false );
+ addEventListeners(document, 'pointermove', Painter.movePaintToolCanvas, false );
+ addEventListeners(document, 'pointerup', Painter.stopPaintToolCanvas, false );
}
},
movePaintToolCanvas(event, data) {
@@ -168,8 +169,8 @@ export const Painter = {
}
},
stopPaintToolCanvas() {
- removeEventListeners(document, 'mousemove touchmove', Painter.movePaintToolCanvas, false );
- removeEventListeners(document, 'mouseup touchend', Painter.stopPaintToolCanvas, false );
+ removeEventListeners(document, 'pointermove', Painter.movePaintToolCanvas, false );
+ removeEventListeners(document, 'pointerup', Painter.stopPaintToolCanvas, false );
Painter.stopPaintTool();
},
getMeshUVIsland(fkey, face) {
@@ -188,6 +189,10 @@ export const Painter = {
Painter.paint_stroke_canceled = true;
return;
}
+ if (!PointerTarget.requestTarget(PointerTarget.types.paint)) {
+ Painter.paint_stroke_canceled = true;
+ return;
+ }
if (Toolbox.selected.brush && Toolbox.selected.brush.onStrokeStart) {
let result = Toolbox.selected.brush.onStrokeStart({texture, x, y, uv: uvTag, event, raycast_data: data});
if (result == false) {
@@ -208,8 +213,8 @@ export const Painter = {
undo_aspects.textures = [texture];
}
Undo.initEdit(undo_aspects);
+ Painter.current.start_event = event;
Painter.brushChanges = false;
- Painter.painting = true;
if (Toolbox.selected.id === 'draw_shape_tool' || Toolbox.selected.id === 'gradient_tool') {
Painter.current = {
@@ -258,6 +263,7 @@ export const Painter = {
movePaintTool(texture, x, y, event, new_face, uv) {
// Called directly from movePaintToolCanvas and moveBrushUV
if (Painter.paint_stroke_canceled) return;
+ if (!PointerTarget.requestTarget(PointerTarget.types.paint)) return;
if (Toolbox.selected.brush && Toolbox.selected.brush.onStrokeMove) {
let result = Toolbox.selected.brush.onStrokeMove({texture, x, y, uv, event, raycast_data: data});
@@ -320,7 +326,8 @@ export const Painter = {
delete Painter.current.textures;
delete Painter.current.uv_rects;
delete Painter.current.uv_islands;
- Painter.painting = false;
+ delete Painter.current.dynamic_brush_size;
+ PointerTarget.endTarget();
Painter.currentPixel = [-1, -1];
},
// Tools
@@ -429,10 +436,12 @@ export const Painter = {
},
useBrush(texture, ctx, x, y, event) {
- var color = tinycolor(ColorPanel.get(Keybinds.extra.paint_secondary_color.keybind.isTriggered(event))).toRgb();
+ let use_2nd_color = Keybinds.extra.paint_secondary_color.keybind.isTriggered(Painter.current.start_event ?? event);
+ var color = tinycolor(ColorPanel.get(use_2nd_color)).toRgb();
var size = BarItems.slider_brush_size.get();
let softness = BarItems.slider_brush_softness.get()/100;
- let b_opacity = BarItems.slider_brush_opacity.get()/255;
+ let max_opacity = BarItems.slider_brush_opacity.get()/255;
+ let b_opacity = max_opacity;
let tool = Toolbox.selected;
let matrix_id = Painter.current.element
? (Painter.current.element.uuid + Painter.current.face)
@@ -454,23 +463,34 @@ export const Painter = {
}
}
}
+ let pressure;
+ let angle;
if (event.touches && event.touches[0] && event.touches[0].touchType == 'stylus' && event.touches[0].force !== undefined) {
// Stylus
var touch = event.touches[0];
+ pressure = touch.force;
+ angle = touch.altitudeAngle;
+
+ } else if (event.pressure >= 0 && event.pressure <= 1 && (event.pressure < 1 || event.pointerType != 'touch') && event.pressure !== 0.5) {
+ pressure = event.pressure;
+ angle = event.altitudeAngle;
+ }
- if (settings.brush_opacity_modifier.value == 'pressure' && touch.force !== undefined) {
- b_opacity = Math.clamp(b_opacity * Math.clamp(touch.force*1.25, 0, 1), 0, 100);
+ if (pressure !== undefined) {
+ if (settings.brush_opacity_modifier.value == 'pressure' && pressure !== undefined) {
+ b_opacity = Math.clamp(b_opacity * Math.clamp(pressure*1.25, 0, 1), 0, 100);
- } else if (settings.brush_opacity_modifier.value == 'tilt' && touch.altitudeAngle !== undefined) {
- var modifier = Math.clamp(0.5 / (touch.altitudeAngle + 0.3), 0, 1);
+ } else if (settings.brush_opacity_modifier.value == 'tilt' && angle !== undefined) {
+ var modifier = Math.clamp(0.5 / (angle + 0.3), 0, 1);
b_opacity = Math.clamp(b_opacity * modifier, 0, 100);
}
- if (settings.brush_size_modifier.value == 'pressure' && touch.force !== undefined) {
- size = Math.clamp(touch.force * size * 2, 1, 20);
+ if (settings.brush_size_modifier.value == 'pressure' && pressure !== undefined) {
+ size = Math.clamp(pressure * size * 2, 1, 20);
- } else if (settings.brush_size_modifier.value == 'tilt' && touch.altitudeAngle !== undefined) {
- size *= Math.clamp(1.5 / (touch.altitudeAngle + 0.3), 1, 4);
+ } else if (settings.brush_size_modifier.value == 'tilt' && angle !== undefined) {
+ size *= Math.clamp(1.5 / (angle + 0.3), 1, 4);
}
+ Painter.current.dynamic_brush_size = size;
}
if (tool.brush.draw) {
@@ -485,7 +505,7 @@ export const Painter = {
return pxcolor;
}
}
- return tool.brush.changePixel(px, py, pxcolor, local_opacity, {color, opacity: b_opacity, ctx, x, y, size, softness, texture, event});
+ return tool.brush.changePixel(px, py, pxcolor, local_opacity, {color, opacity: b_opacity, max_opacity, ctx, x, y, size, softness, texture, event});
}
let shape = BarItems.brush_shape.value;
if (shape == 'square') {
@@ -2367,7 +2387,7 @@ BARS.defineActions(function() {
pixel_perfect: true,
floor_coordinates: () => BarItems.slider_brush_softness.get() == 0,
get interval() {
- let size = BarItems.slider_brush_size.get();
+ let size = Painter.current.dynamic_brush_size ?? BarItems.slider_brush_size.get();
if (size > 40) {
return size / 12;
} else {
@@ -2409,7 +2429,7 @@ BARS.defineActions(function() {
let target = Math.lerp(before, opacity??1, local_opacity);
if (target > before) Painter.setAlphaMatrix(texture, px, py, target);
- a = target;
+ a = Math.max(target, before);
}
}
let result_color;
@@ -2634,7 +2654,8 @@ BARS.defineActions(function() {
offset_even_radius: true,
floor_coordinates: () => BarItems.slider_brush_softness.get() == 0,
get interval() {
- return 1 + BarItems.slider_brush_size.get() * BarItems.slider_brush_softness.get() / 1500;
+ let size = Painter.current.dynamic_brush_size ?? BarItems.slider_brush_size.get();
+ return 1 + size * BarItems.slider_brush_softness.get() / 1500;
},
changePixel(px, py, pxcolor, local_opacity, {opacity, ctx, x, y, size, softness, texture, event}) {
if (Painter.lock_alpha) return pxcolor;
diff --git a/js/texturing/uv.js b/js/texturing/uv.js
index c4696fbb..adb6c9d7 100644
--- a/js/texturing/uv.js
+++ b/js/texturing/uv.js
@@ -132,15 +132,18 @@ export const UVEditor = {
if (event.offsetY >= event.target.clientHeight) return;
}
Painter.startPaintTool(texture, coords.x, coords.y, undefined, event);
- addEventListeners(UVEditor.vue.$refs.viewport, 'mousemove touchmove', UVEditor.movePaintTool, false );
- addEventListeners(document, 'mouseup touchend', UVEditor.stopBrush, false );
+ addEventListeners(UVEditor.vue.$refs.viewport, 'pointermove', UVEditor.movePaintTool, false );
+ addEventListeners(document, 'pointerup', UVEditor.stopBrush, false );
}
},
movePaintTool(event) {
+ if (event.pointerType === 'pen' && event.pressure === 0) {
+ return;
+ }
var texture = UVEditor.getTexture()
if (!texture) {
Blockbench.showQuickMessage('message.untextured')
- } else if (event.which === 1 || Keybinds.extra.paint_secondary_color.keybind.isTriggered(event) || (event.touches && event.touches.length == 1)) {
+ } else if (event.which <= 1 || event.pointerType === 'pen' || Keybinds.extra.paint_secondary_color.keybind.isTriggered(event) || (event.touches && event.touches.length == 1)) {
var new_face;
var {x, y} = UVEditor.getBrushCoordinates(event, texture);
if (texture.img.naturalWidth + texture.img.naturalHeight == 0) return;
@@ -155,6 +158,8 @@ export const UVEditor = {
x = Painter.current.x + (delta[0] / distance) * rounded_distance;
y = Painter.current.y + (delta[1] / distance) * rounded_distance;
}
+ UVEditor.vue.mouse_coords.x = x;
+ UVEditor.vue.mouse_coords.y = y;
if (Painter.current.face !== UVEditor.getSelectedFaces(null)[0]) {
Painter.current.x = x
Painter.current.y = y
@@ -170,8 +175,8 @@ export const UVEditor = {
}
},
stopBrush(event) {
- removeEventListeners( UVEditor.vue.$refs.viewport, 'mousemove touchmove', UVEditor.movePaintTool, false );
- removeEventListeners( document, 'mouseup touchend', UVEditor.stopBrush, false );
+ removeEventListeners( UVEditor.vue.$refs.viewport, 'pointermove', UVEditor.movePaintTool, false );
+ removeEventListeners( document, 'pointerup', UVEditor.stopBrush, false );
if (Toolbox.selected.id !== 'selection_tool') {
Painter.stopPaintTool()
} else {
@@ -2377,6 +2382,7 @@ Interface.definePanels(function() {
texture: 0,
layer: null,
mouse_coords: {x: -1, y: -1, active: false, line_preview: false},
+ touches_count: 0,
last_brush_position: [0, 0],
copy_brush_source: null,
helper_lines: {x: -1, y: -1},
@@ -2688,19 +2694,17 @@ Interface.definePanels(function() {
return false;
}
},
- onMouseDown(event) {
+ onTouchStart(event) {
setActivePanel('uv');
+ this.touches_count = event.touches?.length;
let scope = this;
let second_touch;
let original_zoom = this.zoom;
- let original_margin = scope.getFrameMargin();
- let offset = $(scope.$refs.viewport).offset();
+ let original_margin = this.getFrameMargin();
+ let offset = $(this.$refs.viewport).offset();
UVEditor.total_zoom_offset = [6, 6];
- if (event.which === 2 ||
- (Keybinds.extra.preview_drag.keybind.isTriggered(event) && !event.which == 1) ||
- (event.touches && !Toolbox.selected.paintTool && event.target.id == 'uv_frame')
- ) {
- // Drag
+ if (event.touches && !Toolbox.selected.paintTool && event.target.id == 'uv_frame') {
+ // Drag (touch only)
if (event.touches) {
event.clientX = event.touches[0].clientX;
event.clientY = event.touches[0].clientY;
@@ -2712,7 +2716,7 @@ Interface.definePanels(function() {
viewport.scrollLeft - 5,
viewport.scrollTop - 5
];
- function dragMouseWheel(e2) {
+ function touchPan(e2) {
if (e2.touches) {
e2.clientX = e2.touches[0].clientX;
e2.clientY = e2.touches[0].clientY;
@@ -2745,20 +2749,69 @@ Interface.definePanels(function() {
&& (viewport.scrollTop == margin[1] || viewport.scrollTop == margin_center[1]);
UVEditor.updateUVNavigator();
}
+ function touchPanStop(e) {
+ document.removeEventListener('touchmove', touchPan);
+ document.removeEventListener('touchend', touchPanStop);
+ if (e.which == 3 && Math.pow(viewport.scrollLeft - original[0], 2) + Math.pow(viewport.scrollTop - original[1], 2) > 50) {
+ preventContextMenu();
+ }
+ }
+ document.addEventListener('touchmove', touchPan);
+ document.addEventListener('touchend', touchPanStop);
+ event.preventDefault();
+ $(getFocusedTextInput()).trigger('blur');
+ return false;
+ }
+ },
+ onTouchEnd(event) {
+ this.touches_count = event.touches.length;
+ },
+ onPointerDown(event) {
+ if (this.touches_count) return;
+ setActivePanel('uv');
+ UVEditor.total_zoom_offset = [6, 6];
+ if (event.which === 2 ||
+ (Keybinds.extra.preview_drag.keybind.isTriggered(event) && !event.which == 1)
+ ) {
+ // Drag (Mouse and pen only)
+ let {viewport} = this.$refs;
+ let margin = this.getFrameMargin();
+ let margin_center = [this.width/2, this.height/2];
+ let original = [
+ viewport.scrollLeft - 5,
+ viewport.scrollTop - 5
+ ];
+ function dragMouseWheel(e2) {
+ viewport.scrollLeft = Math.snapToValues(original[0] + event.clientX - e2.clientX + UVEditor.total_zoom_offset[0], [margin[0], margin_center[0]], 10);
+ viewport.scrollTop = Math.snapToValues(original[1] + event.clientY - e2.clientY + UVEditor.total_zoom_offset[1], [margin[1], margin_center[1]], 10);
+
+ UVEditor.vue.centered_view = (viewport.scrollLeft == margin[0] || viewport.scrollLeft == margin_center[0])
+ && (viewport.scrollTop == margin[1] || viewport.scrollTop == margin_center[1]);
+ UVEditor.updateUVNavigator();
+ }
function dragMouseWheelStop(e) {
- removeEventListeners(document, 'mousemove touchmove', dragMouseWheel);
- removeEventListeners(document, 'mouseup touchend', dragMouseWheelStop);
+ removeEventListeners(document, 'pointermove', dragMouseWheel);
+ removeEventListeners(document, 'pointerup', dragMouseWheelStop);
if (e.which == 3 && Math.pow(viewport.scrollLeft - original[0], 2) + Math.pow(viewport.scrollTop - original[1], 2) > 50) {
preventContextMenu();
}
}
- addEventListeners(document, 'mousemove touchmove', dragMouseWheel);
- addEventListeners(document, 'mouseup touchend', dragMouseWheelStop);
+ addEventListeners(document, 'pointermove', dragMouseWheel);
+ addEventListeners(document, 'pointerup', dragMouseWheelStop);
event.preventDefault();
$(getFocusedTextInput()).trigger('blur');
return false;
- } else if (this.mode == 'paint' && Toolbox.selected.paintTool && (event.which === 1 || Keybinds.extra.paint_secondary_color.keybind.isTriggered(event) || (event.touches && event.touches.length == 1))) {
+ } else if (
+ this.mode == 'paint' &&
+ Toolbox.selected.paintTool &&
+ (
+ event.which === 1 ||
+ event.pointerType === 'pen' ||
+ event.pointerType === 'touch' ||
+ Keybinds.extra.paint_secondary_color.keybind.isTriggered(event)
+ )
+ ) {
let is_scrollbar_click = event.target.id == 'uv_viewport' && (event.offsetX > event.target.clientWidth || event.offsetY > event.target.heightWidth);
if (!is_scrollbar_click) {
// Paint
@@ -2767,7 +2820,14 @@ Interface.definePanels(function() {
event.preventDefault();
return false;
- } else if (this.mode == 'uv' && event.target.id == 'uv_frame' && (event.which === 1 || (event.touches && event.touches.length == 1))) {
+ } else if (
+ this.mode == 'uv' &&
+ event.target.id == 'uv_frame' &&
+ (
+ (event.which === 1 && event.pointerType !== 'touch') ||
+ event.pointerType === 'pen'
+ )
+ ) {
if (event.altKey || Pressing.overrides.alt) {
return this.dragFace(null, null, event);
@@ -2792,6 +2852,10 @@ Interface.definePanels(function() {
}
function drag(e1) {
+ if (scope.touches_count == 2) {
+ stop(e1);
+ return;
+ }
selection_rect.active = true;
let rect = getRectangle(
event.offsetX / scope.inner_width * scope.uv_resolution[0],
@@ -2874,8 +2938,8 @@ Interface.definePanels(function() {
updateSelection();
}
function stop(e2) {
- removeEventListeners(document, 'mousemove touchmove', drag);
- removeEventListeners(document, 'mouseup touchend', stop);
+ removeEventListeners(document, 'pointermove', drag);
+ removeEventListeners(document, 'pointerup', stop);
if (Math.pow(event.clientX - e2.clientX, 2) + Math.pow(event.clientY - e2.clientY, 2) < 10) {
for (let element of UVEditor.getMappableElements()) {
@@ -2887,8 +2951,8 @@ Interface.definePanels(function() {
selection_rect.active = false;
}, 1)
}
- addEventListeners(document, 'mousemove touchmove', drag, false);
- addEventListeners(document, 'mouseup touchend', stop, false);
+ addEventListeners(document, 'pointermove', drag, false);
+ addEventListeners(document, 'pointerup', stop, false);
}
},
onMouseEnter(event) {
@@ -4366,8 +4430,9 @@ Interface.definePanels(function() {
{
+ let delta = {
+ x: e2.clientX - e1.clientX,
+ y: e2.clientY - e1.clientY,
+ };
+ let distance = Math.sqrt(Math.pow(delta.x, 2) + Math.pow(delta.y, 2));
+ context.delta = delta;
+ context.distance = distance;
+ context.event = e2;
+ if (!started) {
+ if (distance > (options.start_distance ?? 6)) {
+ started = true;
+ if (options.onStart) options.onStart(context);
+ } else {
+ return;
+ }
+ }
+ if (options.onMove) options.onMove(context);
+ }
+ let stop = (e2: PointerEvent) => {
+ document.removeEventListener('pointermove', drag);
+ document.removeEventListener('pointerup', stop);
+ context.event = e2;
+ if (options.onEnd) options.onEnd(context);
+ };
+ document.addEventListener('pointermove', drag);
+ document.addEventListener('pointerup', stop);
+}
\ No newline at end of file
diff --git a/lang/en.json b/lang/en.json
index 144df4f0..1098f05f 100644
--- a/lang/en.json
+++ b/lang/en.json
@@ -214,6 +214,10 @@
"keys.printscreen": "Print Screen",
"keys.pause": "Pause",
"keys.mousewheel": "Mousewheel",
+ "keys.slide_lmb_horizontal": "Left-click slide horizontal",
+ "keys.slide_lmb_vertical": "Left-click slide vertical",
+ "keys.slide_rmb_horizontal": "Right-click slide horizontal",
+ "keys.slide_rmb_vertical": "Right-click slide vertical",
"status_bar.saved":"Model is saved",
"status_bar.recording":"Recording Timelapse",
@@ -1332,7 +1336,7 @@
"action.image_tiled_view.description": "Enable the tiled view in the 2D editor",
"action.image_onion_skin_view": "Image Editor Onion Skin",
"action.image_onion_skin_view.description": "Enable the onion skin view in the 2D editor",
- "action.slider_brush_size": "Size",
+ "action.slider_brush_size": "Brush Size",
"action.slider_brush_size.desc": "Radius of the brush in pixels",
"action.slider_brush_opacity": "Opacity",
"action.slider_brush_opacity.desc": "Opacity of the brush from 0 to 255",