Files
sdk/samples-dev/swarm/swarm_ui_lib/view/SliderMenu.dart
T
Aske Simon Christensen 4004194c29 Clean up super calls in swarm.
Remove redundant super calls and move all other super calls to the end
of the initializer list, as required by Dart 2.

Change-Id: I2b0968b9c6508d00a3379c225470d5d9c0ec9f78
Reviewed-on: https://dart-review.googlesource.com/69967
Reviewed-by: Stephen Adams <sra@google.com>
Commit-Queue: Stephen Adams <sra@google.com>
2018-08-21 17:56:13 +00:00

180 lines
5.2 KiB
Dart

// Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
part of view;
typedef void SelectHandler(String menuText);
/**
* This implements a horizontal menu bar with a sliding triangle arrow
* that points at the currently selected item.
*/
class SliderMenu extends View {
static const int TRIANGLE_WIDTH = 24;
// currently selected menu item
Element selectedItem;
// This holds the element where a touchstart occured. (This is set
// in touchstart, and cleared in touchend.) If this is null, then a
// touch operation is not in progress.
// TODO(mattsh) - move this to a touch mixin
Element touchItem;
/**
* Callback function that we call when the user chooses something from
* the menu. This is passed the menu item text.
*/
SelectHandler onSelect;
List<String> _menuItems;
SliderMenu(this._menuItems, this.onSelect);
Element render() {
// Create a div for each menu item.
final items = new StringBuffer();
for (final item in _menuItems) {
items.write('<div class="sm-item">$item</div>');
}
// Create a root node to hold this view.
return new Element.html('''
<div class="sm-root">
<div class="sm-item-box">
<div class="sm-item-filler"></div>
$items
<div class="sm-item-filler"></div>
</div>
<div class="sm-slider-box">
<div class="sm-triangle"></div>
</div>
</div>
''');
}
void enterDocument() {
// select the first item
// todo(jacobr): too much actual work is performed in enterDocument.
// Ideally, enterDocument should do nothing more than redecorate a view
// and perhaps calculating the correct child sizes for edge cases that
// cannot be handled by the browser layout engine.
selectItem(node.querySelector('.sm-item'), false);
// TODO(mattsh), abstract this somehow into a touch click mixin
if (Device.supportsTouch) {
node.onTouchStart.listen((event) {
touchItem = itemOfTouchEvent(event);
if (touchItem != null) {
selectItemText(touchItem);
}
event.preventDefault();
});
node.onTouchEnd.listen((event) {
if (touchItem != null) {
if (itemOfTouchEvent(event) == touchItem) {
selectItem(touchItem, true);
} else {
// the Touch target is somewhere other where than the touchstart
// occured, so revert the selected menu text back to where it was
// before the touchstart,
selectItemText(selectedItem);
}
// touch operation has ended
touchItem = null;
}
event.preventDefault();
});
} else {
node.onClick.listen((event) => selectItem(event.target, true));
}
window.onResize.listen((Event event) => updateIndicator(false));
}
/**
* Walks the parent chain of the first Touch target to find the first ancestor
* that has sm-item class.
*/
Element itemOfTouchEvent(event) {
Node node = event.changedTouches[0].target;
return itemOfNode(node);
}
Element itemOfNode(Node node) {
// TODO(jmesserly): workaround for bug 5399957, document.parent == document
while (node != null && node != document) {
if (node is Element) {
Element element = node;
if (element.classes.contains('sm-item')) {
return element;
}
}
node = node.parent;
}
return null;
}
void selectItemText(Element item) {
// unselect all menu items
for (final sliderItem in node.querySelectorAll('.sm-item')) {
sliderItem.classes.remove('sel');
}
// select the item the user clicked on
item.classes.add('sel');
}
void selectItem(Element item, bool animate) {
if (!item.classes.contains('sm-item')) {
return;
}
selectedItem = item;
selectItemText(item);
updateIndicator(animate);
onSelect(item.text);
}
void selectNext(bool animate) {
final result = node.querySelector('.sm-item.sel').nextElementSibling;
if (result != null) {
selectItem(result, animate);
}
}
void selectPrevious(bool animate) {
final result = node.querySelector('.sm-item.sel').previousElementSibling;
if (result != null) {
selectItem(result, animate);
}
}
/**
* animate - if true, then animate the movement of the triangle slider
*/
void updateIndicator(bool animate) {
if (selectedItem != null) {
// calculate where we want to put the triangle
scheduleMicrotask(() {
num x = selectedItem.offset.left +
selectedItem.offset.width / 2 -
TRIANGLE_WIDTH / 2;
_moveIndicator(x, animate);
});
} else {
_moveIndicator(0, animate);
}
}
void _moveIndicator(num x, bool animate) {
// find the slider filler (the div element to the left of the
// triangle) set its width the push the triangle to where we want it.
String duration = animate ? '.3s' : '0s';
final triangle = node.querySelector('.sm-triangle');
triangle.style.transitionDuration = duration;
FxUtil.setWebkitTransform(triangle, x, 0);
}
}