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 @@
- -
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",