| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file | |
| 2 // for details. All rights reserved. Use of this source code is governed by a | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 typedef void SelectHandler(String menuText); | |
| 6 | |
| 7 /** | |
| 8 * This implements a horizontal menu bar with a sliding triangle arrow | |
| 9 * that points at the currently selected item. | |
| 10 */ | |
| 11 class SliderMenu extends View { | |
| 12 static final int TRIANGLE_WIDTH = 24; | |
| 13 | |
| 14 // currently selected menu item | |
| 15 Element selectedItem; | |
| 16 | |
| 17 // This holds the element where a touchstart occured. (This is set | |
| 18 // in touchstart, and cleared in touchend.) If this is null, then a | |
| 19 // touch operation is not in progress. | |
| 20 // TODO(mattsh) - move this to a touch mixin | |
| 21 Element touchItem; | |
| 22 | |
| 23 /** | |
| 24 * Callback function that we call when the user chooses something from | |
| 25 * the menu. This is passed the menu item text. | |
| 26 */ | |
| 27 SelectHandler onSelect; | |
| 28 | |
| 29 List<String> _menuItems; | |
| 30 | |
| 31 SliderMenu(this._menuItems, this.onSelect) : super() {} | |
| 32 | |
| 33 Element render() { | |
| 34 // Create a div for each menu item. | |
| 35 final items = new StringBuffer(); | |
| 36 for (final item in _menuItems) { | |
| 37 items.add('<div class="sm-item">$item</div>'); | |
| 38 } | |
| 39 | |
| 40 // Create a root node to hold this view. | |
| 41 return new Element.html(''' | |
| 42 <div class="sm-root"> | |
| 43 <div class="sm-item-box"> | |
| 44 <div class="sm-item-filler"></div> | |
| 45 $items | |
| 46 <div class="sm-item-filler"></div> | |
| 47 </div> | |
| 48 <div class="sm-slider-box"> | |
| 49 <div class="sm-triangle"></div> | |
| 50 </div> | |
| 51 </div> | |
| 52 '''); | |
| 53 } | |
| 54 | |
| 55 void enterDocument() { | |
| 56 // select the first item | |
| 57 // todo(jacobr): too much actual work is performed in enterDocument. | |
| 58 // Ideally, enterDocument should do nothing more than redecorate a view | |
| 59 // and perhaps calculating the correct child sizes for edge cases that | |
| 60 // cannot be handled by the browser layout engine. | |
| 61 selectItem(node.query('.sm-item'), false); | |
| 62 | |
| 63 // TODO(mattsh), abstract this somehow into a touch click mixin | |
| 64 if (Device.supportsTouch) { | |
| 65 node.on.touchStart.add((event) { | |
| 66 touchItem = itemOfTouchEvent(event); | |
| 67 if (touchItem != null) { | |
| 68 selectItemText(touchItem); | |
| 69 } | |
| 70 event.preventDefault(); | |
| 71 }); | |
| 72 node.on.touchEnd.add((event) { | |
| 73 if (touchItem != null) { | |
| 74 if (itemOfTouchEvent(event) == touchItem) { | |
| 75 selectItem(touchItem, true); | |
| 76 } else { | |
| 77 // the Touch target is somewhere other where than the touchstart | |
| 78 // occured, so revert the selected menu text back to where it was | |
| 79 // before the touchstart, | |
| 80 selectItemText(selectedItem); | |
| 81 } | |
| 82 // touch operation has ended | |
| 83 touchItem = null; | |
| 84 } | |
| 85 event.preventDefault(); | |
| 86 }); | |
| 87 } else { | |
| 88 node.on.click.add((event) => selectItem(event.target, true)); | |
| 89 } | |
| 90 | |
| 91 window.on.resize.add((Event event) => updateIndicator(false)); | |
| 92 } | |
| 93 | |
| 94 /** | |
| 95 * Walks the parent chain of the first Touch target to find the first ancestor | |
| 96 * that has sm-item class. | |
| 97 */ | |
| 98 Element itemOfTouchEvent(event) { | |
| 99 Node node = event.changedTouches[0].target; | |
| 100 return itemOfNode(node); | |
| 101 } | |
| 102 | |
| 103 Element itemOfNode(Node node) { | |
| 104 // TODO(jmesserly): workaround for bug 5399957, document.parent == document | |
| 105 while (node != null && node != document) { | |
| 106 if (node is Element) { | |
| 107 Element element = node; | |
| 108 if (element.classes.contains('sm-item')) { | |
| 109 return element; | |
| 110 } | |
| 111 } | |
| 112 node = node.parent; | |
| 113 } | |
| 114 return null; | |
| 115 } | |
| 116 | |
| 117 void selectItemText(Element item) { | |
| 118 // unselect all menu items | |
| 119 for (final sliderItem in node.queryAll('.sm-item')) { | |
| 120 sliderItem.classes.remove('sel'); | |
| 121 } | |
| 122 | |
| 123 // select the item the user clicked on | |
| 124 item.classes.add('sel'); | |
| 125 } | |
| 126 | |
| 127 void selectItem(Element item, bool animate) { | |
| 128 if (!item.classes.contains('sm-item')) { | |
| 129 return; | |
| 130 } | |
| 131 | |
| 132 selectedItem = item; | |
| 133 selectItemText(item); | |
| 134 updateIndicator(animate); | |
| 135 onSelect(item.text); | |
| 136 } | |
| 137 | |
| 138 void selectNext(bool animate) { | |
| 139 final result = node.query('.sm-item.sel').nextElementSibling; | |
| 140 if (result != null) { | |
| 141 selectItem(result, animate); | |
| 142 } | |
| 143 } | |
| 144 | |
| 145 void selectPrevious(bool animate) { | |
| 146 final result = node.query('.sm-item.sel').previousElementSibling; | |
| 147 if (result != null) { | |
| 148 selectItem(result, animate); | |
| 149 } | |
| 150 } | |
| 151 | |
| 152 /** | |
| 153 * animate - if true, then animate the movement of the triangle slider | |
| 154 */ | |
| 155 void updateIndicator(bool animate) { | |
| 156 if (selectedItem != null) { | |
| 157 // calculate where we want to put the triangle | |
| 158 selectedItem.rect.then((ElementRect rect) { | |
| 159 num x = rect.offset.left + | |
| 160 rect.offset.width / 2 - TRIANGLE_WIDTH / 2; | |
| 161 _moveIndicator(x, animate); | |
| 162 }); | |
| 163 } else { | |
| 164 _moveIndicator(0, animate); | |
| 165 } | |
| 166 } | |
| 167 | |
| 168 void _moveIndicator(num x, bool animate) { | |
| 169 // find the slider filler (the div element to the left of the | |
| 170 // triangle) set its width the push the triangle to where we want it. | |
| 171 String duration = animate ? '.3s' : '0s'; | |
| 172 final triangle = node.query('.sm-triangle'); | |
| 173 triangle.style.transitionDuration = duration; | |
| 174 FxUtil.setWebkitTransform(triangle, x, 0); | |
| 175 } | |
| 176 } | |
| OLD | NEW |