2 * Copyright (C) 2007 Apple Inc.
3 * Copyright (C) 2007 Alp Toker <alp@atoker.com>
4 * Copyright (C) 2008 Collabora Ltd.
5 * Copyright (C) 2009 Kenneth Rohde Christiansen
6 * Copyright (C) 2010 Igalia S.L.
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Library General Public
10 * License as published by the Free Software Foundation; either
11 * version 2 of the License, or (at your option) any later version.
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Library General Public License for more details.
18 * You should have received a copy of the GNU Library General Public License
19 * along with this library; see the file COPYING.LIB. If not, write to
20 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 * Boston, MA 02110-1301, USA.
26 #include "RenderThemeGtk.h"
28 #include "CSSValueKeywords.h"
31 #include "GraphicsContext.h"
32 #include "GtkVersioning.h"
33 #include "HTMLMediaElement.h"
34 #include "HTMLNames.h"
35 #include "MediaControlElements.h"
36 #include "PaintInfo.h"
37 #include "RenderBox.h"
38 #include "RenderObject.h"
39 #include "TimeRanges.h"
40 #include "UserAgentStyleSheets.h"
44 #if ENABLE(PROGRESS_TAG)
45 #include "RenderProgress.h"
50 using namespace HTMLNames;
53 static HTMLMediaElement* getMediaElementFromRenderObject(RenderObject* o)
55 Node* node = o->node();
56 Node* mediaNode = node ? node->shadowAncestorNode() : 0;
57 if (!mediaNode || (!mediaNode->hasTagName(videoTag) && !mediaNode->hasTagName(audioTag)))
60 return static_cast<HTMLMediaElement*>(mediaNode);
63 static GtkIconSize getMediaButtonIconSize(int mediaIconSize)
65 GtkIconSize iconSize = gtk_icon_size_from_name("webkit-media-button-size");
67 iconSize = gtk_icon_size_register("webkit-media-button-size", mediaIconSize, mediaIconSize);
71 void RenderThemeGtk::initMediaButtons()
73 static bool iconsInitialized = false;
78 GRefPtr<GtkIconFactory> iconFactory = adoptGRef(gtk_icon_factory_new());
79 GtkIconSource* iconSource = gtk_icon_source_new();
80 const char* icons[] = { "audio-volume-high", "audio-volume-muted" };
82 gtk_icon_factory_add_default(iconFactory.get());
84 for (size_t i = 0; i < G_N_ELEMENTS(icons); ++i) {
85 gtk_icon_source_set_icon_name(iconSource, icons[i]);
86 GtkIconSet* iconSet = gtk_icon_set_new();
87 gtk_icon_set_add_source(iconSet, iconSource);
88 gtk_icon_factory_add(iconFactory.get(), icons[i], iconSet);
89 gtk_icon_set_unref(iconSet);
92 gtk_icon_source_free(iconSource);
94 iconsInitialized = true;
98 PassRefPtr<RenderTheme> RenderThemeGtk::create()
100 return adoptRef(new RenderThemeGtk());
103 PassRefPtr<RenderTheme> RenderTheme::themeForPage(Page* page)
105 static RenderTheme* rt = RenderThemeGtk::create().releaseRef();
109 RenderThemeGtk::RenderThemeGtk()
110 : m_panelColor(Color::white)
111 , m_sliderColor(Color::white)
112 , m_sliderThumbColor(Color::white)
113 , m_mediaIconSize(16)
114 , m_mediaSliderHeight(14)
115 , m_mediaSliderThumbWidth(12)
116 , m_mediaSliderThumbHeight(12)
125 static bool supportsFocus(ControlPart appearance)
127 switch (appearance) {
132 case SearchFieldPart:
136 case SliderHorizontalPart:
137 case SliderVerticalPart:
144 bool RenderThemeGtk::supportsFocusRing(const RenderStyle* style) const
146 return supportsFocus(style->appearance());
149 bool RenderThemeGtk::controlSupportsTints(const RenderObject* o) const
154 int RenderThemeGtk::baselinePosition(const RenderObject* o) const
159 // FIXME: This strategy is possibly incorrect for the GTK+ port.
160 if (o->style()->appearance() == CheckboxPart
161 || o->style()->appearance() == RadioPart) {
162 const RenderBox* box = toRenderBox(o);
163 return box->marginTop() + box->height() - 2;
166 return RenderTheme::baselinePosition(o);
169 // This is used in RenderThemeGtk2 and RenderThemeGtk3. Normally, it would be in
170 // the RenderThemeGtk header (perhaps as a static method), but we want to avoid
171 // having to include GTK+ headers only for the GtkTextDirection enum.
172 GtkTextDirection gtkTextDirection(TextDirection direction)
176 return GTK_TEXT_DIR_RTL;
178 return GTK_TEXT_DIR_LTR;
180 return GTK_TEXT_DIR_NONE;
184 static GtkStateType gtkIconState(RenderTheme* theme, RenderObject* renderObject)
186 if (!theme->isEnabled(renderObject))
187 return GTK_STATE_INSENSITIVE;
188 if (theme->isPressed(renderObject))
189 return GTK_STATE_ACTIVE;
190 if (theme->isHovered(renderObject))
191 return GTK_STATE_PRELIGHT;
193 return GTK_STATE_NORMAL;
196 void RenderThemeGtk::adjustButtonStyle(CSSStyleSelector* selector, RenderStyle* style, WebCore::Element* e) const
198 // Some layout tests check explicitly that buttons ignore line-height.
199 if (style->appearance() == PushButtonPart)
200 style->setLineHeight(RenderStyle::initialLineHeight());
203 void RenderThemeGtk::adjustMenuListStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const
205 // The tests check explicitly that select menu buttons ignore line height.
206 style->setLineHeight(RenderStyle::initialLineHeight());
208 // We cannot give a proper rendering when border radius is active, unfortunately.
209 style->resetBorderRadius();
212 void RenderThemeGtk::adjustMenuListButtonStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const
214 adjustMenuListStyle(selector, style, e);
217 bool RenderThemeGtk::paintMenuListButton(RenderObject* object, const PaintInfo& info, const IntRect& rect)
219 return paintMenuList(object, info, rect);
222 bool RenderThemeGtk::paintTextArea(RenderObject* o, const PaintInfo& i, const IntRect& r)
224 return paintTextField(o, i, r);
227 static void paintGdkPixbuf(GraphicsContext* context, const GdkPixbuf* icon, const IntRect& iconRect)
229 IntSize iconSize(gdk_pixbuf_get_width(icon), gdk_pixbuf_get_height(icon));
230 if (iconRect.size() != iconSize) {
231 // We could use cairo_scale() here but cairo/pixman downscale quality is quite bad.
232 GRefPtr<GdkPixbuf> scaledIcon = gdk_pixbuf_scale_simple(icon, iconRect.width(), iconRect.height(),
233 GDK_INTERP_BILINEAR);
234 icon = scaledIcon.get();
237 cairo_t* cr = context->platformContext();
239 gdk_cairo_set_source_pixbuf(cr, icon, iconRect.x(), iconRect.y());
244 // Defined in GTK+ (gtk/gtkiconfactory.c)
245 static const gint gtkIconSizeMenu = 16;
246 static const gint gtkIconSizeSmallToolbar = 18;
247 static const gint gtkIconSizeButton = 20;
248 static const gint gtkIconSizeLargeToolbar = 24;
249 static const gint gtkIconSizeDnd = 32;
250 static const gint gtkIconSizeDialog = 48;
252 static GtkIconSize getIconSizeForPixelSize(gint pixelSize)
254 if (pixelSize < gtkIconSizeSmallToolbar)
255 return GTK_ICON_SIZE_MENU;
256 if (pixelSize >= gtkIconSizeSmallToolbar && pixelSize < gtkIconSizeButton)
257 return GTK_ICON_SIZE_SMALL_TOOLBAR;
258 if (pixelSize >= gtkIconSizeButton && pixelSize < gtkIconSizeLargeToolbar)
259 return GTK_ICON_SIZE_BUTTON;
260 if (pixelSize >= gtkIconSizeLargeToolbar && pixelSize < gtkIconSizeDnd)
261 return GTK_ICON_SIZE_LARGE_TOOLBAR;
262 if (pixelSize >= gtkIconSizeDnd && pixelSize < gtkIconSizeDialog)
263 return GTK_ICON_SIZE_DND;
265 return GTK_ICON_SIZE_DIALOG;
268 void RenderThemeGtk::adjustSearchFieldResultsButtonStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const
270 adjustSearchFieldCancelButtonStyle(selector, style, e);
273 bool RenderThemeGtk::paintSearchFieldResultsButton(RenderObject* o, const PaintInfo& i, const IntRect& rect)
275 return paintSearchFieldResultsDecoration(o, i, rect);
278 static void adjustSearchFieldIconStyle(RenderStyle* style)
280 style->resetBorder();
281 style->resetPadding();
283 // Get the icon size based on the font size.
284 int fontSize = style->fontSize();
285 if (fontSize < gtkIconSizeMenu) {
286 style->setWidth(Length(fontSize, Fixed));
287 style->setHeight(Length(fontSize, Fixed));
290 gint width = 0, height = 0;
291 gtk_icon_size_lookup(getIconSizeForPixelSize(fontSize), &width, &height);
292 style->setWidth(Length(width, Fixed));
293 style->setHeight(Length(height, Fixed));
296 void RenderThemeGtk::adjustSearchFieldResultsDecorationStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const
298 adjustSearchFieldIconStyle(style);
301 static IntRect centerRectVerticallyInParentInputElement(RenderObject* renderObject, const IntRect& rect)
303 // Get the renderer of <input> element.
304 Node* input = renderObject->node()->shadowAncestorNode();
305 if (!input->renderer()->isBox())
308 // If possible center the y-coordinate of the rect vertically in the parent input element.
309 // We also add one pixel here to ensure that the y coordinate is rounded up for box heights
310 // that are even, which looks in relation to the box text.
311 IntRect inputContentBox = toRenderBox(input->renderer())->absoluteContentBox();
313 // Make sure the scaled decoration stays square and will fit in its parent's box.
314 int iconSize = std::min(inputContentBox.width(), std::min(inputContentBox.height(), rect.height()));
315 IntRect scaledRect(rect.x(), inputContentBox.y() + (inputContentBox.height() - iconSize + 1) / 2, iconSize, iconSize);
319 bool RenderThemeGtk::paintSearchFieldResultsDecoration(RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect)
321 IntRect iconRect = centerRectVerticallyInParentInputElement(renderObject, rect);
322 if (iconRect.isEmpty())
325 GRefPtr<GdkPixbuf> icon = getStockIcon(GTK_TYPE_ENTRY, GTK_STOCK_FIND,
326 gtkTextDirection(renderObject->style()->direction()),
327 gtkIconState(this, renderObject),
328 getIconSizeForPixelSize(rect.height()));
329 paintGdkPixbuf(paintInfo.context, icon.get(), iconRect);
333 void RenderThemeGtk::adjustSearchFieldCancelButtonStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const
335 adjustSearchFieldIconStyle(style);
338 bool RenderThemeGtk::paintSearchFieldCancelButton(RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect)
340 IntRect iconRect = centerRectVerticallyInParentInputElement(renderObject, rect);
341 if (iconRect.isEmpty())
344 GRefPtr<GdkPixbuf> icon = getStockIcon(GTK_TYPE_ENTRY, GTK_STOCK_CLEAR,
345 gtkTextDirection(renderObject->style()->direction()),
346 gtkIconState(this, renderObject),
347 getIconSizeForPixelSize(rect.height()));
348 paintGdkPixbuf(paintInfo.context, icon.get(), iconRect);
352 void RenderThemeGtk::adjustSearchFieldStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const
354 // We cannot give a proper rendering when border radius is active, unfortunately.
355 style->resetBorderRadius();
356 style->setLineHeight(RenderStyle::initialLineHeight());
359 bool RenderThemeGtk::paintSearchField(RenderObject* o, const PaintInfo& i, const IntRect& rect)
361 return paintTextField(o, i, rect);
364 bool RenderThemeGtk::paintCapsLockIndicator(RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect)
366 // The other paint methods don't need to check whether painting is disabled because RenderTheme already checks it
367 // before calling them, but paintCapsLockIndicator() is called by RenderTextControlSingleLine which doesn't check it.
368 if (paintInfo.context->paintingDisabled())
371 int iconSize = std::min(rect.width(), rect.height());
372 GRefPtr<GdkPixbuf> icon = getStockIcon(GTK_TYPE_ENTRY, GTK_STOCK_CAPS_LOCK_WARNING,
373 gtkTextDirection(renderObject->style()->direction()),
374 0, getIconSizeForPixelSize(iconSize));
376 // Only re-scale the icon when it's smaller than the minimum icon size.
377 if (iconSize >= gtkIconSizeMenu)
378 iconSize = gdk_pixbuf_get_height(icon.get());
380 // GTK+ locates the icon right aligned in the entry. The given rectangle is already
381 // centered vertically by RenderTextControlSingleLine.
382 IntRect iconRect(rect.x() + rect.width() - iconSize,
383 rect.y() + (rect.height() - iconSize) / 2,
385 paintGdkPixbuf(paintInfo.context, icon.get(), iconRect);
389 void RenderThemeGtk::adjustSliderTrackStyle(CSSStyleSelector*, RenderStyle* style, Element*) const
391 style->setBoxShadow(0);
394 void RenderThemeGtk::adjustSliderThumbStyle(CSSStyleSelector*, RenderStyle* style, Element*) const
396 style->setBoxShadow(0);
399 double RenderThemeGtk::caretBlinkInterval() const
401 GtkSettings* settings = gtk_settings_get_default();
403 gboolean shouldBlink;
406 g_object_get(settings, "gtk-cursor-blink", &shouldBlink, "gtk-cursor-blink-time", &time, NULL);
414 double RenderThemeGtk::getScreenDPI()
416 // FIXME: Really this should be the widget's screen.
417 GdkScreen* screen = gdk_screen_get_default();
419 return 96; // Default to 96 DPI.
421 float dpi = gdk_screen_get_resolution(screen);
427 void RenderThemeGtk::systemFont(int, FontDescription& fontDescription) const
429 GtkSettings* settings = gtk_settings_get_default();
433 // This will be a font selection string like "Sans 10" so we cannot use it as the family name.
434 GOwnPtr<gchar> fontName;
435 g_object_get(settings, "gtk-font-name", &fontName.outPtr(), NULL);
437 PangoFontDescription* pangoDescription = pango_font_description_from_string(fontName.get());
438 if (!pangoDescription)
441 fontDescription.firstFamily().setFamily(pango_font_description_get_family(pangoDescription));
443 int size = pango_font_description_get_size(pangoDescription) / PANGO_SCALE;
444 // If the size of the font is in points, we need to convert it to pixels.
445 if (!pango_font_description_get_size_is_absolute(pangoDescription))
446 size = size * (getScreenDPI() / 72.0);
448 fontDescription.setSpecifiedSize(size);
449 fontDescription.setIsAbsoluteSize(true);
450 fontDescription.setGenericFamily(FontDescription::NoFamily);
451 fontDescription.setWeight(FontWeightNormal);
452 fontDescription.setItalic(false);
453 pango_font_description_free(pangoDescription);
456 void RenderThemeGtk::platformColorsDidChange()
461 RenderTheme::platformColorsDidChange();
465 String RenderThemeGtk::extraMediaControlsStyleSheet()
467 return String(mediaControlsGtkUserAgentStyleSheet, sizeof(mediaControlsGtkUserAgentStyleSheet));
470 void RenderThemeGtk::adjustMediaSliderThumbSize(RenderObject* renderObject) const
472 ASSERT(renderObject->style()->appearance() == MediaSliderThumbPart);
473 renderObject->style()->setWidth(Length(m_mediaSliderThumbWidth, Fixed));
474 renderObject->style()->setHeight(Length(m_mediaSliderThumbHeight, Fixed));
477 bool RenderThemeGtk::paintMediaButton(RenderObject* renderObject, GraphicsContext* context, const IntRect& rect, const char* iconName)
479 GRefPtr<GdkPixbuf> icon = getStockIcon(GTK_TYPE_CONTAINER, iconName,
480 gtkTextDirection(renderObject->style()->direction()),
481 gtkIconState(this, renderObject),
482 getMediaButtonIconSize(m_mediaIconSize));
483 IntRect iconRect(rect.x() + (rect.width() - m_mediaIconSize) / 2,
484 rect.y() + (rect.height() - m_mediaIconSize) / 2,
485 m_mediaIconSize, m_mediaIconSize);
486 context->fillRect(FloatRect(rect), m_panelColor, ColorSpaceDeviceRGB);
487 paintGdkPixbuf(context, icon.get(), iconRect);
491 bool RenderThemeGtk::paintMediaFullscreenButton(RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect)
493 return paintMediaButton(renderObject, paintInfo.context, rect, GTK_STOCK_FULLSCREEN);
496 bool RenderThemeGtk::paintMediaMuteButton(RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect)
498 HTMLMediaElement* mediaElement = getMediaElementFromRenderObject(renderObject);
502 return paintMediaButton(renderObject, paintInfo.context, rect, mediaElement->muted() ? "audio-volume-muted" : "audio-volume-high");
505 bool RenderThemeGtk::paintMediaPlayButton(RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect)
507 Node* node = renderObject->node();
511 MediaControlPlayButtonElement* button = static_cast<MediaControlPlayButtonElement*>(node);
512 return paintMediaButton(renderObject, paintInfo.context, rect, button->displayType() == MediaPlayButton ? GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE);
515 bool RenderThemeGtk::paintMediaSeekBackButton(RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect)
517 return paintMediaButton(renderObject, paintInfo.context, rect, GTK_STOCK_MEDIA_REWIND);
520 bool RenderThemeGtk::paintMediaSeekForwardButton(RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect)
522 return paintMediaButton(renderObject, paintInfo.context, rect, GTK_STOCK_MEDIA_FORWARD);
525 bool RenderThemeGtk::paintMediaSliderTrack(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r)
527 GraphicsContext* context = paintInfo.context;
529 context->fillRect(FloatRect(r), m_panelColor, ColorSpaceDeviceRGB);
530 context->fillRect(FloatRect(IntRect(r.x(), r.y() + (r.height() - m_mediaSliderHeight) / 2,
531 r.width(), m_mediaSliderHeight)), m_sliderColor, ColorSpaceDeviceRGB);
533 RenderStyle* style = o->style();
534 HTMLMediaElement* mediaElement = toParentMediaElement(o);
539 // Draw the buffered ranges. This code is highly inspired from
540 // Chrome for the gradient code.
541 float mediaDuration = mediaElement->duration();
542 RefPtr<TimeRanges> timeRanges = mediaElement->buffered();
543 IntRect trackRect = r;
544 int totalWidth = trackRect.width();
546 trackRect.inflate(-style->borderLeftWidth());
548 context->setStrokeStyle(NoStroke);
550 for (unsigned index = 0; index < timeRanges->length(); ++index) {
551 ExceptionCode ignoredException;
552 float start = timeRanges->start(index, ignoredException);
553 float end = timeRanges->end(index, ignoredException);
554 int width = ((end - start) * totalWidth) / mediaDuration;
557 rangeRect = trackRect;
558 rangeRect.setWidth(width);
560 rangeRect.setLocation(IntPoint(trackRect.x() + start / mediaDuration* totalWidth, trackRect.y()));
561 rangeRect.setSize(IntSize(width, trackRect.height()));
564 // Don't bother drawing empty range.
565 if (rangeRect.isEmpty())
568 IntPoint sliderTopLeft = rangeRect.location();
569 IntPoint sliderTopRight = sliderTopLeft;
570 sliderTopRight.move(0, rangeRect.height());
572 RefPtr<Gradient> gradient = Gradient::create(sliderTopLeft, sliderTopRight);
573 Color startColor = m_panelColor;
574 gradient->addColorStop(0.0, startColor);
575 gradient->addColorStop(1.0, Color(startColor.red() / 2, startColor.green() / 2, startColor.blue() / 2, startColor.alpha()));
577 context->setFillGradient(gradient);
578 context->fillRect(rangeRect);
585 bool RenderThemeGtk::paintMediaSliderThumb(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r)
587 // Make the thumb nicer with rounded corners.
588 paintInfo.context->fillRoundedRect(r, IntSize(3, 3), IntSize(3, 3), IntSize(3, 3), IntSize(3, 3), m_sliderThumbColor, ColorSpaceDeviceRGB);
592 bool RenderThemeGtk::paintMediaVolumeSliderContainer(RenderObject*, const PaintInfo& paintInfo, const IntRect& rect)
594 GraphicsContext* context = paintInfo.context;
595 context->fillRect(FloatRect(rect), m_panelColor, ColorSpaceDeviceRGB);
599 bool RenderThemeGtk::paintMediaVolumeSliderTrack(RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect)
601 return paintSliderTrack(renderObject, paintInfo, rect);
604 bool RenderThemeGtk::paintMediaVolumeSliderThumb(RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect)
606 return paintSliderThumb(renderObject, paintInfo, rect);
609 String RenderThemeGtk::formatMediaControlsCurrentTime(float currentTime, float duration) const
611 return formatMediaControlsTime(currentTime) + " / " + formatMediaControlsTime(duration);
614 bool RenderThemeGtk::paintMediaCurrentTime(RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect)
616 GraphicsContext* context = paintInfo.context;
618 context->fillRect(FloatRect(rect), m_panelColor, ColorSpaceDeviceRGB);
623 #if ENABLE(PROGRESS_TAG)
624 void RenderThemeGtk::adjustProgressBarStyle(CSSStyleSelector*, RenderStyle* style, Element*) const
626 style->setBoxShadow(0);
629 // These values have been copied from RenderThemeChromiumSkia.cpp
630 static const int progressActivityBlocks = 5;
631 static const int progressAnimationFrames = 10;
632 static const double progressAnimationInterval = 0.125;
633 double RenderThemeGtk::animationRepeatIntervalForProgressBar(RenderProgress*) const
635 return progressAnimationInterval;
638 double RenderThemeGtk::animationDurationForProgressBar(RenderProgress*) const
640 return progressAnimationInterval * progressAnimationFrames * 2; // "2" for back and forth;
643 IntRect RenderThemeGtk::calculateProgressRect(RenderObject* renderObject, const IntRect& fullBarRect)
645 IntRect progressRect(fullBarRect);
646 RenderProgress* renderProgress = toRenderProgress(renderObject);
647 if (renderProgress->isDeterminate()) {
648 int progressWidth = progressRect.width() * renderProgress->position();
649 if (renderObject->style()->direction() == RTL)
650 progressRect.setX(progressRect.x() + progressRect.width() - progressWidth);
651 progressRect.setWidth(progressWidth);
655 double animationProgress = renderProgress->animationProgress();
657 // Never let the progress rect shrink smaller than 2 pixels.
658 int newWidth = max(2, progressRect.width() / progressActivityBlocks);
659 int movableWidth = progressRect.width() - newWidth;
660 progressRect.setWidth(newWidth);
662 // We want the first 0.5 units of the animation progress to represent the
663 // forward motion and the second 0.5 units to represent the backward motion,
664 // thus we multiply by two here to get the full sweep of the progress bar with
666 if (animationProgress < 0.5)
667 progressRect.setX(progressRect.x() + (animationProgress * 2 * movableWidth));
669 progressRect.setX(progressRect.x() + ((1.0 - animationProgress) * 2 * movableWidth));