1 /*
2 * Copyright (C) 2008, 2011 Emweb bv, Herent, Belgium.
3 *
4 * See the LICENSE file for terms of use.
5 */
6 #include "Wt/WApplication.h"
7 #include "Wt/WContainerWidget.h"
8 #include "Wt/WEnvironment.h"
9 #include "Wt/WPaintDevice.h"
10 #include "Wt/WPaintedWidget.h"
11 #include "Wt/WPainter.h"
12 #include "Wt/WSlider.h"
13 #include "Wt/WStringStream.h"
14 #include "Wt/WBootstrap5Theme.h"
15
16 #include "DomElement.h"
17 #include "WebUtils.h"
18
19 #include <memory>
20
21 /*
22 * FIXME: move styling to the theme classes
23 */
24 namespace Wt {
25
26 const Wt::WFlags<WSlider::TickPosition> WSlider::NoTicks = None;
27 const Wt::WFlags<WSlider::TickPosition> WSlider::TicksBothSides
28 = WSlider::TickPosition::TicksAbove | WSlider::TickPosition::TicksBelow;
29
30 const char *WSlider::INPUT_SIGNAL = "input";
31
32 class PaintedSlider final : public WPaintedWidget
33 {
34 public:
35 PaintedSlider(WSlider *slider);
36 virtual ~PaintedSlider();
37
38 void connectSlots();
39 void updateState();
40 void updateSliderPosition();
41 void doUpdateDom(DomElement& element, bool all);
42
43 void sliderResized(const WLength& width, const WLength& height);
44
45 protected:
46 virtual void paintEvent(WPaintDevice *paintDevice) override;
47
48 private:
49 WSlider *slider_;
50
51 JSignal<int> sliderReleased_;
52 JSlot mouseDownJS_, mouseMovedJS_, mouseUpJS_;
53
54 std::unique_ptr<WInteractWidget> handle_, fill_;
55
range()56 int range() const { return slider_->maximum() - slider_->minimum(); }
57 double w() const;
58 double h() const;
59
60 void onSliderClick(const WMouseEvent& event);
61 void onSliderReleased(int u);
62 };
63
paintEvent(WPaintDevice * paintDevice)64 void PaintedSlider::paintEvent(WPaintDevice *paintDevice)
65 {
66 int tickInterval = slider_->tickInterval();
67 int r = range();
68
69 if (r == 0) {
70 // Empty range, don't paint anything
71 return;
72 }
73
74 if (tickInterval == 0)
75 tickInterval = r / 2;
76
77 int numTicks = tickInterval == 0 ? 2 : r / tickInterval + 1;
78 if (numTicks < 1)
79 return;
80
81 int w = 0, h = 0;
82
83 switch (slider_->orientation()) {
84 case Orientation::Horizontal:
85 w = (int)paintDevice->width().toPixels();
86 h = (int)paintDevice->height().toPixels();
87 break;
88 case Orientation::Vertical:
89 w = (int)paintDevice->height().toPixels();
90 h = (int)paintDevice->width().toPixels();
91 }
92
93 double tickStep = ((double)w + 10 - slider_->handleWidth()) / (numTicks - 1);
94
95 WPainter painter(paintDevice);
96
97 for (int i = 0; i < numTicks; ++i) {
98 int v = slider_->minimum() + i * tickInterval;
99 int x = -5 + slider_->handleWidth()/2 + (int) (i * tickStep);
100
101 switch (slider_->orientation()) {
102 case Orientation::Horizontal:
103 slider_->paintTick(painter, v, x, h/2);
104 break;
105 case Orientation::Vertical:
106 slider_->paintTick(painter, v, h/2, w - x);
107 }
108 }
109 }
110
PaintedSlider(WSlider * slider)111 PaintedSlider::PaintedSlider(WSlider *slider)
112 : WPaintedWidget(),
113 slider_(slider),
114 sliderReleased_(this, "released")
115 {
116 setStyleClass("Wt-slider-bg");
117
118 slider_->addStyleClass
119 (std::string("Wt-slider-")
120 + (slider_->orientation() == Orientation::Horizontal ? "h" : "v"));
121
122 if (slider_->positionScheme() == PositionScheme::Static) {
123 slider_->setPositionScheme(PositionScheme::Relative);
124 slider_->setOffsets(0, Side::Left | Side::Top);
125 }
126
127 auto fill = std::unique_ptr<WInteractWidget>(new WContainerWidget());
128 manageWidget(fill_, std::move(fill));
129 auto handle = slider_->createHandle();
130 manageWidget(handle_, std::move(handle));
131
132 fill_->setPositionScheme(PositionScheme::Absolute);
133 fill_->setStyleClass("fill");
134
135 handle_->setPositionScheme(PositionScheme::Absolute);
136 handle_->setStyleClass("handle");
137
138 handle_->setCanReceiveFocus(true);
139 slider_->setCanReceiveFocus(true);
140
141 connectSlots();
142 }
143
connectSlots()144 void PaintedSlider::connectSlots()
145 {
146 if (Wt::WApplication::instance()->environment().ajax()) {
147 handle_->mouseWentDown().connect(mouseDownJS_);
148 handle_->touchStarted().connect(mouseDownJS_);
149 handle_->mouseMoved().connect(mouseMovedJS_);
150 handle_->touchMoved().connect(mouseMovedJS_);
151 handle_->mouseWentUp().connect(mouseUpJS_);
152 handle_->touchEnded().connect(mouseUpJS_);
153
154 slider_->clicked().connect(this, &PaintedSlider::onSliderClick);
155
156 sliderReleased_.connect(this, &PaintedSlider::onSliderReleased);
157 }
158 }
159
~PaintedSlider()160 PaintedSlider::~PaintedSlider()
161 {
162 manageWidget(fill_, std::unique_ptr<WInteractWidget>());
163 manageWidget(handle_, std::unique_ptr<WInteractWidget>());
164 }
165
w()166 double PaintedSlider::w() const
167 {
168 return width().toPixels()
169 + (slider_->orientation() == Orientation::Horizontal ? 10 : 0);
170 }
171
h()172 double PaintedSlider::h() const
173 {
174 return height().toPixels()
175 + (slider_->orientation() == Orientation::Vertical ? 10 : 0);
176 }
177
updateState()178 void PaintedSlider::updateState()
179 {
180 bool rtl = WApplication::instance()->layoutDirection() ==
181 LayoutDirection::RightToLeft;
182
183 Orientation o = slider_->orientation();
184
185 if (o == Orientation::Horizontal) {
186 handle_->resize(slider_->handleWidth(), h());
187 handle_->setOffsets(0, Side::Top);
188 } else {
189 handle_->resize(w(), slider_->handleWidth());
190 handle_->setOffsets(0, Side::Left);
191 }
192
193 double l = o == Orientation::Horizontal ? w() : h();
194 double pixelsPerUnit = (l - slider_->handleWidth()) / range();
195
196 std::string dir;
197 std::string size;
198 if (o == Orientation::Horizontal) {
199 dir = rtl ? "right" : "left";
200 size = "width";
201 } else {
202 dir = "top";
203 size = "height";
204 }
205
206 char u = (o == Orientation::Horizontal ? 'x' : 'y');
207
208 double max = l - slider_->handleWidth();
209 bool horizontal = o == Orientation::Horizontal;
210
211 char buf[30]; // Buffer for round_js_str
212
213 /*
214 * Note: cancelling the mouseDown event prevents the selection behaviour
215 */
216 WStringStream mouseDownJS;
217 mouseDownJS << "obj.setAttribute('down', " WT_CLASS
218 << ".widgetCoordinates(obj, event)." << u
219 << ");";
220
221 WStringStream computeD; // = 'u' position relative to background, corrected for slider
222 computeD << "var objh = " << handle_->jsRef() << ","
223 << "objf = " << fill_->jsRef() << ","
224 << "objb = " << slider_->jsRef() << ","
225 << "page_u = WT.pageCoordinates(event)." << u << ","
226 << "widget_page_u = WT.widgetPageCoordinates(objb)." << u << ","
227 << "pos = page_u - widget_page_u,"
228 << "rtl = " << rtl << ","
229 << "horizontal = " << horizontal << ";"
230 << "if (rtl && horizontal)";
231 computeD << "pos = " << Utils::round_js_str(l, 3, buf) << " - pos;";
232 computeD << "var d = pos - down;";
233
234 WStringStream mouseMovedJS;
235 mouseMovedJS << "var down = obj.getAttribute('down');"
236 << "var WT = " WT_CLASS ";"
237 << "if (down != null && down != '') {"
238 << computeD.str();
239 mouseMovedJS << "d = Math.max(0, Math.min(d, " << Utils::round_js_str(max, 3, buf) << "));";
240 mouseMovedJS << "var v = Math.round(d/" << Utils::round_js_str(pixelsPerUnit, 3, buf) << ");";
241 mouseMovedJS << "var intd = v*" << Utils::round_js_str(pixelsPerUnit, 3, buf) << ";";
242 mouseMovedJS << "if (Math.abs(WT.pxself(objh, '" << dir
243 << "') - intd) > 1) {"
244 << "objf.style." << size << " = ";
245 if (o == Orientation::Vertical) {
246 mouseMovedJS << '(' << Utils::round_js_str(max, 3, buf);
247 mouseMovedJS << " - intd + " << (slider_->handleWidth() / 2)
248 << ")";
249 } else
250 mouseMovedJS << "intd + " << (slider_->handleWidth() / 2);
251 mouseMovedJS << " + 'px';"
252 << "objh.style." << dir << " = intd + 'px';"
253 << "var vs = ";
254 if (o == Orientation::Horizontal)
255 mouseMovedJS << "v + " << slider_->minimum();
256 else
257 mouseMovedJS << slider_->maximum() << " - v";
258 mouseMovedJS << ";"
259 << "var f = objb.onValueChange;"
260 << "if (f) f(vs);";
261
262 if (slider_->sliderMoved().needsUpdate(true)) {
263 #ifndef WT_TARGET_JAVA
264 mouseMovedJS << slider_->sliderMoved().createCall({"vs"});
265 #else
266 mouseMovedJS << slider_->sliderMoved().createCall("vs");
267 #endif
268 }
269
270 mouseMovedJS << "}"
271 << "}";
272
273 WStringStream mouseUpJS;
274 mouseUpJS << "var down = obj.getAttribute('down');"
275 << "var WT = " WT_CLASS ";"
276 << "if (down != null && down != '') {"
277 << computeD.str()
278 << "d += " << (slider_->handleWidth() / 2) << ";"
279 #ifndef WT_TARGET_JAVA
280 << sliderReleased_.createCall({"Math.round(d)"})
281 #else
282 << sliderReleased_.createCall("Math.round(d)")
283 #endif
284 << "obj.removeAttribute('down');"
285 << "}";
286
287 bool enabled = !slider_->isDisabled();
288
289 mouseDownJS_.setJavaScript(std::string("function(obj, event) {")
290 + (enabled ? mouseDownJS.str() : "")
291 + "}");
292 mouseMovedJS_.setJavaScript(std::string("function(obj, event) {")
293 + (enabled ? mouseMovedJS.str() : "")
294 + "}");
295 mouseUpJS_.setJavaScript(std::string("function(obj, event) {")
296 + (enabled ? mouseUpJS.str() : "")
297 + "}");
298
299 update();
300 updateSliderPosition();
301 }
302
doUpdateDom(DomElement & element,bool all)303 void PaintedSlider::doUpdateDom(DomElement& element, bool all)
304 {
305 if (all) {
306 WApplication *app = WApplication::instance();
307
308 DomElement *west = DomElement::createNew(DomElementType::DIV);
309 west->setProperty(Property::Class, "Wt-w");
310 element.addChild(west);
311
312 DomElement *east = DomElement::createNew(DomElementType::DIV);
313 east->setProperty(Property::Class, "Wt-e");
314 element.addChild(east);
315
316 element.addChild(createSDomElement(app));
317 element.addChild(fill_->createSDomElement(app));
318 element.addChild(handle_->createSDomElement(app));
319 }
320 }
321
sliderResized(const WLength & width,const WLength & height)322 void PaintedSlider::sliderResized(const WLength& width, const WLength& height)
323 {
324 if (slider_->orientation() == Orientation::Horizontal) {
325 WLength w = width;
326 if (!w.isAuto())
327 w = WLength(w.toPixels() - 10);
328
329 resize(w, height);
330 } else {
331 WLength h = height;
332 if (!h.isAuto())
333 h = WLength(h.toPixels() - 10);
334
335 resize(width, h);
336 }
337
338 updateState();
339 }
340
onSliderClick(const WMouseEvent & event)341 void PaintedSlider::onSliderClick(const WMouseEvent& event)
342 {
343 int x = event.widget().x;
344 int y = event.widget().y;
345
346 if (WApplication::instance()->layoutDirection() ==
347 LayoutDirection::RightToLeft)
348 x = (int)(w() - x);
349
350 onSliderReleased(slider_->orientation() == Orientation::Horizontal ? x : y);
351 }
352
onSliderReleased(int u)353 void PaintedSlider::onSliderReleased(int u)
354 {
355 if (slider_->orientation() == Orientation::Horizontal)
356 u -= slider_->handleWidth() / 2;
357 else
358 u = (int)h() - (u + slider_->handleWidth() / 2);
359
360 double l = (slider_->orientation() == Orientation::Horizontal) ? w() : h();
361
362 double pixelsPerUnit = (l - slider_->handleWidth()) / range();
363
364 double v = std::max(slider_->minimum(),
365 std::min(slider_->maximum(),
366 slider_->minimum()
367 + (int)((double)u / pixelsPerUnit + 0.5)));
368
369 // TODO changed() ?
370 slider_->sliderMoved().emit(static_cast<int>(v));
371
372 slider_->setValue(static_cast<int>(v));
373 slider_->valueChanged().emit(slider_->value());
374
375 updateSliderPosition();
376 }
377
updateSliderPosition()378 void PaintedSlider::updateSliderPosition()
379 {
380 double l = (slider_->orientation() == Orientation::Horizontal) ? w() : h();
381 double pixelsPerUnit = (l - slider_->handleWidth()) / range();
382
383 double u = ((double)slider_->value() - slider_->minimum()) * pixelsPerUnit;
384
385 if (slider_->orientation() == Orientation::Horizontal) {
386 handle_->setOffsets(u, Side::Left);
387 fill_->setWidth(u + slider_->handleWidth() / 2);
388 } else {
389 handle_->setOffsets(h() - slider_->handleWidth() - u, Side::Top);
390 fill_->setHeight(u + slider_->handleWidth() / 2);
391 }
392 handle_->setFocus(true);
393 }
394
WSlider()395 WSlider::WSlider()
396 : orientation_(Orientation::Horizontal),
397 tickInterval_(0),
398 tickPosition_(None),
399 preferNative_(false),
400 changed_(false),
401 changedConnected_(false),
402 handleWidth_(20),
403 minimum_(0),
404 maximum_(99),
405 value_(0),
406 sliderMoved_(this, "moved", true)
407 {
408 resize(150, 50);
409 }
410
WSlider(Orientation orientation)411 WSlider::WSlider(Orientation orientation)
412 : orientation_(orientation),
413 tickInterval_(0),
414 tickPosition_(None),
415 preferNative_(false),
416 changed_(false),
417 changedConnected_(false),
418 handleWidth_(20),
419 minimum_(0),
420 maximum_(99),
421 value_(0),
422 sliderMoved_(this, "moved", true)
423 {
424 if (orientation == Orientation::Horizontal)
425 resize(150, 50);
426 else
427 resize(50, 150);
428 }
429
~WSlider()430 WSlider::~WSlider()
431 {
432 manageWidget(paintedSlider_, std::unique_ptr<PaintedSlider>());
433 }
434
input()435 EventSignal<>& WSlider::input()
436 {
437 return *voidEventSignal(INPUT_SIGNAL, true);
438 }
439
enableAjax()440 void WSlider::enableAjax()
441 {
442 if (paintedSlider_)
443 paintedSlider_->connectSlots();
444 }
445
setNativeControl(bool nativeControl)446 void WSlider::setNativeControl(bool nativeControl)
447 {
448 preferNative_ = nativeControl;
449 }
450
nativeControl()451 bool WSlider::nativeControl() const
452 {
453 if (preferNative_) {
454 const WEnvironment& env = WApplication::instance()->environment();
455 if ((env.agentIsChrome() &&
456 static_cast<unsigned int>(env.agent()) >=
457 static_cast<unsigned int>(UserAgent::Chrome5))
458 || (env.agentIsSafari() &&
459 static_cast<unsigned int>(env.agent()) >=
460 static_cast<unsigned int>(UserAgent::Safari4))
461 || (env.agentIsOpera() &&
462 static_cast<unsigned int>(env.agent()) >=
463 static_cast<unsigned int>(UserAgent::Opera10))
464 || (env.agentIsGecko() &&
465 static_cast<unsigned int>(env.agent()) >=
466 static_cast<unsigned int>(UserAgent::Firefox5_0)))
467 return true;
468 }
469
470 return false;
471 }
472
resize(const WLength & width,const WLength & height)473 void WSlider::resize(const WLength& width, const WLength& height)
474 {
475 // Quick transform rotate fix
476 if (orientation() == Orientation::Vertical) {
477 auto app = WApplication::instance();
478 auto bs5Theme = std::dynamic_pointer_cast<WBootstrap5Theme>(app->theme());
479 if (bs5Theme) {
480 WLength w = width, h = height;
481 WLength size = WLength(std::max(w.toPixels(), h.toPixels()));
482 WFormWidget::resize(size, size);
483 }
484 } else {
485 WFormWidget::resize(width, height);
486 }
487
488 if (paintedSlider_)
489 paintedSlider_->sliderResized(width, height);
490 }
491
layoutSizeChanged(int width,int height)492 void WSlider::layoutSizeChanged(int width, int height)
493 {
494 WFormWidget::resize(WLength::Auto, WLength::Auto);
495
496 if (paintedSlider_) {
497 paintedSlider_->sliderResized(width, height);
498 }
499 }
500
setOrientation(Orientation orientation)501 void WSlider::setOrientation(Orientation orientation)
502 {
503 orientation_ = orientation;
504
505 if (paintedSlider_)
506 paintedSlider_->updateState();
507 }
508
setTickPosition(WFlags<TickPosition> tickPosition)509 void WSlider::setTickPosition(WFlags<TickPosition> tickPosition)
510 {
511 tickPosition_ = tickPosition;
512
513 if (paintedSlider_)
514 paintedSlider_->updateState();
515 }
516
setTickInterval(int tickInterval)517 void WSlider::setTickInterval(int tickInterval)
518 {
519 tickInterval_ = tickInterval;
520
521 if (paintedSlider_)
522 paintedSlider_->updateState();
523 }
524
setHandleWidth(int handleWidth)525 void WSlider::setHandleWidth(int handleWidth)
526 {
527 handleWidth_ = handleWidth;
528 }
529
createHandle()530 std::unique_ptr<WInteractWidget> WSlider::createHandle()
531 {
532 return std::unique_ptr<WInteractWidget>(new WContainerWidget());
533 }
534
update()535 void WSlider::update()
536 {
537 if (paintedSlider_)
538 paintedSlider_->updateState();
539 else {
540 changed_ = true;
541 repaint();
542 }
543 }
544
setMinimum(int minimum)545 void WSlider::setMinimum(int minimum)
546 {
547 minimum_ = minimum;
548 value_ = std::max(minimum_, value_);
549 maximum_ = std::max(minimum_ + 1, maximum_);
550
551 update();
552 }
553
setMaximum(int maximum)554 void WSlider::setMaximum(int maximum)
555 {
556 maximum_ = maximum;
557 value_ = std::min(maximum_, value_);
558 minimum_ = std::min(maximum_ - 1, minimum_);
559
560 update();
561 }
562
setRange(int minimum,int maximum)563 void WSlider::setRange(int minimum, int maximum)
564 {
565 minimum_ = minimum;
566 maximum_ = maximum;
567 value_ = std::min(maximum_, std::max(minimum_, value_));
568
569 update();
570 }
571
setValue(int value)572 void WSlider::setValue(int value)
573 {
574 value_ = std::min(maximum_, std::max(minimum_, value));
575
576 if (paintedSlider_)
577 paintedSlider_->updateSliderPosition();
578 else {
579 update();
580 onChange();
581 }
582 }
583
signalConnectionsChanged()584 void WSlider::signalConnectionsChanged()
585 {
586 WFormWidget::signalConnectionsChanged();
587
588 update();
589 }
590
onChange()591 void WSlider::onChange()
592 {
593 valueChanged_.emit(value_);
594 sliderMoved_.emit(value_);
595 }
596
domElementType()597 DomElementType WSlider::domElementType() const
598 {
599 return paintedSlider_ ? DomElementType::DIV : DomElementType::INPUT;
600 }
601
render(WFlags<RenderFlag> flags)602 void WSlider::render(WFlags<RenderFlag> flags)
603 {
604 /*
605 * In theory we are a bit late here to decide what we want to become:
606 * somebody could already have asked the domElementType()
607 */
608 if (flags.test(RenderFlag::Full)) {
609 bool useNative = nativeControl();
610
611 if (!useNative) {
612 if (!paintedSlider_) {
613 auto paintedSlider = std::make_unique<PaintedSlider>(this);
614 manageWidget(paintedSlider_, std::move(paintedSlider));
615 paintedSlider_->sliderResized(width(), height());
616 }
617 }
618
619 setLayoutSizeAware(!useNative);
620 setFormObject(useNative);
621 }
622
623 WFormWidget::render(flags);
624 }
625
updateDom(DomElement & element,bool all)626 void WSlider::updateDom(DomElement& element, bool all)
627 {
628 if (paintedSlider_)
629 paintedSlider_->doUpdateDom(element, all);
630 else {
631 if (all || changed_) {
632 element.setAttribute("type", "range");
633 element.setProperty(Wt::Property::Value, std::to_string(value_));
634 element.setAttribute("min", std::to_string(minimum_));
635 element.setAttribute("max", std::to_string(maximum_));
636
637 if (!changedConnected_
638 && (valueChanged_.isConnected() || sliderMoved_.isConnected())) {
639 changedConnected_ = true;
640 changed().connect(this, &WSlider::onChange);
641 } else if (!inputConnected_ && (valueChanged_.isConnected() || sliderMoved_.isConnected())) {
642 changedConnected_ = true;
643 input().connect(this, &WSlider::onChange);
644 }
645
646 changed_ = false;
647 }
648 }
649
650 WFormWidget::updateDom(element, all);
651 }
652
setFormData(const FormData & formData)653 void WSlider::setFormData(const FormData& formData)
654 {
655 // if the value was updated through the API, then ignore the update from
656 // the browser, this happens when an action generated multiple events,
657 // and we do not want to revert the changes made through the API
658 if (changed_ || isReadOnly())
659 return;
660
661 if (!Utils::isEmpty(formData.values)) {
662 const std::string& value = formData.values[0];
663 try {
664 value_ = Utils::stoi(value);
665 } catch (std::exception& e) { }
666 }
667 }
668
valueText()669 WT_USTRING WSlider::valueText() const
670 {
671 return WT_USTRING::fromUTF8(std::to_string(value_));
672 }
673
setValueText(const WT_USTRING & value)674 void WSlider::setValueText(const WT_USTRING& value)
675 {
676 try {
677 value_ = Utils::stoi(value.toUTF8());
678 } catch (std::exception& e) {
679 }
680 }
681
setDisabled(bool disabled)682 void WSlider::setDisabled(bool disabled)
683 {
684 if (paintedSlider_)
685 paintedSlider_->setDisabled(disabled);
686
687 WFormWidget::setDisabled(disabled);
688
689 if (paintedSlider_)
690 paintedSlider_->updateState();
691 }
692
paintTick(WPainter & painter,int value,int x,int y)693 void WSlider::paintTick(WPainter& painter, int value, int x, int y)
694 {
695 if (!tickPosition_.empty()) {
696 int h = 0;
697
698 if (orientation_ == Orientation::Horizontal)
699 h = (int)painter.device()->height().toPixels();
700 else
701 h = (int)painter.device()->width().toPixels();
702
703 WPen pen;
704 pen.setColor(WColor(0xd7, 0xd7, 0xd7));
705 pen.setCapStyle(PenCapStyle::Flat);
706 pen.setWidth(1);
707 painter.setPen(pen);
708
709 int y1 = h / 4;
710 int y2 = h / 2 - 4;
711 int y3 = h / 2 + 4;
712 int y4 = h - h/4;
713
714 switch (orientation_) {
715 case Orientation::Horizontal:
716 if (tickPosition_.test(WSlider::TickPosition::TicksAbove))
717 painter.drawLine(x + 0.5, y1, x + 0.5, y2);
718 if (tickPosition_.test(WSlider::TickPosition::TicksBelow))
719 painter.drawLine(x + 0.5, y3, x + 0.5, y4);
720
721 break;
722 case Orientation::Vertical:
723 if (tickPosition_.test(WSlider::TickPosition::TicksAbove))
724 painter.drawLine(y1, y + 0.5, y2, y + 0.5);
725 if (tickPosition_.test(WSlider::TickPosition::TicksBelow))
726 painter.drawLine(y3, y + 0.5, y4, y + 0.5);
727 }
728 }
729 }
730
731 }
732