Skip to content

Commit

Permalink
feat: Draw stroke.
Browse files Browse the repository at this point in the history
  • Loading branch information
QuentinRoy committed Jul 28, 2017
1 parent 38b788f commit 89e2b27
Show file tree
Hide file tree
Showing 10 changed files with 250 additions and 71 deletions.
5 changes: 5 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"license": "MIT",
"dependencies": {
"rad2deg": "^1.0.0",
"raf-throttle": "^2.0.3",
"rxjs": "^5.4.2"
},
"devDependencies": {
Expand Down
117 changes: 86 additions & 31 deletions src/connect-navigation-to-layout.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,29 @@
*
* @param {HTMLElement} parentDOM - The element where to append the menu.
* @param {Observable} navigation$ - Notifications of the navigation.
* @param {Function} createLayout - Menu layout factory.
* @param {Function} createMenuLayout - Menu layout factory.
* @param {Function} createStrokeCanvas - Stroke canvas factory.
* @return {Observable} `navigation$` with menu opening and closing side effects.
*/
export default (parentDOM, navigation$, createLayout) => {
export default (
parentDOM,
navigation$,
createMenuLayout,
createStrokeCanvas
) => {
// Open the menu in function of navigation notifications.
let menu = null;
let strokeCanvas = null;
let strokeStart = null;

const closeMenuIfOpened = () => {
if (menu) {
menu.remove();
menu = null;
}
const closeMenu = () => {
menu.remove();
menu = null;
};

const openMenu = (model, position) => {
const cbr = parentDOM.getBoundingClientRect();
menu = createLayout(parentDOM, model, [
menu = createMenuLayout(parentDOM, model, [
position[0] - cbr.left,
position[1] - cbr.top
]);
Expand All @@ -29,34 +35,83 @@ export default (parentDOM, navigation$, createLayout) => {
menu.setActive(id);
};

const startStroke = position => {
strokeCanvas = createStrokeCanvas(parentDOM);
strokeCanvas.drawStroke([position]);
strokeStart = position;
};

const noviceMove = position => {
strokeCanvas.clear();
strokeCanvas.drawStroke([strokeStart, position]);
strokeCanvas.drawPoint(strokeStart);
};

const expertDraw = stroke => {
strokeCanvas.clear();
strokeCanvas.drawStroke(stroke);
};

const clearStroke = () => {
strokeCanvas.remove();
strokeCanvas = null;
strokeStart = null;
};

const onNotification = notification => {
switch (notification.type) {
case 'open': {
// eslint-disable-next-line no-param-reassign
parentDOM.style.cursor = 'none';
if (menu) closeMenu();
clearStroke();
openMenu(notification.menu, notification.center);
startStroke(notification.center);
noviceMove(notification.position);
break;
}
case 'change': {
setActive((notification.active && notification.active.id) || null);
break;
}
case 'select':
case 'cancel':
// eslint-disable-next-line no-param-reassign
parentDOM.style.cursor = '';
if (menu) closeMenu();
clearStroke();
break;
case 'start':
// eslint-disable-next-line no-param-reassign
parentDOM.style.cursor = 'crosshair';
startStroke(notification.position);
break;
case 'draw':
expertDraw(notification.stroke);
break;
case 'move':
noviceMove(notification.position);
break;
default:
throw new Error(
`Invalid navigation notification type: ${notification.type}`
);
}
};

return navigation$.do({
next(notification) {
switch (notification.type) {
case 'open': {
closeMenuIfOpened();
openMenu(notification.menu, notification.center);
break;
}
case 'change': {
setActive((notification.active && notification.active.id) || null);
break;
}
case 'select':
case 'cancel':
closeMenuIfOpened();
break;
case 'draw':
case 'move':
// TODO: Provide a feedback.
break;
default:
throw new Error(
`Invalid navigation notification type: ${notification.type}`
);
try {
onNotification(notification);
} catch (e) {
// eslint-disable-next-line no-console
console.error(e);
throw e;
}
},
complete() {
closeMenuIfOpened();
// Make sure the menu is closed upon completion.
if (menu) closeMenu();
}
});
};
48 changes: 33 additions & 15 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import navigation from './navigation';
import createLayout from './layout';
import { createMenuLayout, createStrokeCanvas } from './layout';
import createModel from './model';
import { watchDrags } from './move';
import connectNavigationToLayout from './connect-navigation-to-layout';
Expand All @@ -25,32 +25,50 @@ import connectNavigationToLayout from './connect-navigation-to-layout';
export default (
items,
parentDOM,
options = {
minSelectionDist: 40,
minMenuSelectionDist: 80,
subMenuOpeningDelay: 25,
movementsThreshold: 5,
noviceDwellingTime: 1000 / 3
}
{
minSelectionDist = 40,
minMenuSelectionDist = 80,
subMenuOpeningDelay = 25,
movementsThreshold = 5,
noviceDwellingTime = 1000 / 3,
strokeColor = 'black',
strokeWidth = 4,
strokeStartPointRadius = 8
} = {}
) => {
// Create model and engine.
// Create the display options
const menuLayoutOptions = {};
const strokeCanvasOptions = {
lineColor: strokeColor,
lineWidth: strokeWidth,
pointRadius: strokeStartPointRadius
};

// Create model and navigation observable.
const model = createModel(items);
const navigation$ = navigation(
watchDrags(parentDOM),
model,
options
).do(({ originalEvent }) => {
const navigation$ = navigation(watchDrags(parentDOM), model, {
minSelectionDist,
minMenuSelectionDist,
subMenuOpeningDelay,
movementsThreshold,
noviceDwellingTime
}).do(({ originalEvent }) => {
// Prevent default on every notifications.
if (originalEvent) originalEvent.preventDefault();
});

// Connect the engine notifications to menu opening/closing.
const connectedNavigation$ = connectNavigationToLayout(
parentDOM,
navigation$,
createLayout
(parent, menuModel, center, current) =>
createMenuLayout(parent, menuModel, center, current, menuLayoutOptions),
parent => createStrokeCanvas(parent, strokeCanvasOptions)
).share();

// Subscribe to start the menu operations.
connectedNavigation$.subscribe();

// Return an observable on the selections.
return connectedNavigation$
.filter(notification => notification.type === 'select')
Expand Down
3 changes: 2 additions & 1 deletion src/layout/index.js
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { default } from './menu';
export { default as createMenuLayout } from './menu';
export { default as createStrokeCanvas } from './stroke';
19 changes: 12 additions & 7 deletions src/layout/menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,22 @@ import './menu.scss';
* @param {ItemModel} model - The model of the menu to open.
* @param {[int, int]} center - The center of the menu.
* @param {String} [current] - The currently active item.
* @param {Document} [doc] - The root document of the menu.
* Mostly useful for testing purposes.
* @param {Document} [options] - Menu options.
* @param {Document} [options.doc=document] - The root document of the menu.
* Mostly useful for testing purposes.
* @return {{ setActive, remove }} - The menu controls.
*/
const createMenu = (parent, model, center, current, doc = document) => {
const createMenu = (
parent,
model,
center,
current,
{ doc = document } = {}
) => {
// Create the DOM.
const domStr = template({ items: model.children, center });
const main = doc.createRange().createContextualFragment(domStr).firstChild;

parent.appendChild(main);

// Clear any active items.
Expand Down Expand Up @@ -46,10 +54,7 @@ const createMenu = (parent, model, center, current, doc = document) => {
if (current) setActive(current);

// Create the interface.
return {
setActive,
remove
};
return { setActive, remove };
};

export default createMenu;
3 changes: 1 addition & 2 deletions src/layout/menu.pug
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,4 @@
each item in items
.marking-menu-item(data-item-id=item.id data-item-angle=item.angle)
.marking-menu-line
.marking-menu-label=item.name
.marking-menu-middle
.marking-menu-label=item.name
13 changes: 1 addition & 12 deletions src/layout/menu.scss
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ $center-radius: 10px;
$line-thickness: 4px;
$line-color: $item-background;
$active-line-color: $active-item-background;
$middle-color: $active-line-color;
$first-item-angle: 0;

// Used for later calculation
Expand Down Expand Up @@ -111,14 +110,4 @@ $item-total-height: $item-height + $item-padding * 2;
border-bottom-left-radius: 0;
}
}
}

.marking-menu-middle {
position: absolute;
top: -$center-radius;
left: -$center-radius;
border-radius: $center-radius * 2;
background-color: $middle-color;
width: $center-radius * 2;
height: $center-radius * 2;
}
}
Loading

0 comments on commit 89e2b27

Please sign in to comment.