You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

преди 6 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 6 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 11 години
преди 12 години
преди 11 години
преди 12 години
преди 11 години
преди 12 години
преди 12 години
преди 12 години
преди 11 години
преди 12 години
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687
  1. /*
  2. * Copyright 2000-2018 Vaadin Ltd.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.vaadin.client.ui;
  17. import com.google.gwt.core.client.Scheduler;
  18. import com.google.gwt.dom.client.Element;
  19. import com.google.gwt.dom.client.Style.Display;
  20. import com.google.gwt.dom.client.Style.Overflow;
  21. import com.google.gwt.dom.client.Style.Unit;
  22. import com.google.gwt.event.dom.client.KeyCodes;
  23. import com.google.gwt.event.logical.shared.ValueChangeEvent;
  24. import com.google.gwt.event.logical.shared.ValueChangeHandler;
  25. import com.google.gwt.event.shared.HandlerRegistration;
  26. import com.google.gwt.user.client.Command;
  27. import com.google.gwt.user.client.DOM;
  28. import com.google.gwt.user.client.Event;
  29. import com.google.gwt.user.client.Window;
  30. import com.google.gwt.user.client.ui.HTML;
  31. import com.google.gwt.user.client.ui.HasValue;
  32. import com.vaadin.client.ApplicationConnection;
  33. import com.vaadin.client.BrowserInfo;
  34. import com.vaadin.client.WidgetUtil;
  35. import com.vaadin.shared.ui.slider.SliderOrientation;
  36. public class VSlider extends SimpleFocusablePanel
  37. implements Field, HasValue<Double>, SubPartAware {
  38. public static final String CLASSNAME = "v-slider";
  39. /**
  40. * Minimum size (width or height, depending on orientation) of the slider
  41. * base.
  42. */
  43. private static final int MIN_SIZE = 50;
  44. protected ApplicationConnection client;
  45. protected String id;
  46. protected boolean disabled;
  47. protected boolean readonly;
  48. private int acceleration = 1;
  49. protected double min;
  50. protected double max;
  51. protected int resolution;
  52. protected Double value;
  53. private boolean updateValueOnClick;
  54. protected SliderOrientation orientation = SliderOrientation.HORIZONTAL;
  55. private final HTML feedback = new HTML("", false);
  56. private final VOverlay feedbackPopup = new VOverlay(true, false) {
  57. {
  58. setOwner(VSlider.this);
  59. }
  60. @Override
  61. public void show() {
  62. super.show();
  63. updateFeedbackPosition();
  64. }
  65. };
  66. /* DOM element for slider's base */
  67. private final Element base;
  68. private static final int BASE_BORDER_WIDTH = 1;
  69. /* DOM element for slider's handle */
  70. private final Element handle;
  71. /* DOM element for decrement arrow */
  72. private final Element smaller;
  73. /* DOM element for increment arrow */
  74. private final Element bigger;
  75. /* Temporary dragging/animation variables */
  76. private boolean dragging = false;
  77. private VLazyExecutor delayedValueUpdater = new VLazyExecutor(100, () -> {
  78. fireValueChanged();
  79. acceleration = 1;
  80. });
  81. public VSlider() {
  82. super();
  83. base = DOM.createDiv();
  84. handle = DOM.createDiv();
  85. smaller = DOM.createDiv();
  86. bigger = DOM.createDiv();
  87. setStyleName(CLASSNAME);
  88. getElement().appendChild(bigger);
  89. getElement().appendChild(smaller);
  90. getElement().appendChild(base);
  91. base.appendChild(handle);
  92. // Hide initially
  93. smaller.getStyle().setDisplay(Display.NONE);
  94. bigger.getStyle().setDisplay(Display.NONE);
  95. sinkEvents(Event.MOUSEEVENTS | Event.ONMOUSEWHEEL | Event.KEYEVENTS
  96. | Event.FOCUSEVENTS | Event.TOUCHEVENTS);
  97. feedbackPopup.setWidget(feedback);
  98. }
  99. @Override
  100. public void setStyleName(String style) {
  101. updateStyleNames(style, false);
  102. }
  103. @Override
  104. public void setStylePrimaryName(String style) {
  105. updateStyleNames(style, true);
  106. }
  107. protected void updateStyleNames(String styleName,
  108. boolean isPrimaryStyleName) {
  109. feedbackPopup.removeStyleName(getStylePrimaryName() + "-feedback");
  110. removeStyleName(getStylePrimaryName() + "-vertical");
  111. if (isPrimaryStyleName) {
  112. super.setStylePrimaryName(styleName);
  113. } else {
  114. super.setStyleName(styleName);
  115. }
  116. feedbackPopup.addStyleName(getStylePrimaryName() + "-feedback");
  117. base.setClassName(getStylePrimaryName() + "-base");
  118. handle.setClassName(getStylePrimaryName() + "-handle");
  119. smaller.setClassName(getStylePrimaryName() + "-smaller");
  120. bigger.setClassName(getStylePrimaryName() + "-bigger");
  121. if (isVertical()) {
  122. addStyleName(getStylePrimaryName() + "-vertical");
  123. }
  124. }
  125. public void setFeedbackValue(double value) {
  126. feedback.setText(String.valueOf(value));
  127. }
  128. private void updateFeedbackPosition() {
  129. if (isVertical()) {
  130. feedbackPopup.setPopupPosition(
  131. handle.getAbsoluteLeft() + handle.getOffsetWidth(),
  132. handle.getAbsoluteTop() + handle.getOffsetHeight() / 2
  133. - feedbackPopup.getOffsetHeight() / 2);
  134. } else {
  135. feedbackPopup.setPopupPosition(
  136. handle.getAbsoluteLeft() + handle.getOffsetWidth() / 2
  137. - feedbackPopup.getOffsetWidth() / 2,
  138. handle.getAbsoluteTop() - feedbackPopup.getOffsetHeight());
  139. }
  140. }
  141. /** For internal use only. May be removed or replaced in the future. */
  142. public void buildBase() {
  143. final String styleAttribute = isVertical() ? "height" : "width";
  144. final String oppositeStyleAttribute = isVertical() ? "width" : "height";
  145. final String domProperty = isVertical() ? "offsetHeight"
  146. : "offsetWidth";
  147. // clear unnecessary opposite style attribute
  148. base.getStyle().clearProperty(oppositeStyleAttribute);
  149. /*
  150. * To resolve defect #13681 we should not return from method buildBase()
  151. * if slider has no parentElement, because such operations as
  152. * buildHandle() and setValues(), which are needed for Slider, are
  153. * called at the end of method buildBase(). And these methods will not
  154. * be called if there is no parentElement. So, instead of returning from
  155. * method buildBase() if there is no parentElement "if condition" is
  156. * applied to call code for parentElement only in case it exists.
  157. */
  158. if (getElement().hasParentElement()) {
  159. final Element p = getElement();
  160. if (p.getPropertyInt(domProperty) > MIN_SIZE) {
  161. if (isVertical()) {
  162. setHeight();
  163. } else {
  164. base.getStyle().clearProperty(styleAttribute);
  165. }
  166. } else {
  167. // Set minimum size and adjust after all components have
  168. // (supposedly) been drawn completely.
  169. base.getStyle().setPropertyPx(styleAttribute, MIN_SIZE);
  170. Scheduler.get().scheduleDeferred(new Command() {
  171. @Override
  172. public void execute() {
  173. final Element p = getElement();
  174. if (p.getPropertyInt(domProperty) > MIN_SIZE + 5
  175. || propertyNotNullOrEmpty(styleAttribute, p)) {
  176. if (isVertical()) {
  177. setHeight();
  178. } else {
  179. base.getStyle().clearProperty(styleAttribute);
  180. }
  181. // Ensure correct position
  182. setValue(value, false);
  183. }
  184. }
  185. // Style has non empty property
  186. private boolean propertyNotNullOrEmpty(
  187. final String styleAttribute, final Element p) {
  188. return p.getStyle().getProperty(styleAttribute) != null
  189. && !p.getStyle().getProperty(styleAttribute)
  190. .isEmpty();
  191. }
  192. });
  193. }
  194. }
  195. if (!isVertical()) {
  196. // Draw handle with a delay to allow base to gain maximum width
  197. Scheduler.get().scheduleDeferred(() -> {
  198. buildHandle();
  199. setValue(value, false);
  200. });
  201. } else {
  202. buildHandle();
  203. setValue(value, false);
  204. }
  205. // TODO attach listeners for focusing and arrow keys
  206. }
  207. void buildHandle() {
  208. final String handleAttribute = isVertical() ? "marginTop"
  209. : "marginLeft";
  210. final String oppositeHandleAttribute = isVertical() ? "marginLeft"
  211. : "marginTop";
  212. handle.getStyle().setProperty(handleAttribute, "0");
  213. // clear unnecessary opposite handle attribute
  214. handle.getStyle().clearProperty(oppositeHandleAttribute);
  215. }
  216. @Override
  217. public void onBrowserEvent(Event event) {
  218. if (disabled || readonly) {
  219. return;
  220. }
  221. final Element targ = DOM.eventGetTarget(event);
  222. if (DOM.eventGetType(event) == Event.ONMOUSEWHEEL) {
  223. processMouseWheelEvent(event);
  224. } else if (dragging || targ == handle) {
  225. processHandleEvent(event);
  226. } else if (targ.equals(base)
  227. && DOM.eventGetType(event) == Event.ONMOUSEUP
  228. && updateValueOnClick) {
  229. processBaseEvent(event);
  230. feedbackPopup.show();
  231. } else if (targ == smaller) {
  232. decreaseValue(true);
  233. } else if (targ == bigger) {
  234. increaseValue(true);
  235. } else if (isNavigationEvent(event)) {
  236. if (handleNavigation(event.getKeyCode(), event.getCtrlKey(),
  237. event.getShiftKey())) {
  238. feedbackPopup.show();
  239. delayedValueUpdater.trigger();
  240. DOM.eventPreventDefault(event);
  241. DOM.eventCancelBubble(event, true);
  242. }
  243. } else if (targ.equals(getElement())
  244. && DOM.eventGetType(event) == Event.ONFOCUS) {
  245. feedbackPopup.show();
  246. } else if (targ.equals(getElement())
  247. && DOM.eventGetType(event) == Event.ONBLUR) {
  248. feedbackPopup.hide();
  249. } else if (DOM.eventGetType(event) == Event.ONMOUSEDOWN) {
  250. feedbackPopup.show();
  251. }
  252. if (WidgetUtil.isTouchEvent(event)) {
  253. event.preventDefault(); // avoid simulated events
  254. event.stopPropagation();
  255. }
  256. }
  257. private boolean isNavigationEvent(Event event) {
  258. if (BrowserInfo.get().isGecko()
  259. && BrowserInfo.get().getGeckoVersion() < 65) {
  260. return DOM.eventGetType(event) == Event.ONKEYPRESS;
  261. } else {
  262. return DOM.eventGetType(event) == Event.ONKEYDOWN;
  263. }
  264. }
  265. private void processMouseWheelEvent(final Event event) {
  266. final int dir = DOM.eventGetMouseWheelVelocityY(event);
  267. if (dir < 0) {
  268. increaseValue(false);
  269. } else {
  270. decreaseValue(false);
  271. }
  272. delayedValueUpdater.trigger();
  273. DOM.eventPreventDefault(event);
  274. DOM.eventCancelBubble(event, true);
  275. }
  276. private void processHandleEvent(Event event) {
  277. switch (DOM.eventGetType(event)) {
  278. case Event.ONMOUSEDOWN:
  279. case Event.ONTOUCHSTART:
  280. if (!disabled && !readonly) {
  281. focus();
  282. feedbackPopup.show();
  283. dragging = true;
  284. handle.setClassName(getStylePrimaryName() + "-handle");
  285. handle.addClassName(getStylePrimaryName() + "-handle-active");
  286. DOM.setCapture(getElement());
  287. DOM.eventPreventDefault(event); // prevent selecting text
  288. DOM.eventCancelBubble(event, true);
  289. event.stopPropagation();
  290. }
  291. break;
  292. case Event.ONMOUSEMOVE:
  293. case Event.ONTOUCHMOVE:
  294. if (dragging) {
  295. setValueByEvent(event, false);
  296. updateFeedbackPosition();
  297. event.stopPropagation();
  298. }
  299. break;
  300. case Event.ONTOUCHEND:
  301. feedbackPopup.hide();
  302. case Event.ONMOUSEUP:
  303. // feedbackPopup.hide();
  304. dragging = false;
  305. handle.setClassName(getStylePrimaryName() + "-handle");
  306. DOM.releaseCapture(getElement());
  307. setValueByEvent(event, true);
  308. event.stopPropagation();
  309. break;
  310. default:
  311. break;
  312. }
  313. }
  314. private void processBaseEvent(Event event) {
  315. if (!disabled && !readonly && !dragging) {
  316. setValueByEvent(event, true);
  317. DOM.eventCancelBubble(event, true);
  318. }
  319. }
  320. private void decreaseValue(boolean updateToServer) {
  321. setValue(new Double(value.doubleValue() - Math.pow(10, -resolution)),
  322. updateToServer);
  323. }
  324. private void increaseValue(boolean updateToServer) {
  325. setValue(new Double(value.doubleValue() + Math.pow(10, -resolution)),
  326. updateToServer);
  327. }
  328. private void setValueByEvent(Event event, boolean updateToServer) {
  329. double v = min; // Fallback to min
  330. final int coord = getEventPosition(event);
  331. final int handleSize, baseSize, baseOffset;
  332. if (isVertical()) {
  333. handleSize = handle.getOffsetHeight();
  334. baseSize = base.getOffsetHeight();
  335. baseOffset = base.getAbsoluteTop() - Window.getScrollTop()
  336. - handleSize / 2;
  337. } else {
  338. handleSize = handle.getOffsetWidth();
  339. baseSize = base.getOffsetWidth();
  340. baseOffset = base.getAbsoluteLeft() - Window.getScrollLeft()
  341. + handleSize / 2;
  342. }
  343. if (isVertical()) {
  344. v = (baseSize - (coord - baseOffset))
  345. / (double) (baseSize - handleSize) * (max - min) + min;
  346. } else {
  347. v = (coord - baseOffset) / (double) (baseSize - handleSize)
  348. * (max - min) + min;
  349. }
  350. if (v < min) {
  351. v = min;
  352. } else if (v > max) {
  353. v = max;
  354. }
  355. setValue(v, updateToServer);
  356. }
  357. /**
  358. * TODO consider extracting touches support to an impl class specific for
  359. * webkit (only browser that really supports touches).
  360. *
  361. * @param event
  362. * @return
  363. */
  364. protected int getEventPosition(Event event) {
  365. if (isVertical()) {
  366. return WidgetUtil.getTouchOrMouseClientY(event);
  367. } else {
  368. return WidgetUtil.getTouchOrMouseClientX(event);
  369. }
  370. }
  371. public void iLayout() {
  372. if (isVertical()) {
  373. setHeight();
  374. }
  375. // Update handle position
  376. setValue(value, false);
  377. }
  378. private void setHeight() {
  379. // Calculate decoration size
  380. base.getStyle().setHeight(0, Unit.PX);
  381. base.getStyle().setOverflow(Overflow.HIDDEN);
  382. int h = getElement().getOffsetHeight();
  383. if (h < MIN_SIZE) {
  384. h = MIN_SIZE;
  385. }
  386. base.getStyle().setHeight(h, Unit.PX);
  387. base.getStyle().clearOverflow();
  388. }
  389. private void fireValueChanged() {
  390. ValueChangeEvent.fire(VSlider.this, value);
  391. }
  392. /**
  393. * Handles the keyboard events handled by the Slider.
  394. *
  395. * @param keycode
  396. * The key code received
  397. * @param ctrl
  398. * Whether {@code CTRL} was pressed
  399. * @param shift
  400. * Whether {@code SHIFT} was pressed
  401. * @return true if the navigation event was handled
  402. */
  403. public boolean handleNavigation(int keycode, boolean ctrl, boolean shift) {
  404. // No support for ctrl moving
  405. if (ctrl) {
  406. return false;
  407. }
  408. if (keycode == getNavigationUpKey() && isVertical()
  409. || keycode == getNavigationRightKey() && !isVertical()) {
  410. if (shift) {
  411. for (int a = 0; a < acceleration; a++) {
  412. increaseValue(false);
  413. }
  414. acceleration++;
  415. } else {
  416. increaseValue(false);
  417. }
  418. return true;
  419. } else if (keycode == getNavigationDownKey() && isVertical()
  420. || keycode == getNavigationLeftKey() && !isVertical()) {
  421. if (shift) {
  422. for (int a = 0; a < acceleration; a++) {
  423. decreaseValue(false);
  424. }
  425. acceleration++;
  426. } else {
  427. decreaseValue(false);
  428. }
  429. return true;
  430. }
  431. return false;
  432. }
  433. /**
  434. * Get the key that increases the vertical slider. By default it is the up
  435. * arrow key but by overriding this you can change the key to whatever you
  436. * want.
  437. *
  438. * @return The keycode of the key
  439. */
  440. protected int getNavigationUpKey() {
  441. return KeyCodes.KEY_UP;
  442. }
  443. /**
  444. * Get the key that decreases the vertical slider. By default it is the down
  445. * arrow key but by overriding this you can change the key to whatever you
  446. * want.
  447. *
  448. * @return The keycode of the key
  449. */
  450. protected int getNavigationDownKey() {
  451. return KeyCodes.KEY_DOWN;
  452. }
  453. /**
  454. * Get the key that decreases the horizontal slider. By default it is the
  455. * left arrow key but by overriding this you can change the key to whatever
  456. * you want.
  457. *
  458. * @return The keycode of the key
  459. */
  460. protected int getNavigationLeftKey() {
  461. return KeyCodes.KEY_LEFT;
  462. }
  463. /**
  464. * Get the key that increases the horizontal slider. By default it is the
  465. * right arrow key but by overriding this you can change the key to whatever
  466. * you want.
  467. *
  468. * @return The keycode of the key
  469. */
  470. protected int getNavigationRightKey() {
  471. return KeyCodes.KEY_RIGHT;
  472. }
  473. public void setConnection(ApplicationConnection client) {
  474. this.client = client;
  475. }
  476. public void setId(String id) {
  477. this.id = id;
  478. }
  479. public void setDisabled(boolean disabled) {
  480. this.disabled = disabled;
  481. }
  482. public void setReadOnly(boolean readonly) {
  483. this.readonly = readonly;
  484. }
  485. private boolean isVertical() {
  486. return orientation == SliderOrientation.VERTICAL;
  487. }
  488. public void setOrientation(SliderOrientation orientation) {
  489. if (this.orientation != orientation) {
  490. this.orientation = orientation;
  491. updateStyleNames(getStylePrimaryName(), true);
  492. }
  493. }
  494. public void setMinValue(double value) {
  495. min = value;
  496. }
  497. public void setMaxValue(double value) {
  498. max = value;
  499. }
  500. public void setResolution(int resolution) {
  501. this.resolution = resolution;
  502. }
  503. @Override
  504. public HandlerRegistration addValueChangeHandler(
  505. ValueChangeHandler<Double> handler) {
  506. return addHandler(handler, ValueChangeEvent.getType());
  507. }
  508. @Override
  509. public Double getValue() {
  510. return value;
  511. }
  512. @Override
  513. public void setValue(Double value) {
  514. if (value < min) {
  515. value = min;
  516. } else if (value > max) {
  517. value = max;
  518. }
  519. // Update handle position
  520. final String styleAttribute = isVertical() ? "marginTop" : "marginLeft";
  521. final String domProperty = isVertical() ? "offsetHeight"
  522. : "offsetWidth";
  523. final int handleSize = handle.getPropertyInt(domProperty);
  524. final int baseSize = base.getPropertyInt(domProperty)
  525. - 2 * BASE_BORDER_WIDTH;
  526. final int range = baseSize - handleSize;
  527. double v = value.doubleValue();
  528. // Round value to resolution
  529. if (resolution > 0) {
  530. v = Math.round(v * Math.pow(10, resolution));
  531. v = v / Math.pow(10, resolution);
  532. } else {
  533. v = Math.round(v);
  534. }
  535. final double valueRange = max - min;
  536. double p = 0;
  537. if (valueRange > 0) {
  538. p = range * ((v - min) / valueRange);
  539. }
  540. if (p < 0) {
  541. p = 0;
  542. }
  543. if (isVertical()) {
  544. p = range - p;
  545. }
  546. final double pos = p;
  547. handle.getStyle().setPropertyPx(styleAttribute, (int) Math.round(pos));
  548. // Update value
  549. this.value = new Double(v);
  550. setFeedbackValue(v);
  551. }
  552. @Override
  553. public void setValue(Double value, boolean fireEvents) {
  554. if (value == null) {
  555. return;
  556. }
  557. setValue(value);
  558. if (fireEvents) {
  559. fireValueChanged();
  560. }
  561. }
  562. @Override
  563. public com.google.gwt.user.client.Element getSubPartElement(
  564. String subPart) {
  565. if (subPart.equals("popup")) {
  566. feedbackPopup.show();
  567. return feedbackPopup.getElement();
  568. }
  569. return null;
  570. }
  571. @Override
  572. public String getSubPartName(
  573. com.google.gwt.user.client.Element subElement) {
  574. if (feedbackPopup.getElement().isOrHasChild(subElement)) {
  575. return "popup";
  576. }
  577. return null;
  578. }
  579. /**
  580. * Specifies whether or not click event should update the Slider's value.
  581. *
  582. * @param updateValueOnClick
  583. */
  584. public void setUpdateValueOnClick(boolean updateValueOnClick) {
  585. this.updateValueOnClick = updateValueOnClick;
  586. }
  587. }