ui toolkit

This commit is contained in:
Val Erastov 2016-10-12 21:28:07 -07:00
parent 0128f1b041
commit e5485dd9be
21 changed files with 845 additions and 41 deletions

View file

@ -39,6 +39,7 @@
},
"dependencies": {
"diff-match-patch": "1.0.0",
"numeric": "1.2.6"
"numeric": "1.2.6",
"jwerty": "0.3.2"
}
}

View file

@ -0,0 +1,92 @@
export function cssIconsToClasses(cssIcons) {
return cssIcons.map((i)=> 'fa-'+i).join(' ')
}
export function ActionManager(app) {
this.app = app;
this.actions = {};
this.eventsToActions = {};
this.registerAction('-', {'type': 'separator'});
}
ActionManager.prototype.registerAction = function(id, action) {
action = Object.assign({id: id}, action);
action.__handler = handler(action);
action.state = {
hint: '',
enabled: true,
visible: true
};
this.addListeners(action);
this.actions[id] = action;
};
ActionManager.prototype.addListeners = function(action) {
if (action.listens == undefined || action.update == undefined) return;
for (let event of action.listens) {
let actions = this.eventsToActions[event];
if (actions == undefined) {
actions = [];
this.eventsToActions[event] = actions;
this.app.bus.subscribe(event, (data) => this.notify(event));
}
actions.push(action);
}
this.updateAction(action);
};
ActionManager.prototype.notify = function(event) {
let actions = this.eventsToActions[event];
if (actions != undefined) {
for (let action of actions) {
this.updateAction(action);
this.app.bus.notify('action.update.' + action.id, action.state);
}
}
};
ActionManager.prototype.updateAction = function(action) {
action.state.hint = '';
action.state.enabled = true;
action.state.visible = true;
action.update(action.state, this.app);
};
ActionManager.prototype.registerActions = function(actions) {
for (let actionName in actions) {
this.registerAction(actionName, actions[actionName]);
}
};
ActionManager.prototype.run = function(actionId, event) {
var action = this.actions[actionId];
if (action == undefined) {
return;
}
if (action.state.enabled) {
action.__handler(this.app, event);
} else {
this.app.inputManager.info("action '"+actionId+"' is disabled and can't be launched<br>" + action.state.hint);
}
};
ActionManager.prototype.subscribe = function(actionId, callback) {
this.app.bus.subscribe('action.update.'+actionId, callback);
};
const NOOP = () => {};
function handler(action) {
if (action.type == 'binary') {
return (app, event, source) => app.state[action.property] = !app.state[action.property];
} else if (action.type == 'separator') {
return NOOP;
} else if (action.type == 'menu') {
return (app, event) => action.menu.show(app, event);
} else if (action.invoke != undefined) {
return (app, event) => action.invoke(app, event);
} else {
return NOOP;
}
}

View file

@ -0,0 +1,25 @@
export const refreshSketches = {
cssIcons: ['refresh'],
label: 'Refresh Sketches',
info: 'refresh all visible sketches',
invoke: (app) => app.refreshSketches()
};
export const info = {
cssIcons: ['info-circle'],
label: 'info',
info: 'opens help dialog',
invoke: (app) => app.showInfo()
};
export const showSketches = {
type: 'binary',
property: 'showSketches',
cssIcons: ['image'],
label: 'show sketches',
info: 'toggle whether show sketches on a solid face'
};
export const noIcon = {
label: 'no icon'
};

View file

@ -0,0 +1,70 @@
import * as Operations from '../operations'
function mergeInfo(op, action) {
action.label = op.label;
action.icon32 = op.icon + '32.png';
action.icon96 = op.icon + '96.png';
return action;
}
export const OperationActions = {
'CUT': mergeInfo(Operations.CUT, {
info: 'makes a cut based on 2D sketch'
}),
'PAD': mergeInfo(Operations.PAD, {
info: 'extrudes 2D sketch'
}),
'BOX': mergeInfo(Operations.BOX, {
info: 'creates new object box'
}),
'PLANE': mergeInfo(Operations.PLANE, {
info: 'creates new object plane'
}),
'SPHERE': mergeInfo(Operations.SPHERE, {
info: 'creates new object sphere'
}),
'INTERSECTION': mergeInfo(Operations.INTERSECTION, {
info: 'intersection operation on two solids'
}),
'DIFFERENCE': mergeInfo(Operations.DIFFERENCE, {
info: 'difference operation on two solids'
}),
'UNION': mergeInfo(Operations.UNION, {
info: 'union operation on two solids'
})
};
requiresFaceSelection(OperationActions.CUT, 1);
requiresFaceSelection(OperationActions.PAD, 1);
requiresSolidSelection(OperationActions.INTERSECTION, 2);
requiresSolidSelection(OperationActions.DIFFERENCE, 2);
requiresSolidSelection(OperationActions.UNION, 2);
function requiresFaceSelection(action, amount) {
action.listens = ['selection'];
action.update = (state, app) => {
state.enabled = app.viewer.selectionMgr.selection.length >= amount;
if (!state.enabled) {
state.hint = 'requires at least one face to be selected';
}
}
}
function requiresSolidSelection(action, amount) {
action.listens = ['selection'];
action.update = (state, app) => {
state.enabled = app.viewer.selectionMgr.selection.length >= amount;
if (!state.enabled) {
state.hint = 'requires at least two solids to be selected';
}
}
}

View file

@ -0,0 +1,34 @@
export const file = {
label: 'file',
actions: ['save', 'exportStl', 'uploadToThingiverse']
};
export const craft = {
label: 'craft',
cssIcons: ['magic'],
info: 'set of available craft operations on a solid',
actions: ['PAD', 'CUT']
};
export const primitives = {
label: 'add',
cssIcons: ['cube', 'plus'],
info: 'set of available solid creation operations',
actions: ['PLANE', 'BOX', 'SPHERE']
};
export const boolean = {
label: 'bool',
cssIcons: ['pie-chart'],
info: 'set of available boolean operations',
actions: ['INTERSECTION', 'DIFFERENCE', 'UNION']
};
export const main = {
label: 'start',
cssIcons: ['rocket'],
info: 'common set of actions',
actions: ['PAD', 'CUT', '-', 'INTERSECTION', 'DIFFERENCE', 'UNION', '-', 'PLANE', 'BOX', 'SPHERE', '-',
'deselectAll', 'refreshSketches' ]
};

91
web/app/3d/menu/menu.js Normal file
View file

@ -0,0 +1,91 @@
import {cssIconsToClasses} from '../actions/actions'
import {EventData} from '../ui/utils'
export default function Menu(menuActions, inputManager) {
this.inputManager = inputManager;
this.node = $('<div>', {
'class' : 'menu'
});
let container = $('<div>', {'class': 'menu-container'});
this.node.append(container);
let separatorAllowed = false;
for (var i = 0; i < menuActions.length; i++) {
var action = menuActions[i];
if (action.type == 'separator') {
container.append($('<div>', {'class': 'menu-separator'}));
separatorAllowed = false;
continue;
}
separatorAllowed = i != menuActions.length - 1;
let menuItem = $('<div>', {'class' : 'menu-item action-item'});
menuItem.data('action', action.id);
menuItem.addClass('icon16-left');
if (action.icon32 != undefined) {
menuItem.css({
'background-image' : 'url('+action.icon32+')'
});
} else if (action.cssIcons != undefined) {
menuItem.append($('<i>', {'class': 'fa ' + cssIconsToClasses(action.cssIcons)})).append(' ');
} else {
}
menuItem.append($('<span>',{text: action.label}));
var hotkey = this.inputManager.keymap[action.id];
if (hotkey) {
hotkey = hotkey.replace(/\s/g, '');
if (hotkey.length < 15) {
menuItem.append($('<span>',{text: hotkey,'class' : 'action-hotkey-info'}));
}
}
container.append(menuItem);
var uiUpdater = (state) => {
if (state.enabled) {
menuItem.removeClass('action-disabled');
} else {
menuItem.addClass('action-disabled');
}
menuItem.data('actionHint', state.hint);
};
uiUpdater(action.state);
this.inputManager.app.actionManager.subscribe(action.id, uiUpdater);
}
this.node.hide();
$('body').append(this.node);
};
Menu.prototype.show = function(app, event) {
this.node.removeClass('menu-flat-top');
this.node.removeClass('menu-flat-bottom');
this.node.show(); //node should be visible to get right dimensions
const r = Math.round;
let source = EventData.get(event, 'menu-button');
if (source != undefined) {
var off = source.offset();
var orientation = source.data('menuOrientation');
if (orientation == 'up') {
this.node.addClass('menu-flat-bottom');
this.node.offset({
left: r(off.left),
top: r(off.top - this.node.outerHeight())
});
} else if (orientation == 'down') {
this.node.addClass('menu-flat-top');
this.node.offset({
left: r(off.left),
top: r(off.top + source.outerHeight())
});
} else {
}
} else {
var mouseInfo = this.inputManager.mouseInfo;
if (mouseInfo != null) {
this.node.offset({
left: r(mouseInfo.pageX - this.node.outerWidth() / 2),
top: r(mouseInfo.pageY - this.node.outerHeight() / 2)
});
}
}
this.inputManager.registerOpenMenu(this);
};

View file

@ -1,7 +1,12 @@
import {Bus} from '../ui/toolkit'
import {Viewer} from './viewer'
import {UI} from './ctrl'
import TabSwitcher from './tab-switcher'
import {UI} from './ui/ctrl'
import TabSwitcher from './ui/tab-switcher'
import ControlBar from './ui/control-bar'
import {InputManager} from './ui/input-manager'
import {ActionManager} from './actions/actions'
import * as CoreActions from './actions/core-actions'
import {OperationActions} from './actions/operation-actions'
import Vector from '../math/vector'
import {Matrix3, AXIS, ORIGIN, IDENTITY_BASIS} from '../math/l3space'
import * as workbench from './workbench'
@ -19,7 +24,15 @@ function App() {
this.initSample();
}
this.bus = new Bus();
this.actionManager = new ActionManager(this);
this.inputManager = new InputManager(this);
this.state = this.createState();
this.viewer = new Viewer(this.bus, document.getElementById('viewer-container'));
this.actionManager.registerActions(CoreActions);
this.actionManager.registerActions(OperationActions);
this.tabSwitcher = new TabSwitcher($('#tab-switcher'), $('#view-3d'));
this.controlBar = new ControlBar(this, $('#control-bar'));
this.ui = new UI(this);
this.craft = new workbench.Craft(this);
@ -29,8 +42,6 @@ function App() {
this.load();
}
this.tabSwitcher = new TabSwitcher($('#tab-switcher'), $('#view-3d'));
this._refreshSketches();
this.viewer.render();
@ -47,6 +58,7 @@ function App() {
app.viewer.render();
}
}
window.addEventListener('storage', storage_handler, false);
this.bus.subscribe("craft", function() {
var historyEditMode = app.craft.historyPointer != app.craft.history.length;
@ -55,9 +67,14 @@ function App() {
}
app._refreshSketches();
});
window.addEventListener('storage', storage_handler, false);
}
App.prototype.createState = function() {
const state = {};
this.bus.defineObservable(state, 'showSketches', true);
return state;
};
App.prototype.findAllSolids = function() {
return this.viewer.workGroup.children
.filter(function(obj) {return obj.__tcad_solid !== undefined} )

48
web/app/3d/operations.js Normal file
View file

@ -0,0 +1,48 @@
export const CUT = {
icon: 'img/3d/cut',
label: 'Cut',
info: (p) => 'CUT (' + p + ')'
};
export const PAD = {
icon: 'img/3d/extrude',
label: 'Extrude',
info: (p) => 'PAD (' + p + ')'
};
export const BOX = {
icon: 'img/3d/cube',
label: 'Box',
info: (p) => 'BOX (' + p + ')'
};
export const PLANE = {
icon: 'img/3d/plane',
label: 'Plane',
info: (p) => 'PLANE (' + p + ')'
};
export const SPHERE = {
icon: 'img/3d/sphere',
label: 'Sphere',
info: (p) => 'SPHERE (' + p + ')'
};
export const INTERSECTION = {
icon: 'img/3d/intersection',
label: 'Intersection',
info: (p) => 'INTERSECTION (' + p + ')'
};
export const DIFFERENCE = {
icon: 'img/3d/difference',
label: 'Difference',
info: (p) => 'DIFFERENCE (' + p + ')'
};
export const UNION = {
icon: 'img/3d/union',
label: 'Union',
info: (p) => 'UNION (' + p + ')'
};

View file

@ -0,0 +1,39 @@
import {cssIconsToClasses} from '../actions/actions'
export default function ControlBar(app, bar) {
this.app = app;
this.bar = bar;
}
ControlBar.prototype.add = function(actionName, left, overrides) {
let action = this.app.actionManager.actions[actionName];
if (action == undefined) return;
if (overrides != undefined) {
action = Object.assign({}, action, overrides);
}
const btn = $('<div>', {'class': 'button'});
if (action.cssIcons != undefined) {
btn.append($('<i>', {'class': 'fa ' + cssIconsToClasses(action.cssIcons)}));
}
if (action.label != undefined && action.label != null) {
if (action.cssIcons != undefined) {
btn.append(' ');
}
btn.append(action.label);
}
var to = this.bar.find(left ? '.left-group' : '.right-group');
to.append(btn);
if (action.type == 'binary') {
this.app.bus.subscribe(action.property, (show) => {
btn.removeClass('button-selected');
if (show) {
btn.addClass('button-selected');
}
})(this.app.state[action.property]);
} else if (action.type == 'menu') {
btn.data('menuOrientation', 'up');
}
btn.addClass('action-item');
btn.data('action', actionName);
return btn;
};

View file

@ -1,14 +1,17 @@
import * as tk from '../ui/toolkit'
import * as cad_utils from './cad-utils'
import * as math from '../math/math'
import * as workbench from './workbench'
import ToolBar from '../ui/toolbar'
import {ExtrudeWizard} from './wizards/extrude'
import {PlaneWizard} from './wizards/plane'
import {BoxWizard} from './wizards/box'
import {SphereWizard} from './wizards/sphere'
import {TransformWizard} from './wizards/transform'
import {IO} from '../sketcher/io'
import * as tk from '../../ui/toolkit'
import * as cad_utils from '../cad-utils'
import * as math from '../../math/math'
import * as workbench from '../workbench'
import ToolBar from '../../ui/toolbar'
import * as MenuConfig from '../menu/menu-config'
import * as Operations from '../operations'
import Menu from '../menu/menu'
import {ExtrudeWizard} from '../wizards/extrude'
import {PlaneWizard} from '../wizards/plane'
import {BoxWizard} from '../wizards/box'
import {SphereWizard} from '../wizards/sphere'
import {TransformWizard} from '../wizards/transform'
import {IO} from '../../sketcher/io'
function UI(app) {
this.app = app;
@ -37,10 +40,13 @@ function UI(app) {
tk.add(modificationsFolder, modificationsListComp);
var toolbarVertOffset = 10; //this.mainBox.root.position().top;
this.registerMenuActions();
this.craftToolBar = this.createCraftToolBar(toolbarVertOffset);
this.createBoolToolBar(this.craftToolBar.node.position().top + this.craftToolBar.node.height() + 20);
this.createMiscToolBar(toolbarVertOffset);
this.fillControlBar();
var ui = this;
function setHistory() {
@ -119,8 +125,7 @@ function UI(app) {
printFaceId.root.click(function () {
console.log(app.viewer.selectionMgr.selection[0].id);
});
showSketches.input.click(function () {
var enabled = this.checked;
this.app.bus.subscribe("showSketches", (enabled) => {
var solids = app.findAllSolids();
for (var i = 0; i < solids.length; i++) {
for (var j = 0; j < solids[i].polyFaces.length; j++) {
@ -130,6 +135,7 @@ function UI(app) {
}
app.viewer.render();
});
save.root.click(function() {
app.save();
});
@ -166,8 +172,8 @@ UI.prototype.createCraftToolBar = function (vertPos) {
toolBar.add('Plane', 'img/3d/plane96.png', () => this.registerWizard(new PlaneWizard(this.app.viewer), false));
toolBar.add('Box', 'img/3d/cube96.png', () => this.registerWizard(new BoxWizard(this.app.viewer), false));
toolBar.add('Sphere', 'img/3d/sphere96.png', () => this.registerWizard(new SphereWizard(this.app.viewer), false));
$('#view-3d').append(toolBar.node);
toolBar.node.css({top : vertPos + 'px'});
$('#viewer-container').append(toolBar.node);
toolBar.node.css({left: '10px',top : vertPos + 'px'});
return toolBar;
};
@ -177,9 +183,9 @@ UI.prototype.createMiscToolBar = function (vertPos) {
toolBar.addFa('upload', () => this.app.sketchFace());
toolBar.addFa('refresh', () => this.app.sketchFace());
toolBar.addFa('square-o', () => this.app.sketchFace());
$('#view-3d').append(toolBar.node);
$('#viewer-container').append(toolBar.node);
toolBar.node.css({top : vertPos + 'px'});
toolBar.node.css({left : '', right: '20px', 'font-size': '16px'});
toolBar.node.css({right: '10px', 'font-size': '16px'});
return toolBar;
};
@ -188,11 +194,33 @@ UI.prototype.createBoolToolBar = function(vertPos) {
toolBar.add('Intersection', 'img/3d/intersection96.png', () => this.app.sketchFace());
toolBar.add('Difference', 'img/3d/difference96.png', this.cutExtrude(true));
toolBar.add('Union', 'img/3d/union96.png', this.cutExtrude(false));
$('#view-3d').append(toolBar.node);
toolBar.node.css({top : vertPos + 'px'});
$('#viewer-container').append(toolBar.node);
toolBar.node.css({left: '10px', top : vertPos + 'px'});
return toolBar;
};
UI.prototype.registerMenuActions = function() {
for (let menuName in MenuConfig) {
const m = MenuConfig[menuName];
var action = Object.assign({'type' : 'menu'}, m);
delete action['actions'];
action.menu = new Menu(
m.actions.map((a) => this.app.actionManager.actions[a])
.filter((a) => a != undefined), this.app.inputManager);
this.app.actionManager.registerAction('menu.' + menuName, action);
}
};
UI.prototype.fillControlBar = function() {
const LEFT = true;
const RIGHT = !LEFT;
this.app.controlBar.add('info', RIGHT, {'label': null});
this.app.controlBar.add('refreshSketches', RIGHT, {'label': null});
this.app.controlBar.add('showSketches', RIGHT, {'label': 'sketches'});
this.app.controlBar.add('menu.craft', LEFT);
this.app.controlBar.add('menu.primitives', LEFT);
this.app.controlBar.add('menu.boolean', LEFT);
};
UI.prototype.registerWizard = function(wizard, overridingHistory) {
wizard.ui.box.root.css({left : (this.mainBox.root.width() + this.craftToolBar.node.width() + 30) + 'px', top : 0});

View file

@ -0,0 +1,115 @@
import {jwerty} from 'jwerty'
import {keymap} from './keymaps/default'
import {DefaultMouseEvent, EventData, fit} from './utils'
export function InputManager(app) {
this.app = app;
this.openMenus = [];
this.keymap = keymap;
this.mouseInfo = new DefaultMouseEvent();
this.requestedActionInfo = null;
$(() => {
$(document)
.on('keydown', (e) => this.handleKeyPress(e))
.on('mousedown', (e) => this.clear(e))
.on('mouseenter', '.action-item', (e) => this.showActionInfo($(e.target)))
.on('mouseleave', '.action-item', (e) => this.emptyInfo())
.on('mousemove', (e) => this.mouseInfo = e)
.on('click', '.action-item', (e) => this.handleActionClick(e));
});
}
InputManager.prototype.handleKeyPress = function(e) {
console.log(e.keyCode);
switch (e.keyCode) {
case 27 : this.clear(); break;
}
for (let action in this.keymap) {
if (jwerty.is(this.keymap[action], e)) {
this.app.actionManager.run(action, e);
break;
}
}
};
InputManager.prototype.clear = function(e) {
if (e != undefined && $(e.target).closest('.menu-item').length != 0) {
return;
}
if (this.openMenus.length != 0) {
for (let openMenu of this.openMenus) {
openMenu.node.hide();
}
this.openMenus = [];
}
this.requestedActionInfo = null;
$('#message-sink').hide();
};
InputManager.prototype.handleActionClick = function(event) {
var target = $(event.currentTarget);
var action = target.data('action');
if (action != undefined) {
this.clear();
EventData.set(event, 'menu-button', target);
this.app.actionManager.run(action, event);
}
};
InputManager.prototype.registerOpenMenu = function(menu) {
fit(menu.node, $('body'));
this.openMenus.push(menu);
};
InputManager.messageSink = function() {
return $('#message-sink');
};
InputManager.prototype.emptyInfo = function() {
this.requestedActionInfo = null;
var messageSink = InputManager.messageSink();
messageSink.empty();
messageSink.hide();
};
InputManager.prototype.showActionInfo = function(el) {
//show hint immediately and deffer showing the full info
var hint = el.data('actionHint');
if (hint) {
InputManager.messageSink().text(hint);
this.showMessageSinkAround();
}
this.requestInfo(el.data('action'));
};
InputManager.prototype.info = function(text) {
InputManager.messageSink().html(text);
this.showMessageSinkAround();
};
InputManager.prototype.showMessageSinkAround = function() {
var messageSink = InputManager.messageSink();
messageSink.show();
messageSink.offset({left: this.mouseInfo.pageX + 10, top: this.mouseInfo.pageY + 10});
fit(messageSink, $('body'));
};
InputManager.prototype.requestInfo = function(action) {
this.requestedActionInfo = action;
setTimeout(() => {
var actionId = this.requestedActionInfo;
this.requestedActionInfo = null;
if (actionId != null) {
const action = this.app.actionManager.actions[actionId];
if (action) {
var hotkey = this.keymap[actionId];
InputManager.messageSink().html(
(action.state.hint ? action.state.hint : '') +
('<div>' + action.info + '</div>') +
(hotkey ? '<div >hotkey: ' + hotkey + '</div>' : ''));
this.showMessageSinkAround();
}
}
}, 1000);
};

View file

@ -0,0 +1,11 @@
export const keymap = {
'CUT': 'C',
'PAD': 'E',
'zoomIn': '+',
'zoomOut': '-',
'menu.craft': 'shift+C',
'menu.primitives': 'shift+A',
'menu.main': 'space',
'save': 'CTRL + S'
};

74
web/app/3d/ui/utils.js Normal file
View file

@ -0,0 +1,74 @@
export function DefaultMouseEvent() {
var viewer = $('#viewer-container');
var off = viewer.offset();
const r = Math.round;
this.type = 'click';
this.canBubble = true;
this.cancelable = true;
this.detail = 1;
this.screenX = r(off.left + viewer.width() / 2);
this.screenY = r(off.top + viewer.height() / 2);
this.clientX = this.screenX;
this.clientY = this.screenY;
this.pageX = this.screenX;
this.pageY = this.screenY;
this.ctrlKey = false;
this.altKey = false;
this.shiftKey = false;
this.metaKey = false;
this.button = 0;
this.relatedTarget = null;
}
export const EventData = {
get: function(event, key) {
if (event.data) {
return event.data[key];
} else {
return undefined;
}
},
set: function(event, key, value) {
if (!event.data) {
event.data = {};
}
event.data[key] = value;
}
};
export function fit(el, relativeEl) {
const span = 5;
var relOff = relativeEl.offset();
var off = el.offset();
var needToSet = false;
if (off.left < relOff.left ) {
off.left = relOff.left + span;
needToSet = true;
}
const right = relOff.left + relativeEl.width() - span;
var outerWidth = el.outerWidth();
if (off.left + outerWidth >= right) {
off.left = right - outerWidth;
needToSet = true;
}
if (off.top < relOff.top + span) {
off.top = relOff.top + span;
needToSet = true;
}
var bottom = relOff.top + relativeEl.height() - span;
var outerHeight = el.outerHeight();
if (off.top + outerHeight >= bottom) {
off.top = bottom - outerHeight;
needToSet = true;
}
if (needToSet) {
el.css({
left: off.left + 'px',
top: off.top + 'px'
});
}
}

View file

@ -121,14 +121,24 @@ function Viewer(bus, container) {
startY : 0
};
//fix for FireFox
function fixOffsetAPI(event) {
if (event.offsetX == undefined) {
event.offsetX = event.layerX;
event.offsetY = event.layerY;
}
}
renderer.domElement.addEventListener('mousedown',
function(e) {
fixOffsetAPI(e);
mouseState.startX = e.offsetX;
mouseState.startY = e.offsetY;
}, false);
renderer.domElement.addEventListener('mouseup',
function(e) {
fixOffsetAPI(e);
var dx = Math.abs(mouseState.startX - e.offsetX);
var dy = Math.abs(mouseState.startY - e.offsetY);
var TOL = 1;
@ -258,6 +268,7 @@ SelectionManager.prototype.deselectAll = function() {
this.selection[i].solid.mesh.geometry.colorsNeedUpdate = true;
}
this.clear();
this.viewer.bus.notify('selection', null);
this.viewer.render();
};

View file

@ -101,7 +101,7 @@ function Viewer(canvas, IO) {
this._serviceLayers = [];
this.dimLayer = new Layer("_dim", Styles.DIM);
this.dimLayers = [this.dimLayer];
this.bus.defineObservable(this, 'dimScale', 'dimScale', 1);
this.bus.defineObservable(this, 'dimScale', 1);
this.bus.subscribe('dimScale', function(){ viewer.refresh(); });
this._workspace = [this.dimLayers, this.layers, this._serviceLayers];

View file

@ -3,8 +3,6 @@ export default function ToolBar() {
this.node = $('<div>', {
css :{
'position': 'absolute',
'left': '260px',
'top': '10px',
'background-color': 'rgba(255, 255, 255, 0.5)',
'padding': '5px',
'border-radius' : '5px'

View file

@ -278,20 +278,26 @@ export function Bus() {
this.listeners = {};
}
Bus.prototype.subscribe = function(event, callback) {
Bus.prototype.subscribe = function(event, callback, listenerId) {
var listenerList = this.listeners[event];
if (listenerList === undefined) {
listenerList = [];
this.listeners[event] = listenerList;
}
listenerList.push(callback);
if (listenerId == undefined) listenerId = null;
listenerList.push([callback, listenerId]);
return callback;
};
Bus.prototype.notify = function(event, data) {
Bus.prototype.notify = function(event, data, sender) {
var listenerList = this.listeners[event];
if (listenerList !== undefined) {
for (var i = 0; i < listenerList.length; i++) {
listenerList[i](data);
const callback = listenerList[i][0];
const listenerId = listenerList[i][1];
if (sender == undefined || listenerId == null || listenerId != sender) {
callback(data);
}
}
}
};
@ -300,7 +306,8 @@ Bus.Observable = function(initValue) {
this.value = initValue;
};
Bus.prototype.defineObservable = function(scope, name, eventName, initValue) {
Bus.prototype.defineObservable = function(scope, name, initValue, eventName) {
if (eventName == undefined) eventName = name;
var observable = new Bus.Observable(initValue);
var bus = this;
return Object.defineProperty(scope, name, {

View file

@ -6,6 +6,9 @@
@right-panel-width: 250px;
@control-button-border: 1px solid #2c2c2c;
@menu-border-radius: 3px;
.no-selection {
user-select: none;
-webkit-user-select: none;
@ -15,6 +18,11 @@
body {
background-color: #808080;
.main-font;
}
.main-font {
font: 11px 'Lucida Grande', sans-serif;
}
.history-selected, .history-selected:hover {
@ -36,7 +44,6 @@ body {
width: 100%;
border-top: 1px solid @tab-border-color;
color: #eee;
font: 11px 'Lucida Grande', sans-serif;
text-align: center;
.no-selection;
}
@ -68,15 +75,147 @@ body {
}
#viewer-container {
position: absolute; left: @right-panel-width; right:0; height: 100%;
position: absolute;
left: @right-panel-width;
right: 0;
top: 0;
bottom: 0;
}
#control-bar {
position: absolute;
left: @right-panel-width;
right: 0;
bottom: 0;
height: 20px;
background-color: rgba(0, 0, 0, 0.5);
color: #ccc;
}
#control-bar .left-group {
text-align: left;
float: left;
}
#control-bar .right-group {
text-align: right;
}
#control-bar .left-group .button {
float: left;
border-right: @control-button-border;
}
#control-bar .right-group .button {
float: right;
border-left: @control-button-border;
}
.button .fa {
line-height: 1.5;
}
#control-bar .button {
padding: 3px 7px 0 5px;
height: 100%;
vertical-align: baseline;
cursor: pointer;
.no-selection
}
#control-bar .button:hover {
background-color: #555;
}
#control-bar .button-selected {
background-color: #666;
}
#control-bar .button-selected:hover {
background-color: #666;
}
#right-panel {
position: absolute; height: 100%; width: @right-panel-width;
position: absolute;
height: 100%;
background-color: #000;
width: @right-panel-width;
}
.aux-win {
color: #fff;
background-color: rgba(0,0,0,0.7);
border: solid 1px #000;
border-radius: @menu-border-radius
}
.menu {
position: absolute;
.aux-win;
/* this element can't have neither padding nor margin to be properly positioned as menu */
}
.menu-container {
padding: 5px 0 5px 0;
}
.menu-item {
padding: 5px 5px 5px 2px;
cursor: pointer;
white-space: nowrap;
}
.menu-item:hover {
background-color: #0074D9;
}
.menu-flat-bottom {
border-radius: @menu-border-radius @menu-border-radius 0 0;
}
.menu-flat-top {
border-radius: 0 0 @menu-border-radius @menu-border-radius;
}
.menu-separator {
border-top: solid 1px #777;
}
.menu-item .fa {
margin-left: -16px;
padding-right: 3px;
}
.menu-item.action-disabled {
color: #888;
}
.menu-item .action-hotkey-info {
float: right;
padding-left: 15px;
color: #888;
font-size: 9px;
margin-top: 1px;
}
.icon16-left {
background-position-y: center;
background-position-x: 5px;
background-repeat: no-repeat;
background-size: 16px 16px;
padding-left: 25px;
}
#message-sink {
display: none;
position: absolute;
max-width: 400px;
padding: 5px;
.aux-win;
color: #ccc;
white-space: nowrap;
z-index: 999;
}

View file

@ -29,11 +29,15 @@
<div id="right-panel"></div>
<div id="viewer-container"></div>
<div id="control-bar">
<div class="left-group">
</div>
<div class="right-group">
</div>
</div>
</div>
</div>
<div id="tab-switcher">
</div>
<div id="tab-switcher"></div>
<div id="message-sink"></div>
<a id="downloader" style="display: none;" ></a>
</body>
</html>

View file

@ -395,7 +395,7 @@ THREE.TrackballControls = function ( object, domElement ) {
if ( _this.enabled === false ) return;
event.preventDefault();
event.stopPropagation();
//event.stopPropagation(); - interfere with entire application
if ( _state === STATE.NONE ) {