changeset 37053:84c93a60ead3

Add new item 'rpotmeter'. This is the missing counterpart to hpotmeter and vpotmeter allowing rotary control elements in a GUI skin now. Based on an idea and a realization by Hans-Dieter Kosch, hdkosch kabelbw de. Additionally, update (and revise) documentation.
author ib
date Sat, 12 Apr 2014 23:29:29 +0000
parents 2ef6693131f7
children df3d5a18318d
files Changelog DOCS/xml/de/skin.xml DOCS/xml/en/skin.xml gui/app/app.c gui/app/app.h gui/app/gui.h gui/skin/skin.c gui/ui/main.c gui/ui/playbar.c gui/ui/render.c gui/ui/ui.h gui/win32/gui.c gui/win32/gui.h gui/win32/skinload.c gui/win32/skinload.h gui/win32/widgetrender.c
diffstat 16 files changed, 468 insertions(+), 46 deletions(-) [+]
line wrap: on
line diff
--- a/Changelog	Fri Apr 11 09:34:31 2014 +0000
+++ b/Changelog	Sat Apr 12 23:29:29 2014 +0000
@@ -40,7 +40,7 @@
     * Console message with information on deprecated (but still supported)
       entries in the skin configuration file
     * New symbol character (r) and new dynamic label variables ($D, $U, $P)
-    * New item: pimage
+    * New items (pimage, rpotmeter)
 
   1.1: "We gave up on 1.0"
 
--- a/DOCS/xml/de/skin.xml	Fri Apr 11 09:34:31 2014 +0000
+++ b/DOCS/xml/de/skin.xml	Sat Apr 12 23:29:29 2014 +0000
@@ -444,15 +444,28 @@
             </literal>
           </term>
           <listitem>
+            <para></para>
+          </listitem>
+        </varlistentry>
+
+        <varlistentry>
+          <term>
+            <literal>
+              <anchor id="skin-rpotmeter"/>rpotmeter = button, bwidth, bheight, phases, numphases, x<subscript>0</subscript>, y<subscript>0</subscript>, x<subscript>1</subscript>, y<subscript>1</subscript>, default, X, Y, width, height, message
+            </literal>
+          </term>
+          <listitem>
             <para>
-              Platziere einen horizontal (hpotmeter) oder vertikal (vpotmeter) Potentiometer mit
+              Platziere ein horizontales (<literal>hpotmeter</literal>), vertikales (<literal>vpotmeter</literal>) oder drehbares (<literal>rpotmeter</literal>) Potentiometer
               der Größe <literal>width</literal> * <literal>height</literal> an Position
               <literal>X,Y</literal>. Die Grafik kann in unterschiedliche Teile für die
               verschiedenen Phasen des Potentiometers aufgeteilt werden (du kannst zum Beispiel
               eines für die Lautstärkeregelung haben, das von rot nach grün wechselt, während sich
               sein Wert vom Minimum zum Maximum ändert.).
-              <literal>hpotmeter</literal> kann einen Button besitzen, der horizontal gezogen
-              werden kann. Die Parameter sind:
+              Alle Potentiometer können einen Button besitzen, der bei einem
+              <literal>hpotmeter</literal> und <literal>vpotmeter</literal>
+              gezogen werden kann. Ein <literal>rpotmeter</literal> kann auch
+              ohne Button gedreht werden. Die Parameter sind:
             </para>
             <itemizedlist>
               <listitem>
@@ -471,9 +484,11 @@
               <listitem>
                 <para>
                   <literal>phases</literal> - die für die verschiedenen Phasen
-                  zu verwendende Grafik des hpotmeter. Ein spezieller Wert von <literal>NULL</literal>
+                  zu verwendende Grafik des Potentiometers. Ein spezieller Wert von <literal>NULL</literal>
                   kann benutzt werden, wenn du keine solche Grafik anwenden willst. Die Grafik muss
-                  in <literal>numphases</literal> untereinander (bzw. für vpotmeter nebeneinander) liegende Teile wie folgt aufgeteilt werden:
+                  in <literal>numphases</literal> untereinander (bzw. für
+                  <literal>vpotmeter</literal> nebeneinander) liegende Teile
+                  wie folgt aufgeteilt werden:
                 </para>
                 <informalfigure>
                   <screen>
@@ -497,25 +512,44 @@
               </listitem>
               <listitem>
                 <para>
-                  <literal>default</literal> - Standardwert für hpotmeter
+                  <literal>x<subscript>0</subscript></literal>,
+                  <literal>y<subscript>0</subscript></literal> und
+                  <literal>x<subscript>1</subscript></literal>,
+                  <literal>y<subscript>1</subscript></literal> - Position des
+                  0%-Start-Punkts und 100%-Stopp-Punkts des Potentiometers
+                  <emphasis role="bold">(nur für <literal>rpotmeter</literal>)</emphasis>
+                </para>
+                <para>
+                  Die erste Koordinate <literal>x<subscript>0</subscript>,y<subscript>0</subscript></literal>
+                  definiert den 0%-Start-Punkt (auf dem Rand des
+                  Potentiometer) in der Grafik für Phase #1 und die zweite
+                  Koordinate <literal>x<subscript>1</subscript>,y<subscript>1</subscript></literal>
+                  den 100%-Stopp-Punkt in der Grafik für Phase #n - mit
+                  anderen Worten: die Koordinaten der Spitze der Markierung
+                  auf dem Potentiometer in den beiden einzelnen Grafiken.
+                </para>
+              </listitem>
+              <listitem>
+                <para>
+                  <literal>default</literal> - Voreinstellung des Potentiometers
                   (im Bereich <literal>0</literal> bis <literal>100</literal>)
                 </para>
               </listitem>
               <listitem>
                 <para>
-                  <literal>X</literal>, <literal>Y</literal> - Position fürs hpotmeter
+                  <literal>X</literal>, <literal>Y</literal> - Position des Potentiometers
                 </para>
               </listitem>
               <listitem>
                 <para>
                   <literal>width</literal>, <literal>height</literal> - Breite und Höhe
-                  des <literal>hpotmeter</literal>
+                  des Potentiometers
                 </para>
               </listitem>
               <listitem>
                 <para>
                   <literal>message</literal> - die Nachricht, die erzeugt werden soll,
-                  wenn der Wert des <literal>hpotmeter</literal> geändert wird
+                  wenn der Wert des Potentiometers geändert wird
                 </para>
               </listitem>
             </itemizedlist>
--- a/DOCS/xml/en/skin.xml	Fri Apr 11 09:34:31 2014 +0000
+++ b/DOCS/xml/en/skin.xml	Sat Apr 12 23:29:29 2014 +0000
@@ -377,13 +377,23 @@
   <anchor id="skin-vpotmeter"/>vpotmeter = button, bwidth, bheight, phases, numphases, default, X, Y, width, height, message
   </literal></term>
   <listitem><para>
-  Place a horizontal (hpotmeter) or vertical (vpotmeter) potmeter of
+  </para></listitem>
+</varlistentry>
+
+<varlistentry>
+  <term><literal>
+  <anchor id="skin-rpotmeter"/>rpotmeter = button, bwidth, bheight, phases, numphases, x<subscript>0</subscript>, y<subscript>0</subscript>, x<subscript>1</subscript>, y<subscript>1</subscript>, default, X, Y, width, height, message
+  </literal></term>
+  <listitem><para>
+  Place a horizontal (<literal>hpotmeter</literal>), vertical (<literal>vpotmeter</literal>) or rotary (<literal>rpotmeter</literal>) potmeter of
   <literal>width</literal> * <literal>height</literal> size at position
   <literal>X,Y</literal>. The image can be divided into different parts for the
   different phases of the potmeter (for example, you can have a pot for volume
   control that turns from green to red while its value changes from the minimum
-  to the maximum.). <literal>hpotmeter</literal> can have a button that can be
-  dragged horizontally. The parameters are:
+  to the maximum). All potentiometers can have a button that can be dragged
+  with a <literal>hpotmeter</literal> and <literal>vpotmeter</literal>. A
+  <literal>rpotmeter</literal> can be spun even without a button. The
+  parameters are:
   <itemizedlist>
   <listitem><para>
     <literal>button</literal> - the image to be used for the
@@ -397,9 +407,10 @@
   </para></listitem>
   <listitem><para>
     <literal>phases</literal> - the image to be used for the
-    different phases of the hpotmeter. A special value of <literal>NULL</literal>
+    different phases of the potentiometer. A special value of <literal>NULL</literal>
     can be used if you want no such image. The image must be divided into
-    <literal>numphases</literal> parts below each other (resp. side by side for vpotmeter) like this:
+    <literal>numphases</literal> parts below each other (resp. side by side
+    for <literal>vpotmeter</literal>) like this:
     <informalfigure><screen>
 +------------+
 |  phase #1  |                vpotmeter only:
@@ -417,19 +428,31 @@
     <literal>phases</literal> image
   </para></listitem>
   <listitem><para>
-    <literal>default</literal> - default value for hpotmeter
+    <literal>x<subscript>0</subscript></literal>,
+    <literal>y<subscript>0</subscript></literal> and
+    <literal>x<subscript>1</subscript></literal>,
+    <literal>y<subscript>1</subscript></literal> - position of the 0% start
+    point and 100% stop point for the potentiometer <emphasis role="bold">(<literal>rpotmeter</literal> only)</emphasis></para>
+    <para>The first coordinate <literal>x<subscript>0</subscript>,y<subscript>0</subscript></literal>
+    defines the 0% start point (on the edge of the potentiometer) in the
+    image for phase #1 and the second coordinate <literal>x<subscript>1</subscript>,y<subscript>1</subscript></literal>
+    the 100% stop point in the image for phase #n - in other words, the
+    coordinates of the tip of the mark on the potentiometer in the two
+    individual images.</para></listitem>
+  <listitem><para>
+    <literal>default</literal> - default value for the potentiometer
     (in the range <literal>0</literal> to <literal>100</literal>)
   </para></listitem>
   <listitem><para>
-    <literal>X</literal>, <literal>Y</literal> - position for the hpotmeter
+    <literal>X</literal>, <literal>Y</literal> - position for the potentiometer
   </para></listitem>
   <listitem><para>
     <literal>width</literal>, <literal>height</literal> - width and height
-    of the <literal>hpotmeter</literal>
+    of the potentiometer
   </para></listitem>
   <listitem><para>
     <literal>message</literal> - the message to be generated when the
-    value of <literal>hpotmeter</literal> is changed
+    value of the potentiometer is changed
   </para></listitem>
   </itemizedlist>
   </para></listitem>
--- a/gui/app/app.c	Fri Apr 11 09:34:31 2014 +0000
+++ b/gui/app/app.c	Sat Apr 12 23:29:29 2014 +0000
@@ -21,6 +21,8 @@
  * @brief GUI application helpers
  */
 
+#include <math.h>
+
 #include "app.h"
 #include "gui.h"
 #include "gui/skin/font.h"
@@ -180,6 +182,31 @@
 }
 
 /**
+ * @brief Calculate the radian of a point inside the visual representation
+ *        of an item.
+ *
+ * @param item pointer to the item
+ * @param x x position of the point
+ * @param y y position of the point
+ *
+ * @return radian of the point
+ *
+ * @note The return value is a @a clockwise radian.
+ */
+double appRadian(guiItem *item, int x, int y)
+{
+    double tx, ty;
+
+    // transform the center to (0,0)
+    tx = x - item->width / 2.0;
+    ty = y - item->height / 2.0;
+
+    // the y-axis is upside down and must be mirrored
+    // the x-axis is being mirrored for a clockwise radian
+    return (tx == 0.0 && ty == 0.0 ? 0.0 : atan2(-ty, -tx) + M_PI);
+}
+
+/**
  * @brief Modify the value of the item belonging to an event.
  *
  * @param event event
--- a/gui/app/app.h	Fri Apr 11 09:34:31 2014 +0000
+++ b/gui/app/app.h	Sat Apr 12 23:29:29 2014 +0000
@@ -102,6 +102,7 @@
     itDLabel,
     itHPotmeter,
     itVPotmeter,
+    itRPotmeter,
     itPimage,
     itMenu,
     itPLMButton = 100,
@@ -132,6 +133,7 @@
     int pbwidth, pbheight;
     int numphases;
     float value;
+    double zeropoint, arclength;
 
     int message;
 
@@ -180,6 +182,7 @@
 guiItem *appFindItem(int event);
 int appFindMessage(const char *name);
 void appFreeStruct(void);
+double appRadian(guiItem *item, int x, int y);
 void btnModify(int event, float value);
 void btnSet(int event, int state);
 void btnValue(int event, float *value);
--- a/gui/app/gui.h	Fri Apr 11 09:34:31 2014 +0000
+++ b/gui/app/gui.h	Sat Apr 12 23:29:29 2014 +0000
@@ -48,9 +48,9 @@
 #define isInside(x, y, tx, ty, bx, by) ((x) > (tx) && (y) > (ty) && (x) < (bx) && (y) < (by))
 
 /// Check whether #guiItem @a item has a button (and thus a pressed state).
-#define hasButton(item) (item.type == itButton || item.type == itHPotmeter || item.type == itVPotmeter)
+#define hasButton(item) (item.type == itButton || item.type == itHPotmeter || item.type == itVPotmeter || item.type == itRPotmeter)
 
 /// Check whether #guiItem @a item utilizes member 'value'
-#define hasValue(item) (item.type == itHPotmeter || item.type == itVPotmeter || item.type == itPimage)
+#define hasValue(item) (item.type == itHPotmeter || item.type == itVPotmeter || item.type == itRPotmeter || item.type == itPimage)
 
 #endif /* MPLAYER_GUI_GUI_H */
--- a/gui/skin/skin.c	Fri Apr 11 09:34:31 2014 +0000
+++ b/gui/skin/skin.c	Sat Apr 12 23:29:29 2014 +0000
@@ -21,6 +21,7 @@
  * @brief Skin parser
  */
 
+#include <math.h>
 #include <stdio.h>
 #include <string.h>
 
@@ -35,6 +36,7 @@
 
 #include "help_mp.h"
 #include "mp_msg.h"
+#include "libavutil/attributes.h"
 #include "libavutil/avstring.h"
 #include "libavutil/common.h"
 
@@ -589,13 +591,15 @@
 }
 
 /**
- * @brief Parse a hpotmeter or vpotmeter definition.
+ * @brief Parse a hpotmeter, vpotmeter or rpotmeter definition.
  *
- *        Parameters: button,bwidth,bheight,phases,numphases,default,x,y,width,height,message
+ *        Parameters: button,bwidth,bheight,phases,numphases,[x0,y0,x1,y1,]default,x,y,width,height,message
  *
  * @param item pointer to item to store the parameters in
  * @param in definition to be analyzed
  *
+ * @note item->type is already available.
+ *
  * @return 0 (ok) or 1 (error)
  */
 static int parse_potmeter(guiItem *item, char *in)
@@ -603,6 +607,7 @@
     unsigned char bfname[256];
     unsigned char phfname[256];
     unsigned char buf[512];
+    int i = 0, av_uninit(x0), av_uninit(y0), av_uninit(x1), av_uninit(y1);
     int bwidth, bheight, num, d, x, y, w, h, message;
 
     if (!window_item(currItem))
@@ -613,17 +618,25 @@
     if (in_window("menu"))
         return 1;
 
-    cutStr(in, bfname, ',', 0);
-    bwidth  = cutInt(in, ',', 1);
-    bheight = cutInt(in, ',', 2);
-    cutStr(in, phfname, ',', 3);
-    num = cutInt(in, ',', 4);
-    d   = cutInt(in, ',', 5);
-    x   = cutInt(in, ',', 6);
-    y   = cutInt(in, ',', 7);
-    w   = cutInt(in, ',', 8);
-    h   = cutInt(in, ',', 9);
-    cutStr(in, buf, ',', 10);
+    cutStr(in, bfname, ',', i++);
+    bwidth  = cutInt(in, ',', i++);
+    bheight = cutInt(in, ',', i++);
+    cutStr(in, phfname, ',', i++);
+    num = cutInt(in, ',', i++);
+
+    if (item->type == itRPotmeter) {
+        x0 = cutInt(in, ',', i++);
+        y0 = cutInt(in, ',', i++);
+        x1 = cutInt(in, ',', i++);
+        y1 = cutInt(in, ',', i++);
+    }
+
+    d = cutInt(in, ',', i++);
+    x = cutInt(in, ',', i++);
+    y = cutInt(in, ',', i++);
+    w = cutInt(in, ',', i++);
+    h = cutInt(in, ',', i++);
+    cutStr(in, buf, ',', i++);
 
     message = appFindMessage(buf);
 
@@ -656,6 +669,19 @@
     item->message   = message;
     item->pressed   = btnReleased;
 
+    if (item->type == itRPotmeter) {
+        mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]     start: %d,%d / stop: %d,%d\n", x0, y0, x1, y1);
+
+        item->zeropoint = appRadian(item, x0, y0);
+        item->arclength = appRadian(item, x1, y1) - item->zeropoint;
+
+        if (item->arclength < 0.0)
+            item->arclength += 2 * M_PI;
+        // else check if radians of (x0,y0) and (x1,y1) only differ below threshold
+        else if (item->arclength < 0.05)
+            item->arclength = 2 * M_PI;
+    }
+
     item->Bitmap.Image = NULL;
 
     if (strcmp(phfname, "NULL") != 0) {
@@ -735,6 +761,29 @@
 }
 
 /**
+ * @brief Parse a @a rpotmeter definition.
+ *
+ *        Syntax: rpotmeter=button,bwidth,bheight,phases,numphases,x0,y0,x1,y1,default,x,y,width,height,message
+ *
+ * @param in definition to be analyzed
+ *
+ * @return 0 (ok) or 1 (error)
+ */
+static int item_rpotmeter(char *in)
+{
+    guiItem *item;
+
+    item = next_item();
+
+    if (!item)
+        return 1;
+
+    item->type = itRPotmeter;
+
+    return parse_potmeter(item, in);
+}
+
+/**
  * @brief Parse a @a potmeter definition.
  *
  *        Syntax: potmeter=phases,numphases,default,x,y,width,height,message
@@ -1075,6 +1124,7 @@
     { "menu",       item_menu       },
     { "pimage",     item_pimage     },
     { "potmeter",   item_potmeter   }, // legacy
+    { "rpotmeter",  item_rpotmeter  },
     { "section",    item_section    },
     { "selected",   item_selected   },
     { "slabel",     item_slabel     },
--- a/gui/ui/main.c	Fri Apr 11 09:34:31 2014 +0000
+++ b/gui/ui/main.c	Sat Apr 12 23:29:29 2014 +0000
@@ -18,6 +18,7 @@
 
 /* main window */
 
+#include <math.h>
 #include <stdlib.h>
 #include <stdio.h>
 #include <sys/stat.h>
@@ -95,10 +96,13 @@
  static int     itemtype = 0;
         int     i;
         guiItem * item = NULL;
+ static double  prev_point;
+        double  point;
         float   value = 0.0f;
 
  static int     SelectedItem = -1;
         int     currentselected = -1;
+ static int     endstop;
 
  for ( i=0;i <= guiApp.IndexOfMainItems;i++ )
   if ( ( guiApp.mainItems[i].pressed != btnDisabled )&&
@@ -135,6 +139,13 @@
                   { item->pressed=btnDisabled; }
                  break;
            }*/
+          if ( itemtype == itRPotmeter )
+           {
+            prev_point=appRadian( item, X - item->x, Y - item->y ) - item->zeropoint;
+            if ( prev_point < 0.0 ) prev_point+=2*M_PI;
+            if ( prev_point <= item->arclength ) endstop=False;
+            else endstop=STOPPED_AT_0 + STOPPED_AT_100;   // block movement
+           }
           break;
    case wsRLMouseButton:
           boxMoved=False;
@@ -154,6 +165,12 @@
             case itVPotmeter:
                  value=100.0 - 100.0 * ( Y - item->y ) / item->height;
                  break;
+            case itRPotmeter:
+                 if ( endstop ) { itemtype=0; return; }
+                 point=appRadian( item, X - item->x, Y - item->y ) - item->zeropoint;
+                 if ( point < 0.0 ) point+=2*M_PI;
+                 value=100.0 * point / item->arclength;
+                 break;
            }
           uiEvent( item->message,value );
           itemtype=0;
@@ -170,7 +187,7 @@
           if (currentselected != - 1)
            {
             item=&guiApp.mainItems[currentselected];
-            if ( ( item->type == itHPotmeter )||( item->type == itVPotmeter ) )
+            if ( ( item->type == itHPotmeter )||( item->type == itVPotmeter )||( item->type == itRPotmeter ) )
              {
               item->value=constrain(item->value + value);
               uiEvent( item->message,item->value );
@@ -189,6 +206,45 @@
             case itPRMButton:
                  if (guiApp.menuIsPresent) guiApp.menuWindow.MouseHandler( 0,RX,RY,0,0 );
                  break;
+            case itRPotmeter:
+                 point=appRadian( item, X - item->x, Y - item->y ) - item->zeropoint;
+                 if ( point < 0.0 ) point+=2*M_PI;
+                 if ( item->arclength < 2 * M_PI )
+                 /* a potmeter with separated 0% and 100% positions */
+                  {
+                   value=item->value;
+                   if ( point - prev_point > M_PI )
+                   /* turned beyond the 0% position */
+                    {
+                     if ( !endstop )
+                      {
+                       endstop=STOPPED_AT_0;
+                       value=0.0f;
+                      }
+                    }
+                   else if ( prev_point - point > M_PI )
+                   /* turned back from beyond the 0% position */
+                    {
+                     if ( endstop == STOPPED_AT_0 ) endstop=False;
+                    }
+                   else if ( prev_point <= item->arclength && point > item->arclength )
+                   /* turned beyond the 100% position */
+                    {
+                     if ( !endstop )
+                      {
+                       endstop=STOPPED_AT_100;
+                       value=100.0f;
+                      }
+                    }
+                   else if ( prev_point > item->arclength && point <= item->arclength )
+                   /* turned back from beyond the 100% position */
+                    {
+                     if ( endstop == STOPPED_AT_100 ) endstop=False;
+                    }
+                  }
+                 if ( !endstop ) value=100.0 * point / item->arclength;
+                 prev_point=point;
+                 goto potihandled;
             case itVPotmeter:
                  value=100.0 - 100.0 * ( Y - item->y ) / item->height;
                  goto potihandled;
--- a/gui/ui/playbar.c	Fri Apr 11 09:34:31 2014 +0000
+++ b/gui/ui/playbar.c	Sat Apr 12 23:29:29 2014 +0000
@@ -18,6 +18,7 @@
 
 /* playbar window */
 
+#include <math.h>
 #include <stdlib.h>
 #include <stdio.h>
 #include <sys/stat.h>
@@ -123,10 +124,13 @@
  static int     itemtype = 0;
         int     i;
         guiItem * item = NULL;
+ static double  prev_point;
+        double  point;
         float   value = 0.0f;
 
  static int     SelectedItem = -1;
         int     currentselected = -1;
+ static int     endstop;
 
  for ( i=0;i <= guiApp.IndexOfPlaybarItems;i++ )
    if ( ( guiApp.playbarItems[i].pressed != btnDisabled )&&
@@ -162,6 +166,12 @@
                  ( ( item->message == evPauseSwitchToPlay && item->message == evPlaySwitchToPause ) ) ) )
                  { item->pressed=btnDisabled; }
                break;
+          case itRPotmeter:
+               prev_point=appRadian( item, X - item->x, Y - item->y ) - item->zeropoint;
+               if ( prev_point < 0.0 ) prev_point+=2*M_PI;
+               if ( prev_point <= item->arclength ) endstop=False;
+               else endstop=STOPPED_AT_0 + STOPPED_AT_100;   // block movement
+               break;
          }
 
         break;
@@ -183,6 +193,12 @@
           case itVPotmeter:
                value=100.0 - 100.0 * ( Y - item->y ) / item->height;
                break;
+          case itRPotmeter:
+               if ( endstop ) { itemtype=0; return; }
+               point=appRadian( item, X - item->x, Y - item->y ) - item->zeropoint;
+               if ( point < 0.0 ) point+=2*M_PI;
+               value=100.0 * point / item->arclength;
+               break;
          }
         uiEvent( item->message,value );
 
@@ -195,7 +211,7 @@
         if (currentselected != - 1)
          {
           item=&guiApp.playbarItems[currentselected];
-          if ( ( item->type == itHPotmeter )||( item->type == itVPotmeter ) )
+          if ( ( item->type == itHPotmeter )||( item->type == itVPotmeter )||( item->type == itRPotmeter ) )
            {
             item->value=constrain(item->value + value);
             uiEvent( item->message,item->value );
@@ -210,6 +226,45 @@
           case itPRMButton:
                if (guiApp.menuIsPresent) guiApp.menuWindow.MouseHandler( 0,RX,RY,0,0 );
                break;
+          case itRPotmeter:
+               point=appRadian( item, X - item->x, Y - item->y ) - item->zeropoint;
+               if ( point < 0.0 ) point+=2*M_PI;
+               if ( item->arclength < 2 * M_PI )
+               /* a potmeter with separated 0% and 100% positions */
+                {
+                 value=item->value;
+                 if ( point - prev_point > M_PI )
+                 /* turned beyond the 0% position */
+                  {
+                   if ( !endstop )
+                    {
+                     endstop=STOPPED_AT_0;
+                     value=0.0f;
+                    }
+                  }
+                 else if ( prev_point - point > M_PI )
+                 /* turned back from beyond the 0% position */
+                  {
+                   if ( endstop == STOPPED_AT_0 ) endstop=False;
+                  }
+                 else if ( prev_point <= item->arclength && point > item->arclength )
+                 /* turned beyond the 100% position */
+                  {
+                   if ( !endstop )
+                    {
+                     endstop=STOPPED_AT_100;
+                     value=100.0f;
+                    }
+                  }
+                 else if ( prev_point > item->arclength && point <= item->arclength )
+                 /* turned back from beyond the 100% position */
+                  {
+                   if ( endstop == STOPPED_AT_100 ) endstop=False;
+                  }
+                }
+               if ( !endstop ) value=100.0 * point / item->arclength;
+               prev_point=point;
+               goto potihandled;
           case itVPotmeter:
                value=100.0 - 100.0 * ( Y - item->y ) / item->height;
                goto potihandled;
--- a/gui/ui/render.c	Fri Apr 11 09:34:31 2014 +0000
+++ b/gui/ui/render.c	Sat Apr 12 23:29:29 2014 +0000
@@ -21,6 +21,7 @@
  * @brief GUI rendering
  */
 
+#include <math.h>
 #include <stdint.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -35,6 +36,7 @@
 #include "access_mpcontext.h"
 #include "help_mp.h"
 #include "libavutil/avstring.h"
+#include "libavutil/common.h"
 #include "osdep/timer.h"
 #include "stream/stream.h"
 
@@ -479,6 +481,29 @@
             PutImage(item->x, item->y + (item->height - item->pbheight) * (1.0 - item->value / 100.0), db, dw, &item->Mask, 3, index, True);
             break;
 
+        case itRPotmeter:
+
+            PutImage(item->x, item->y, db, dw, &item->Bitmap, item->numphases, (item->numphases - 1) * item->value / 100.0, True);
+
+            if (item->Mask.Image) {
+                double radius, radian;
+                int y;
+
+                // keep the button inside the potmeter outline
+                radius = (FFMIN(item->width, item->height) - FFMAX(item->pbwidth, item->pbheight)) / 2.0;
+
+                radian = item->value / 100.0 * item->arclength + item->zeropoint;
+
+                // coordinates plus a correction for a non-square item
+                // (remember: both axes are mirrored, we have a clockwise radian)
+                x = radius * (1 + cos(radian)) + FFMAX(0, (item->width - item->height) / 2.0) + 0.5;
+                y = radius * (1 + sin(radian)) + FFMAX(0, (item->height - item->width) / 2.0) + 0.5;
+
+                PutImage(item->x + x, item->y + y, db, dw, &item->Mask, 3, index, True);
+            }
+
+            break;
+
         case itSLabel:
 
             if (item->width == -1)
--- a/gui/ui/ui.h	Fri Apr 11 09:34:31 2014 +0000
+++ b/gui/ui/ui.h	Sat Apr 12 23:29:29 2014 +0000
@@ -19,6 +19,14 @@
 #ifndef MPLAYER_GUI_UI_H
 #define MPLAYER_GUI_UI_H
 
+/// End stops of a rotary potentiometer (::itRPotmeter)
+enum
+{
+  NOT_STOPPED,
+  STOPPED_AT_0,
+  STOPPED_AT_100
+};
+
 extern unsigned char * menuDrawBuffer;
 extern int             mainVisible;
 
--- a/gui/win32/gui.c	Fri Apr 11 09:34:31 2014 +0000
+++ b/gui/win32/gui.c	Sat Apr 12 23:29:29 2014 +0000
@@ -25,6 +25,7 @@
 #include <stdlib.h>
 #include <ctype.h>
 #include <fcntl.h>
+#include <math.h>
 #include <windows.h>
 #include <windowsx.h>
 #include <shlobj.h>
@@ -117,6 +118,19 @@
     return "?";
 }
 
+double appRadian (widget *item, int x, int y)
+{
+  double tx, ty;
+
+  // transform the center to (0,0)
+  tx = x - item->wwidth / 2.0;
+  ty = y - item->wheight / 2.0;
+
+  // the y-axis is upside down and must be mirrored
+  // the x-axis is being mirrored for a clockwise radian
+  return (tx == 0.0 && ty == 0.0 ? 0.0 : atan2(-ty, -tx) + M_PI);
+}
+
 static void console_toggle(gui_t *gui)
 {
     if (console_state)
@@ -304,10 +318,10 @@
 
     if(!hwnd) return;
 
-    /* load all hpotmeters vpotmeters pimages */
+    /* load all hpotmeters vpotmeters rpotmeters pimages */
     for(i=0; i<gui->skin->widgetcount; i++)
     {
-        if(gui->skin->widgets[i]->type == tyHpotmeter || gui->skin->widgets[i]->type == tyVpotmeter || gui->skin->widgets[i]->type == tyPimage)
+        if(gui->skin->widgets[i]->type == tyHpotmeter || gui->skin->widgets[i]->type == tyVpotmeter || gui->skin->widgets[i]->type == tyRpotmeter || gui->skin->widgets[i]->type == tyPimage)
         {
             if(gui->skin->widgets[i]->msg == evSetVolume)
                 gui->skin->widgets[i]->value = guiInfo.Volume;
@@ -695,6 +709,8 @@
 /* Window Proc for the gui Window */
 static LRESULT CALLBACK EventProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
 {
+    static double prev_point;
+    static int endstop;
     gui_t *gui = (gui_t *) GetWindowLongPtr(hWnd, GWLP_USERDATA);
 
     /* Avoid processing when then window doesn't match gui mainwindow */
@@ -833,6 +849,14 @@
                 renderwidget(gui->skin, get_drawground(hWnd), gui->activewidget, 0);
                 RedrawWindow(hWnd, NULL, NULL, RDW_INVALIDATE);
                 handlemsg(hWnd, gui->activewidget->msg);
+
+                if(gui->activewidget->type == tyRpotmeter)
+                {
+                    prev_point = appRadian(gui->activewidget, gui->mousewx, gui->mousewy) - gui->activewidget->zeropoint;
+                    if(prev_point < 0.0) prev_point += 2 * M_PI;
+                    if(prev_point <= gui->activewidget->arclength) endstop=FALSE;
+                    else endstop=STOPPED_AT_0 + STOPPED_AT_100;   // block movement
+                }
             }
             break;
         }
@@ -942,8 +966,49 @@
                         item->y = GET_Y_LPARAM(lParam) - gui->mousewy;
                         item->value = 100.0 - 100.0 * (item->y - item->wy) / (item->wheight - item->height);
                     }
+                    if(item->type == tyRpotmeter)
+                    {
+                        double point;
 
-                    if((item->type == tyHpotmeter) || (item->type == tyVpotmeter))
+                        point = appRadian(item, GET_X_LPARAM(lParam) - gui->activewidget->x, GET_Y_LPARAM(lParam) - gui->activewidget->y) - item->zeropoint;
+                        if(point < 0.0) point += 2 * M_PI;
+                        if(item->arclength < 2 * M_PI)
+                        /* a potmeter with separated 0% and 100% positions */
+                        {
+                            if(point - prev_point > M_PI)
+                            /* turned beyond the 0% position */
+                            {
+                                if(!endstop)
+                                {
+                                    endstop = STOPPED_AT_0;
+                                    item->value = 0.0f;
+                                }
+                            }
+                            else if(prev_point - point > M_PI)
+                            /* turned back from beyond the 0% position */
+                            {
+                                if(endstop == STOPPED_AT_0) endstop = FALSE;
+                            }
+                            else if(prev_point <= item->arclength && point > item->arclength)
+                            /* turned beyond the 100% position */
+                            {
+                                if (!endstop)
+                                {
+                                    endstop = STOPPED_AT_100;
+                                    item->value = 100.0f;
+                                }
+                            }
+                            else if(prev_point > item->arclength && point <= item->arclength)
+                            /* turned back from beyond the 100% position */
+                            {
+                                if(endstop == STOPPED_AT_100) endstop = FALSE;
+                            }
+                        }
+                        if(!endstop) item->value = 100.0 * point / item->arclength;
+                        prev_point = point;
+                    }
+
+                    if((item->type == tyHpotmeter) || (item->type == tyVpotmeter) || (item->type == tyRpotmeter))
                     {
                         /* Bound checks */
                         if(item->value > 100.0f)
--- a/gui/win32/gui.h	Fri Apr 11 09:34:31 2014 +0000
+++ b/gui/win32/gui.h	Sat Apr 12 23:29:29 2014 +0000
@@ -107,6 +107,7 @@
 int parse_filename(char *file, play_tree_t *playtree, m_config_t *mconfig, int clear);
 void capitalize(char *fname);
 LPSTR acp(LPCSTR utf8);
+double appRadian(widget *item, int x, int y);
 
 void renderinfobox(skin_t *skin, window_priv_t *priv);
 void renderwidget(skin_t *skin, image *dest, widget *item, int state);
--- a/gui/win32/skinload.c	Fri Apr 11 09:34:31 2014 +0000
+++ b/gui/win32/skinload.c	Sat Apr 12 23:29:29 2014 +0000
@@ -25,12 +25,15 @@
 #include <stdlib.h>
 #include <stdio.h>
 #include <inttypes.h>
+#include <math.h>
 #include <windows.h>
 
 #include "mp_msg.h"
 #include "help_mp.h"
 #include "cpudetect.h"
 #include "libswscale/swscale.h"
+#include "libavutil/attributes.h"
+#include "libavutil/common.h"
 #include "libavutil/imgutils.h"
 #include "gui.h"
 #include "gui/util/mem.h"
@@ -364,12 +367,13 @@
               (mywidget->bitmap[0]) ? mywidget->bitmap[0]->name : NULL,
                mywidget->x, mywidget->y, mywidget->width, mywidget->height, mywidget->msg);
     }
-    else if(!strncmp(desc, "hpotmeter", 9) || !strncmp(desc, "vpotmeter", 9) || /* legacy */ !strncmp(desc, "potmeter", 8))
+    else if(!strncmp(desc, "hpotmeter", 9) || !strncmp(desc, "vpotmeter", 9) || !strncmp(desc, "rpotmeter", 9) || /* legacy */ !strncmp(desc, "potmeter", 8))
     {
         int base = counttonextchar(desc, '=') + 1;
-        int i;
+        int i, av_uninit(x0), av_uninit(y0), av_uninit(x1), av_uninit(y1);
         /* hpotmeter = button, bwidth, bheight, phases, numphases, default, X, Y, width, height, message */
         if(!strncmp(desc, "vpotmeter", 9)) mywidget->type = tyVpotmeter;
+        else if(!strncmp(desc, "rpotmeter", 9)) mywidget->type = tyRpotmeter;
         else mywidget->type = tyHpotmeter;
         if (*desc == 'p')
         {
@@ -388,6 +392,15 @@
         }
         mywidget->bitmap[1] = pngRead(skin, findnextstring(temp, desc, &base));
         mywidget->phases = atoi(findnextstring(temp, desc, &base));
+
+        if (*desc == 'r')
+        {
+            x0 = atoi(findnextstring(temp, desc, &base));
+            y0 = atoi(findnextstring(temp, desc, &base));
+            x1 = atoi(findnextstring(temp, desc, &base));
+            y1 = atoi(findnextstring(temp, desc, &base));
+        }
+
         mywidget->value = atof(findnextstring(temp, desc, &base));
         mywidget->x = mywidget->wx = atoi(findnextstring(temp, desc, &base));
         mywidget->y = mywidget->wy = atoi(findnextstring(temp, desc, &base));
@@ -406,12 +419,24 @@
                 break;
             }
         }
-        mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[SKIN] [ITEM] %s %s %i %i %s %i %f %i %i %i %i msg %i\n",
-                (mywidget->type == tyHpotmeter) ? "[HPOTMETER]" : "[VPOTMETER]",
+        if (*desc == 'r')
+        {
+            mywidget->zeropoint = appRadian(mywidget, x0, y0);
+            mywidget->arclength = appRadian(mywidget, x1, y1) - mywidget->zeropoint;
+
+            if (mywidget->arclength < 0.0) mywidget->arclength += 2 * M_PI;
+            // else check if radians of (x0,y0) and (x1,y1) only differ below threshold
+            else if (mywidget->arclength < 0.05) mywidget->arclength = 2 * M_PI;
+        }
+        mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[SKIN] [ITEM] %s %s %i %i %s %i ",
+                (mywidget->type == tyHpotmeter) ? "[HPOTMETER]" : (mywidget->type == tyVpotmeter) ? "[VPOTMETER]" : "[RPOTMETER]",
                 (mywidget->bitmap[0]) ? mywidget->bitmap[0]->name : NULL,
                 mywidget->width, mywidget->height,
                 (mywidget->bitmap[1]) ? mywidget->bitmap[1]->name : NULL,
-                mywidget->phases, mywidget->value,
+                mywidget->phases);
+        if (*desc == 'r')
+            mp_msg(MSGT_GPLAYER, MSGL_DBG2, "%i,%i %i,%i ", x0, y0, x1, y1);
+        mp_msg(MSGT_GPLAYER, MSGL_DBG2, "%f %i %i %i %i msg %i\n", mywidget->value,
                 mywidget->wx, mywidget->wy, mywidget->wwidth, mywidget->wwidth,
                 mywidget->msg);
         if (mywidget->bitmap[0] == NULL || mywidget->width == 0 || mywidget->height == 0)
@@ -420,6 +445,14 @@
             mywidget->width = mywidget->wwidth;
             mywidget->height = mywidget->wheight;
         }
+        if (*desc == 'r')
+        {
+            mywidget->maxwh = FFMAX(mywidget->width, mywidget->height);
+
+            // clickedinsidewidget() checks with width/height, so set it
+            mywidget->width = mywidget->wwidth;
+            mywidget->height = mywidget->wheight;
+        }
     }
     else if(!strncmp(desc, "pimage", 6))
     {
--- a/gui/win32/skinload.h	Fri Apr 11 09:34:31 2014 +0000
+++ b/gui/win32/skinload.h	Sat Apr 12 23:29:29 2014 +0000
@@ -61,12 +61,15 @@
     int width, height;              /* width and height of the button */
     int wwidth, wheight;            /* width and height of the widget */
     // ---
+    int maxwh;
     // ---
     int msg, msg2;
     int pressed, tmp;
     int key, key2;
     int phases;
     float value;
+    double zeropoint;
+    double arclength;
     image *bitmap[2];               /* Associated image(s) in imagepool */
     // ---
     font_t *font;
@@ -121,6 +124,7 @@
 #define tyMenu          6
 #define tySlabel        7
 #define tyDlabel        8
+#define tyRpotmeter     9
 
 /* --- Window types --- */
 
--- a/gui/win32/widgetrender.c	Fri Apr 11 09:34:31 2014 +0000
+++ b/gui/win32/widgetrender.c	Sat Apr 12 23:29:29 2014 +0000
@@ -24,6 +24,7 @@
 #include <stdio.h>
 #include <string.h>
 #include <ctype.h>
+#include <math.h>
 #include <windows.h>
 
 #include "gui/util/bitmap.h"
@@ -34,6 +35,7 @@
 #include "access_mpcontext.h"
 #include "help_mp.h"
 #include "libavutil/avstring.h"
+#include "libavutil/common.h"
 #include "stream/stream.h"
 
 #define MAX_LABELSIZE 250
@@ -409,11 +411,13 @@
     if(!dest) return;
     if((item->type == tyButton) || (item->type == tyHpotmeter) || (item->type == tyVpotmeter) || (item->type == tyPimage))
         img = item->bitmap[0];
+    if(item->type == tyRpotmeter)
+        img = item->bitmap[1];
 
     if(!img) return;
 
     y = item->y;
-    if(item->type == tyPimage || /* legacy (potmeter) */ (item->type == tyHpotmeter && item->width == item->wwidth))
+    if(item->type == tyPimage || /* legacy (potmeter) */ (item->type == tyHpotmeter && item->width == item->wwidth) || item->type == tyRpotmeter)
     {
         height = img->height / item->phases;
         y =  height * (int)(item->value * item->phases / 100);
@@ -430,7 +434,7 @@
     if(item->type == tyButton)
         render(skin->desktopbpp, dest, find_background(skin,item), item->x, item->y, item->x, item->y, img->width, height, 1);
 
-    if((item->type == tyHpotmeter) || (item->type == tyVpotmeter) || (item->type == tyPimage))
+    if((item->type == tyHpotmeter) || (item->type == tyVpotmeter) || (item->type == tyRpotmeter) || (item->type == tyPimage))
     {
         if(item->type == tyVpotmeter)
         {
@@ -438,6 +442,13 @@
             render(skin->desktopbpp, dest, find_background(skin, item), item->wx, item->wy, item->wx, item->wy, item->width, item->wheight, 1);
             item->y = (100 - item->value) * (item->wheight-item->height) / 100 + item->wy;
         }
+        else if(item->type == tyRpotmeter)
+        {
+            /* repaint the area behind the rpotmeter */
+            render(skin->desktopbpp, dest, find_background(skin, item), item->wx, item->wy, item->wx, item->wy, item->wwidth, item->wheight, 1);
+            item->x = item->wx;
+            item->y = item->wy;
+        }
         else
         {
             /* repaint the area behind the slider */
@@ -446,4 +457,31 @@
         }
     }
     render(skin->desktopbpp, dest, img, item->x, item->y, 0, y, img->width, height, 1);
+
+    /* rpotmeter button */
+    if(item->type == tyRpotmeter && item->bitmap[0] != item->bitmap[1])
+    {
+        img = item->bitmap[0];
+
+        if(img)
+        {
+            double radius, radian;
+            int ix, iy;
+
+            // keep the button inside the potmeter outline
+            radius = (FFMIN(item->wwidth, item->wheight) - item->maxwh) / 2.0;
+
+            radian = item->value / 100.0 * item->arclength + item->zeropoint;
+
+            // coordinates plus a correction for a non-square item
+            // (remember: both axes are mirrored, we have a clockwise radian)
+            ix = item->wx + radius * (1 + cos(radian)) + FFMAX(0, (item->wwidth - item->wheight) / 2.0) + 0.5;
+            iy = item->wy + radius * (1 + sin(radian)) + FFMAX(0, (item->wheight - item->wwidth) / 2.0) + 0.5;
+
+            height = img->height / 3;
+            y = state * height;
+
+            render(skin->desktopbpp, dest, img, ix, iy, 0, y, img->width, height, 1);
+        }
+    }
 }