From eae868ae7b80cfeebb8e732014d352d6cf8075c3 Mon Sep 17 00:00:00 2001 From: tof <> Date: Sun, 23 Sep 2007 12:02:44 +0000 Subject: Update TSlider, and make it looks nicer by default. --- .gitattributes | 5 +- .../pages/Controls/Samples/TSlider/Home.page | 17 +- .../pages/Controls/Samples/TSlider/Home.php | 6 +- framework/Web/Javascripts/source/packages.php | 6 +- .../Javascripts/source/prado/controls/slider.js | 234 +++++++++++++++++++-- framework/Web/UI/WebControls/TSlider.php | 180 ++++++---------- framework/Web/UI/WebControls/assets/TSlider.css | 55 ----- .../Web/UI/WebControls/assets/TSlider/TSlider.css | 95 +++++++++ .../assets/TSlider/TSliderHandleHorizontal.png | Bin 0 -> 286 bytes .../assets/TSlider/TSliderHandleVertical.png | Bin 0 -> 249 bytes .../Web/UI/WebControls/assets/TSliderHandle.png | Bin 717 -> 0 bytes 11 files changed, 405 insertions(+), 193 deletions(-) delete mode 100755 framework/Web/UI/WebControls/assets/TSlider.css create mode 100755 framework/Web/UI/WebControls/assets/TSlider/TSlider.css create mode 100644 framework/Web/UI/WebControls/assets/TSlider/TSliderHandleHorizontal.png create mode 100644 framework/Web/UI/WebControls/assets/TSlider/TSliderHandleVertical.png delete mode 100644 framework/Web/UI/WebControls/assets/TSliderHandle.png diff --git a/.gitattributes b/.gitattributes index 27c86adf..d8bbf559 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2603,8 +2603,9 @@ framework/Web/UI/WebControls/TWebControlAdapter.php -text framework/Web/UI/WebControls/TWizard.php -text framework/Web/UI/WebControls/TWizardNavigationButtonStyle.php -text framework/Web/UI/WebControls/TXmlTransform.php -text -framework/Web/UI/WebControls/assets/TSlider.css -text -framework/Web/UI/WebControls/assets/TSliderHandle.png -text svneol=unset#image/png +framework/Web/UI/WebControls/assets/TSlider/TSlider.css -text +framework/Web/UI/WebControls/assets/TSlider/TSliderHandleHorizontal.png -text +framework/Web/UI/WebControls/assets/TSlider/TSliderHandleVertical.png -text framework/Web/UI/WebControls/assets/captcha.php -text framework/Web/UI/WebControls/assets/keyboard.css -text framework/Web/UI/WebControls/assets/tabpanel.css -text diff --git a/demos/quickstart/protected/pages/Controls/Samples/TSlider/Home.page b/demos/quickstart/protected/pages/Controls/Samples/TSlider/Home.page index 7670f9d3..bfbbe8e3 100644 --- a/demos/quickstart/protected/pages/Controls/Samples/TSlider/Home.page +++ b/demos/quickstart/protected/pages/Controls/Samples/TSlider/Home.page @@ -4,17 +4,17 @@ -
-Simple horizontal slider, with no events handler attached - +Simple horizontal slider, with no progress indicator, with no events handler attached +
-Horizontal slider from -50 to 50, with image handle, Javascript 'onSlide' handler, ServerSide 'onSliderChanged' handler, initial value and autopostback=true +Horizontal slider from -50 to 50, Javascript 'onSlide' handler, ServerSide 'onSliderChanged' handler, initial value and autopostback=true - + $('slider2value').innerHTML = value; @@ -24,9 +24,9 @@ Value : <%=$this->slider2->value%>
-Vertical slider from 0 to 1, with image handle, Javascript 'onSlide' handler, ServerSide 'onSliderChanged' handler, StepSize, and no autopostback +Vertical slider from 0 to 1, Javascript 'onSlide' handler, ServerSide 'onSliderChanged' handler, StepSize, and no autopostback - + $('slider3value').innerHTML = value; @@ -35,6 +35,7 @@ Value : <%=$this->slider3->value%> -
+ + + \ No newline at end of file diff --git a/demos/quickstart/protected/pages/Controls/Samples/TSlider/Home.php b/demos/quickstart/protected/pages/Controls/Samples/TSlider/Home.php index 4b9b61b9..c1c8a711 100644 --- a/demos/quickstart/protected/pages/Controls/Samples/TSlider/Home.php +++ b/demos/quickstart/protected/pages/Controls/Samples/TSlider/Home.php @@ -1,6 +1,10 @@ slider1Result->setText('Slider Value : '.$this->slider1->getValue()); diff --git a/framework/Web/Javascripts/source/packages.php b/framework/Web/Javascripts/source/packages.php index 03e237de..d056a7b8 100644 --- a/framework/Web/Javascripts/source/packages.php +++ b/framework/Web/Javascripts/source/packages.php @@ -47,10 +47,12 @@ $packages = array( ), 'slider'=>array( - 'scriptaculous/slider.js', 'prado/controls/slider.js' ), - + 'slider2'=>array( + 'scriptaculous/slider.js', + 'prado/controls/slider2.js' + ), 'keyboard'=>array( 'prado/controls/keyboard.js' ), diff --git a/framework/Web/Javascripts/source/prado/controls/slider.js b/framework/Web/Javascripts/source/prado/controls/slider.js index 203d95ed..c2725141 100644 --- a/framework/Web/Javascripts/source/prado/controls/slider.js +++ b/framework/Web/Javascripts/source/prado/controls/slider.js @@ -1,28 +1,234 @@ +/** + * TSlider client class. + * This clas is mainly based on Scriptaculous Slider control (http://script.aculo.us) + */ + Prado.WebUI.TSlider = Class.extend(Prado.WebUI.PostBackControl, -{ +{ onInit : function (options) { - this.options=options; - this.onChange=options.onChange; - options.onChange=this.change.bind(this); - + var slider = this; + this.options=options || {}; + this.track = $(options.ID+'_track'); + this.handle =$(options.ID+'_handle'); + this.progress = $(options.ID+'_progress'); + this.axis = this.options.axis || 'horizontal'; + this.range = this.options.range || $R(0,1); + this.value = 0; + this.maximum = this.options.maximum || this.range.end; + this.minimum = this.options.minimum || this.range.start; this.hiddenField=$(this.options.ID+'_1'); - new Control.Slider(options.ID+'_handle',options.ID, options); + + // Will be used to align the handle onto the track, if necessary + this.alignX = parseInt(this.options.alignX || - this.track.offsetLeft); + this.alignY = parseInt(this.options.alignY || - this.track.offsetTop); + + this.trackLength = this.maximumOffset() - this.minimumOffset(); + this.handleLength = this.isVertical() ? + (this.handle.offsetHeight != 0 ? + this.handle.offsetHeight : this.handles.style.height.replace(/px$/,"")) : + (this.handle.offsetWidth != 0 ? this.handle.offsetWidth : + this.handle.style.width.replace(/px$/,"")); + + this.active = false; + this.dragging = false; + this.disabled = false; + + if(this.options.disabled) this.setDisabled(); + + // Allowed values array + this.allowedValues = this.options.values ? this.options.values.sortBy(Prototype.K) : false; + if(this.allowedValues) { + this.minimum = this.allowedValues.min(); + this.maximum = this.allowedValues.max(); + } + + this.eventMouseDown = this.startDrag.bindAsEventListener(this); + this.eventMouseUp = this.endDrag.bindAsEventListener(this); + this.eventMouseMove = this.update.bindAsEventListener(this); + + // Initialize handle + this.setValue(parseFloat(slider.options.sliderValue)); + Element.makePositioned(this.handle); // fix IE + Event.observe (this.handle, "mousedown", this.eventMouseDown); + + Event.observe (this.track, "mousedown", this.eventMouseDown); + if (this.progress) Event.observe (this.progress, "mousedown", this.eventMouseDown); + Event.observe (document, "mouseup", this.eventMouseUp); + Event.observe (document, "mousemove", this.eventMouseMove); + + this.initialized=true; + if(this.options['AutoPostBack']==true) Event.observe(this.hiddenField, "change", Prado.PostBack.bindEvent(this,options)); + }, + + dispose: function() { + var slider = this; + Event.stopObserving(this.track, "mousedown", this.eventMouseDown); + Event.stopObserving(document, "mouseup", this.eventMouseUp); + Event.stopObserving(document, "mousemove", this.eventMouseMove); - change : function (v) - { - this.hiddenField.value=v; - if (this.onChange) - { - this.onChange(v); + Event.stopObserving(this.handle, "mousedown", slider.eventMouseDown); + }, + + setDisabled: function(){ + this.disabled = true; + }, + setEnabled: function(){ + this.disabled = false; + }, + getNearestValue: function(value){ + if(this.allowedValues){ + if(value >= this.allowedValues.max()) return(this.allowedValues.max()); + if(value <= this.allowedValues.min()) return(this.allowedValues.min()); + + var offset = Math.abs(this.allowedValues[0] - value); + var newValue = this.allowedValues[0]; + this.allowedValues.each( function(v) { + var currentOffset = Math.abs(v - value); + if(currentOffset <= offset){ + newValue = v; + offset = currentOffset; + } + }); + return newValue; } - if(this.options['AutoPostBack']==true) + if(value > this.range.end) return this.range.end; + if(value < this.range.start) return this.range.start; + return value; + }, + + setValue: function(sliderValue){ + if(!this.active) { + this.updateStyles(); + } + this.value = this.getNearestValue(sliderValue); + var pixelValue= this.translateToPx(this.value); + this.handle.style[this.isVertical() ? 'top' : 'left'] = pixelValue; + if (this.progress) + this.progress.style[this.isVertical() ? 'height' : 'width'] = pixelValue; + + //this.drawSpans(); + if(!this.dragging || !this.event) this.updateFinished(); + }, + + setValueBy: function(delta) { + this.setValue(this.value + delta); + }, + + translateToPx: function(value) { + return Math.round( + ((this.trackLength-this.handleLength)/(this.range.end-this.range.start)) * (value - this.range.start)) + "px"; + }, + + translateToValue: function(offset) { + return ((offset/(this.trackLength-this.handleLength) * (this.range.end-this.range.start)) + this.range.start); + }, + + getRange: function(range) { + var v = this.values.sortBy(Prototype.K); + range = range || 0; + return $R(v[range],v[range+1]); + }, + + minimumOffset: function(){ + return(this.isVertical() ? this.alignY : this.alignX); + }, + + maximumOffset: function(){ + return(this.isVertical() ? + (this.track.offsetHeight != 0 ? this.track.offsetHeight : + this.track.style.height.replace(/px$/,"")) - this.alignY : + (this.track.offsetWidth != 0 ? this.track.offsetWidth : + this.track.style.width.replace(/px$/,"")) - this.alignX); + }, + + isVertical: function(){ + return (this.axis == 'vertical'); + }, + + updateStyles: function() { + if (this.active) + Element.addClassName(this.handle, 'selected'); + else + Element.removeClassName(this.handle, 'selected'); + }, + + startDrag: function(event) { + if(Event.isLeftClick(event)) { + if(!this.disabled){ + this.active = true; + var handle = Event.element(event); + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + var track = handle; + if(track==this.track) { + var offsets = Position.cumulativeOffset(this.track); + this.event = event; + this.setValue(this.translateToValue( + (this.isVertical() ? pointer[1]-offsets[1] : pointer[0]-offsets[0])-(this.handleLength/2) + )); + var offsets = Position.cumulativeOffset(this.handle); + this.offsetX = (pointer[0] - offsets[0]); + this.offsetY = (pointer[1] - offsets[1]); + } else { + this.updateStyles(); + var offsets = Position.cumulativeOffset(this.handle); + this.offsetX = (pointer[0] - offsets[0]); + this.offsetY = (pointer[1] - offsets[1]); + } + } + Event.stop(event); + } + }, + + update: function(event) { + if(this.active) { + if(!this.dragging) this.dragging = true; + this.draw(event); + if(Prototype.Browser.WebKit) window.scrollBy(0,0); + Event.stop(event); + } + }, + + draw: function(event) { + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + var offsets = Position.cumulativeOffset(this.track); + pointer[0] -= this.offsetX + offsets[0]; + pointer[1] -= this.offsetY + offsets[1]; + this.event = event; + this.setValue(this.translateToValue( this.isVertical() ? pointer[1] : pointer[0] )); + if(this.initialized && this.options.onSlide) + this.options.onSlide(this.value, this); + }, + + endDrag: function(event) { + if(this.active && this.dragging) { + this.finishDrag(event, true); + Event.stop(event); + } + this.active = false; + this.dragging = false; + }, + + finishDrag: function(event, success) { + this.active = false; + this.dragging = false; + this.updateFinished(); + }, + + updateFinished: function() { + this.hiddenField.value=this.value; + this.updateStyles(); + if(this.initialized && this.options.onChange) + this.options.onChange(this.value, this); + this.event = null; + if (this.options['AutoPostBack']==true) { - Event.fireEvent(this.hiddenField, "change"); + Event.fireEvent(this.hiddenField,"change"); } } + }); \ No newline at end of file diff --git a/framework/Web/UI/WebControls/TSlider.php b/framework/Web/UI/WebControls/TSlider.php index 7ac8f033..c7363fc9 100644 --- a/framework/Web/UI/WebControls/TSlider.php +++ b/framework/Web/UI/WebControls/TSlider.php @@ -25,17 +25,12 @@ * Notice that this step will be recomputed if there is more than 200 values between the range boundaries. * You can also provide the allowed values by setting the {@link SetValues Values} array. * - * The handle sub-properties can be accessed by {@link GetHandle Handle} property. You can also provide your own control - * for the handle, using {@link SetHandleClass HandleClass} property. Note that this class must be a subclass of - * {@link TSliderHandle} - * + * A 'Progress Indicator' can be displayed within the track with the {@link SetProgressIndicator ProgressIndicator} property. + * * The TSlider control can be easily customized using CssClasses. You can provide your own css file, using the * {@link SetCssUrl CssUrl} property. - * The css class for TSlider can be set by the {@link setCssClass CssClass} property. Defaults values are "hslider" for - * an Horizontal slider, or "vslider" for a Vertical one. - * The css class for the Handle can be set by the Handle.CssClass subproperty. Defaults is "handle", which just - * draw a red block as a cursor. 'handle-image' css class is also provided for your convenience, which display an image - * as the handle. + * The css class for TSlider can be set by the {@link setCssClass CssClass} property. Default value is "Slider HorizontalSlider" + * for an horizontal slider, and "Slider VerticalSlider" for a vertical one. * * If {@link SetAutoPostBack AutoPostBack} property is true, postback is sent as soon as the value changed. * @@ -150,7 +145,22 @@ class TSlider extends TWebControl implements IPostBackDataHandler, IDataRenderer { $this->setViewState('StepSize', $value, 1.0); } + + /** + * @return boolean wether to display a progress indicator or not. Defaults to true. + */ + public function getProgressIndicator () + { + return $this->getViewState('ProgressIndicator', true); + } + /** + * @param boolean wether to display a progress indicator or not. Defaults to true. + */ + public function setProgressIndicator ($value) + { + $this->setViewState('ProgressIndicator', TPropertyValue::ensureBoolean($value), true); + } /** * @return float current value of slider */ @@ -209,48 +219,7 @@ class TSlider extends TWebControl implements IPostBackDataHandler, IDataRenderer { $this->setViewState('Values', TPropertyValue::ensureArray($value), array()); } - - /** - * Create the child controls - * Override parent implementation to create the handle control - */ - public function createChildControls() - { - $this->_handle=prado::createComponent($this->getHandleClass(), $this); - if (!$this->_handle instanceof TSliderHandle) - { - throw new TInvalidDataTypeException('slider_handle_class_invalid', get_class($this->_handle)); - } - $this->getControls()->add($this->_handle); - } - - /** - * This method will return the handle control. - * @return TSliderHandle the control for the slider's handle (must inherit from TSliderHandle} - */ - public function getHandle () - { - $this->ensureChildControls(); - return $this->_handle; - } - - /** - * @return string Custom handle class. Defaults to TSliderHandle; - */ - public function getHandleClass () - { - return $this->getViewState('HandleClass', 'TSliderHandle'); - } - - /** - * @param string Custom Handle Class - */ - public function setHandleClass ($value) - { - $this->setViewState('HandleClass', $value, 'TSliderHandle'); - } - /** * @return boolean a value indicating whether an automatic postback to the server * will occur whenever the user modifies the slider value. Defaults to false. @@ -378,9 +347,55 @@ class TSlider extends TWebControl implements IPostBackDataHandler, IDataRenderer parent::addAttributesToRender($writer); $writer->addAttribute('id',$this->getClientID()); if ($this->getCssClass()==='') - $writer->addAttribute('class', $this->getDirection()===TSliderDirection::Horizontal?'hslider':'vslider'); + { + $class=($this->getDirection()==TSliderDirection::Horizontal)?'HorizontalSlider':'VerticalSlider'; + $writer->addAttribute('class', 'Slider '.$class); + } + } + /** + * Render the body content + */ + public function renderContents($writer) + { + // Render the 'Track' + $writer->addAttribute('class', 'Track'); + $writer->addAttribute('id', $this->getClientID().'_track'); + $writer->renderBeginTag('div'); + // Render the 'Progress Indicator' + if ($this->getProgressIndicator()) + { + $writer->addAttribute('class', 'Progress'); + $writer->addAttribute('id', $this->getClientID().'_progress'); + $writer->renderBeginTag('div'); + $writer->renderEndTag(); + } + // Render the 'Ruler' + /* + * Removing for now + $writer->addAttribute('class', 'RuleContainer'); + $writer->addAttribute('id', $this->getClientID()."_rule"); + $writer->renderBeginTag('div'); + for ($i=0;$i<=100;$i+=10) + { + $writer->addAttribute('class', 'RuleMark'); + $attr=($this->getDirection()===TSliderDirection::Horizontal)?"left":"top"; + $writer->addStyleAttribute($attr, $i.'%'); + $writer->renderBeginTag('div'); + $writer->renderEndTag(); + } + $writer->renderEndTag(); + */ + + $writer->renderEndTag(); + + // Render the 'Handle' + $writer->addAttribute('class', 'Handle'); + $writer->addAttribute('id', $this->getClientID().'_handle'); + $writer->renderBeginTag('div'); + $writer->renderEndTag(); + } /** * Registers CSS and JS. * This method is invoked right before the control rendering, if the control is visible. @@ -404,10 +419,9 @@ class TSlider extends TWebControl implements IPostBackDataHandler, IDataRenderer if(($url=$this->getCssUrl())==='') { $manager=$this->getApplication()->getAssetManager(); - // publish the handle image - $manager->publishFilePath(dirname(__FILE__).DIRECTORY_SEPARATOR.'assets'.DIRECTORY_SEPARATOR.'TSliderHandle.png'); - // publish the css file - $url=$manager->publishFilePath(dirname(__FILE__).DIRECTORY_SEPARATOR.'assets'.DIRECTORY_SEPARATOR.'TSlider.css'); + // publish the assets + $url=$manager->publishFilePath(dirname(__FILE__).DIRECTORY_SEPARATOR.'assets'.DIRECTORY_SEPARATOR.'TSlider'); + $url.='/TSlider.css'; } $this->getPage()->getClientScript()->registerStyleSheetFile($url,$url); } @@ -558,60 +572,4 @@ class TSliderDirection extends TEnumerable } -/** - * TSliderHandle class - * - * TSliderHandle is responsible of rendering the 'handle' control on a {@link TSlider} - * Users can override this class to personalize the handle. - * Default class renders a 'div' tag, and apply the css class provided by {@link setCssClass CssClass} property. - * - * Two css classes are provided by default : - * - handle : render a simple red cursor - * - handle-image : render an image as handle - * - * @author Christophe Boulain - * @version $Id$ - * @package System.Web.UI.WebControls - * @since 3.1.1 - */ -class TSliderHandle extends TWebControl -{ - private $_track; - - /** - * Override parent constructor to get the track control as parameter - * - * @param TSlider track control - */ - public function __construct (TSlider $track) - { - $this->_track=$track; - } - - /** - * @return TSlider track control - */ - public function getTrack() - { - return $this->_track; - } - - public function getTagName() - { - return 'div'; - } - - /** - * Add the specified css classes to the handle - * @param THtmlWriter writer - */ - public function addAttributesToRender($writer) - { - parent::addAttributesToRender($writer); - $writer->addAttribute('id', $this->getTrack()->getClientID()."_handle"); - if($this->getCssClass()==='') - $writer->addAttribute('class', 'handle'); - } -} - ?> \ No newline at end of file diff --git a/framework/Web/UI/WebControls/assets/TSlider.css b/framework/Web/UI/WebControls/assets/TSlider.css deleted file mode 100755 index 3b068c2f..00000000 --- a/framework/Web/UI/WebControls/assets/TSlider.css +++ /dev/null @@ -1,55 +0,0 @@ -/* Css class for an Horizontal Slider */ -.hslider -{ - background-color: rgb(170,170,170); - width: 200px; - height: 5px; -} - -/* Css class for a Vertical Slider */ -.vslider -{ - background-color: rgb(170,170,170); - width: 5px; - height: 200px; -} - -/* Css class for a block cursor as handle */ -.handle -{ - background-color: red; - cursor: move; -} - -/* Dimensions of cursor depend on Direction */ -.hslider .handle -{ - width: 5px; - height: 10px; -} - -.vslider .handle -{ - width: 10px; - height: 5px; -} - -/* Css class for an image as handle */ -.handle-image -{ - width: 14px; - height: 15px; - cursor: move; - background: url("TSliderHandle.png") no-repeat; -} - -/* Center the image on the track */ -.hslider .handle-image -{ - top: -5px; -} - -.vslider .handle-image -{ - left: -5px; -} \ No newline at end of file diff --git a/framework/Web/UI/WebControls/assets/TSlider/TSlider.css b/framework/Web/UI/WebControls/assets/TSlider/TSlider.css new file mode 100755 index 00000000..c0364d91 --- /dev/null +++ b/framework/Web/UI/WebControls/assets/TSlider/TSlider.css @@ -0,0 +1,95 @@ +/* Base CSS Class for the slider */ +.Slider +{ + position: relative; + margin: 10px; + padding: 0; + background-color: Gainsboro; + border: 1px outset; +} + +/* Class for horizontal slider */ +.HorizontalSlider +{ + width: 210px; + height: 14px; +} + +/* Class for vertical slider */ +.VerticalSlider +{ + height: 210px; + width: 14px; +} + +/* Class for Track */ +.Track +{ + border-style: inset; + outline-color: invert; + outline-style: none; + outline-width: 1px; + border-width: 1px; + position: absolute; + background-color: lightBlue; +} + +/* Horizontal Track */ +.HorizontalSlider .Track +{ + + height: 3px; + top: 5px; + width: 200px; + left: 3px; +} + +/* Vertical Track */ +.VerticalSlider .Track +{ + height: 200px; + width: 3px; + left: 5px; + top: 3px; +} + +/* Class for the progress indicator */ +.Progress +{ + background-color: red; +} + +/* Horizontal one, take 100% of track (parent) height*/ +.HorizontalSlider .Progress +{ + height: 100%; +} + +/* Vertical progress indicator, take 100% of track width*/ +.VerticalSlider .Progress +{ + width: 100% +} + +/* Class for handle */ +.Handle { + border: 0px none; + position: relative; + cursor: move; +} + +/* Horizontal slider handle */ +.HorizontalSlider .Handle +{ + width: 31px; + height: 14px; + background: transparent url("TSliderHandleHorizontal.png") no-repeat scroll center; +} + +/* Vertical slider handle */ +.VerticalSlider .Handle +{ + width: 14px; + height: 31px; + background: transparent url("TSliderHandleVertical.png") no-repeat scroll center; +} diff --git a/framework/Web/UI/WebControls/assets/TSlider/TSliderHandleHorizontal.png b/framework/Web/UI/WebControls/assets/TSlider/TSliderHandleHorizontal.png new file mode 100644 index 00000000..d9dc7ff9 Binary files /dev/null and b/framework/Web/UI/WebControls/assets/TSlider/TSliderHandleHorizontal.png differ diff --git a/framework/Web/UI/WebControls/assets/TSlider/TSliderHandleVertical.png b/framework/Web/UI/WebControls/assets/TSlider/TSliderHandleVertical.png new file mode 100644 index 00000000..00532fbe Binary files /dev/null and b/framework/Web/UI/WebControls/assets/TSlider/TSliderHandleVertical.png differ diff --git a/framework/Web/UI/WebControls/assets/TSliderHandle.png b/framework/Web/UI/WebControls/assets/TSliderHandle.png deleted file mode 100644 index 47d02e88..00000000 Binary files a/framework/Web/UI/WebControls/assets/TSliderHandle.png and /dev/null differ -- cgit v1.2.3