wxWidgets / Phoenix

wxPython's Project Phoenix. A new implementation of wxPython, better, stronger, faster than he was before.
http://wxpython.org/
2.23k stars 510 forks source link

AGW Slider Widget #1429

Closed kdschlosser closed 4 years ago

kdschlosser commented 4 years ago

I wrote a custom slider widget I thought might be of interest. It handles floats as well as integers, a step or increment can also be supplied. If it is something you might want to add you are more then welcome to. It is currently modeled after the slider in Windows, I can add other OS's into that as well.

I have attached the script, just run the script and the test program will run displaying an assortment of varying sliders.

If someone finds a bug please let me know so I can correct it.

AGWSlider.zip

RobinD42 commented 4 years ago

I've finally taken a look at this, it is a very nice slider control. I think it would make a good addition. Please make a PR adding it to wx/lib/agw/ and move the sample code to a new module in demo/agw/. A couple observations and "nice to have" changes if you have time:

  1. Once it is moved into AGW the name of the module can be just slider.py with the class named Slider.

  2. Although there are good docstrings in the main class, it would be nice if there were more docstrings or comments in the other code in the module. Enough that, for things that are more than a simple getter and setter for example, that it makes it clear to a reader what is being done and why.

  3. I noticed that there is an indicator when the control has the focus, and that key events can modify the slider's position, but it looks like tab-traversal is not working

kdschlosser commented 4 years ago

I never even thought about the tab traversal..

I wanted you to have a look see before I cleaned up the code and finished the docstrings. I need to know how the docstring formatting should get done. You do not use sphinx syntax,, so I need to know what the formatting is so if you wanted to include documentation for the control you would be able to.

I would like to make the slider appearance to be native based on OS. I am running Windows so that is why the slider is modeled the way that it is. I do not know what the appearance is for other OS's I will do some research on that and see what I can come up with. I know that one of wxWidgets and wxPythons main goals is to provide a UI that looks native to the OS. I would also like to have this control do the same.

I do not like how I am currently handling the thumb. I load the thumb from images included in the file. I would much rather draw them.. This is beyond my knowledge of wx. right now if the user has a custom color arrangement in the OS thumb is not going to be the same colors as specified in the OS If the thumb was drawn using wx.DC this could be accomplished. Where I hit a road block specifically was when the thumb was activated or "pressed". didn't know how to go about adding that gradient. If you have the time to help me with that portion either through guidance or coding that portion of it it would be a huge help.

Thanks for taking the time to look over it.

(I wrote this a few years back when I was in my programming infancy. There is quite a bit of code cleanup needing to be done.)

RobinD42 commented 4 years ago

I need to know how the docstring formatting

See https://docs.wxpython.org/DocstringsGuidelines.html

I know that one of wxWidgets and wxPythons main goals is to provide a UI that looks native to the OS.

In general that is true, but for custom widgets, especially those in wx.lib.agw it's okay to have a custom look. One problem with trying to match native LnF in custom controls is that it tends to change when new versions of the OS are released. The wx.RendererNative class can help out with it, as it can be used to draw certain UI elements using the current theme's APIs, but it doesn't (yet?) support drawing the slider thumb.

So, in a nutshell, I wouldn't worry too much about native LnF for this widget. Personally I think it looks good as-is on OSX too. If you do want to support multiple looks then a good approach that has been used elsewhere in AGW (like flatnotebook.py) is to delegate drawing to a renderer class, and then provide multiple implementations of the renderer base class which draw the widget in each style.

if the user has a custom color arrangement in the OS thumb

Take a look at wx.SystemSettings.GetColour. If the system's slider thumb is using some of the standard colors reported there, then you can use them to match the look. Even if they're not there you could probably use the stock button colors and be close enough.

Where I hit a road block specifically was when the thumb was activated or "pressed". didn't know how to go about adding that gradient.

You may be able to get some ideas from wx.lib.buttons or some of the other custom widgets.

kdschlosser commented 4 years ago

ok cool.

I also did up a volume knob. I do know that one already exists in wx, it lacks "curb appeal" so to speak.

It is a really nice widget. I know it is not 100% compete in terms of the events or allowing for color customization but adding those things is fairly simple to do. If you are interested in that one as well and I will clean them both up at the same time.

https://github.com/kdschlosser/wxVolumeKnob

kdschlosser commented 4 years ago

I may have found an issue that I would prefer to not have to work around.

The documentation in wxPython is pretty good about parameter types and having the correct types enforced.

the wx.ScrollEvent class which is what gets used for the events from a slider type of control has the SetPosition method. this is what is used to set the value of the slider when an event occurs. the issue is the data type specified in the docs state int.. I am pretty sure if I pass a float an exception will probably ensue.

there are 2 ways around this. and I think both are kind of a hack way to go about it.

  1. I can subclass int to "trick" the type checking but it is going to require the user to do the following.

    value = event.GetPosition()
    value = float(value)

    this will then return the correct value.. It's ugly but it will work.

  2. subclass wx.ScrollEvent and override the SetPosition and GetPosition.

I personally do not like either and would rather see float added to the SetPosition and GetPosition methods. I also do not know if the wx.ScrollEvent is apart of wxWidgets or if it is coded in Python. This is also not my decision to make!! LOL.

I will do whatever you tell me to in order to workaround around this limitation, I do feel there needs to be a way the user is able to access the value from the control in an event callback other then using event.GetEventObject().GetValue()

Metallicow commented 4 years ago

@kdschlosser nice slider demo. Would be nice if someone wrote a rangeslider that was phoenix compatible also. @ruckyboy started on something similar. Here is the fork if interested in looking at it. https://github.com/Metallicow/wx_aic

kdschlosser commented 4 years ago

@Metallicow

That is a nice couple of widgets. but they are done in almost the same manner as what I have done. and use pre made images.. the problem with that is changing the color to support the colors the user has the GUI set to.. The only way to go about doing that is to draw the widget using wx. I did just think about how to go about drawing the thumb. it is small enough i could do it pixel by pixel it wouldn't be all that hard to do including the gradient. for when the thumb is active.

kdschlosser commented 4 years ago

@Metallicow

I did some work on this repository https://github.com/kdschlosser/wxVolumeKnob It is mostly complete now. I still have to do a little buff and shine on the code and add the documentation then I will do a PR and see if it will get added.

you can test it by running the __init__

The thing I like the most about that control is it uses no images. it is all 100% wxPython drawn. That means that it is going to match a users OS color scheme. I have learned how to use the gradients well enough where I think I will be able to tackle the thumb for the slider.

Metallicow commented 4 years ago

@kdschlosser actually I was referring to the rangeslider with 2 grabbers on it that stop on both sides of a slider. usually they are a custom widget. The one that looks like a standard slider but like this. wxWidgets/wxPython doesnt have one yet. 0 [-------o-range-o--------] 100 rangeslider The rotarydial is the one similar to a volume potentiometer, or often seen in music software as just an amp knob(of course it should look badass like a cabinet stack, hence people usually use images or scalable graphics for them. My amp actually says it goes to 11 lol joke) rotarydial

As far as the wxVolumeCtrl knob you wrote, I checked it out. Looks nice by the way. wxVolumeCtrl worked on Win7 wxPy4.1 Py2.7 I did get a few deprecations in the terminal tho that need fixing

wxVolumeCtrl.py:1064: wxPyDeprecationWarning: Call to deprecated item EmptyBitmapRGBA. Use :meth:`wx.Bitmap.FromRGBA` instead. height

wxVolumeCtrl.py:1244: wxPyDeprecationWarning: Call to deprecated item EmptyBitmap. Use :class:`wx.Bitmap` instead dc.SelectObject(wx.EmptyBitmap(1, 1))

Also regarding Py3... With Py2 going byebye to retirement land in 3 weeks.

print   # You should know better than to leave a blank statement. erase this.

plus there are 2 print statements that need ( ) around them in the main sample at the bottom.

Check out Andreas Widgets for Sphinx documentation stuff. There is a section in the docs that will help a bit also.

...um the license needs to be changed to wxWidgets/wxPython

...and I can give you an A+ for no trailing whitespace lol Hollar again when you get closer to completion and I'll try to find time to review again. Cheers.

ruckyboy commented 4 years ago

@Metallicow Ahh, nice to see screen-grabs from a very early version of my aic widgets, :)

I'll get back to working on that library again in the new year, there are a lot of widgets in there at the moment, I need to cull and normalise.

@kdschlosser I like that you are trying a no-bmp version of the knob, the result looks pretty slick. Well done. There are performance issues though, resizing slows things down and the thumb-wheel looses it's grab if you move the mouse too quickly. (Win10, Python 3.8, wxPy 4.0.7) I'm interested in seeing how you go with this project. Cheers, P

kdschlosser commented 4 years ago

@Metallicow

I did say that I needed to give the code a buff shine. It's not done yet. I do also know of the EOL for Python 2. I am the Administrator of a project called EventGhost. It is running Python 2.7 and also wxPython 3.x. It is going to be a massive code overhaul to bump those versions. and not so much for the main application. It's the 400+ extensions that are available that is what is going to take an enormity of time. There is a large number of changes between wxPython 3.x and 4.x, I did write a script that monkey patches a very large portion of wxPython so it will run using the 3.x API there are things I am not going to be able to patch to make work using the old API. and that is the reason why you are seeing the depreciation warnings. I have not written in a version check of wxPython to make the appropriate adjustments needed to get the API right. This could also be one of the things cause a possible performance issue @ruckyboy mentioned. I wrote the knob using Python 2.7 and wxPython 3.x. I will be testing and making changes needed for Python 3.5+ and wxPython 4.x

@ruckyboy

The Thumb Wheel loses it' grab on purpose. This is to prevent jumping of the control. I believe that wxPython runs in a single thread.. the main thread. so when an event callback gets called for a mouse movement there is some processing that gets done to determine the direction of travel. Now from there I could directly update the UI and this is going to add to the latency of receiving the next mouse movement event, so you end up with a jittery control. So I make use of wx.CallAfter to insert the update if the UI to be run by the main thread whenever it has the time. this way we can get the next mouse movement event. Even tho I use that mechanism the UI update still causes a small lag. and with fast mouse movements the control would jump/jitter. So the way I went about dealing with it is I allow I think 3 or 4 times the thumb diameter of buffer on either side of the thumb. so if the mouse moves outside of that buffer then the mouse is moving to fast and the control is going to jump So I have it release the capture on the mouse instead.

I was specifically thinking of the use case where the knob would be used as a volume knob... well that is what I designed it for. and if for some reason someone's hand gets bumped or whatever the case is and the mouse moves rapidly and the volume jumps from 30 to 90 and blows someone's speakers up. I thought that they might be a wee bit upset if that happened. So best to plan ahead and not allow that kind of a scenario to take place, In all honesty I think I have a really good balance of the mouse speed to knob movement. it's not slow and the knob moves nice and smooth.

Now. I can compile the control into a python dll (pyd) and this would get the speed boost of being run as c code and the mouse issue would probably vanish. I think that most of wxPython is compiled in this manner and uses stub files for the most part to allow an IDE and Python a mechanism that appears to "peek" inside of the dll.

I used GCDC to handle all of the drawing so the quality would be better, and that comes at a cost. It has a lot to process when doing resizes. especially when you get HUGE with the knob.. But in all reality, how often would the thing be getting resized if at all?? the other thing is how large would you actually have it being displayed? quite a bit smaller then the demo I would imagine.

If a computer is not up to snuff or the gfx adapter is weak there is the ability to turn off the tick marks, or reduce the number of tick marks being shown you can also disable the glow around the knob, disable the glow around the thumb, not use the multi colored ranges, remove the shadow. remove the depression on the knob. each one of those things when removed/reduced is going to increase the speed in which the UI can redraw the knob. a resize of the knob causes the hole thing to have to get redrawn there is no way around this. If you look at the code you ill see the Handler class. it's whole purpose is to handle the buffer of any data that can be buffered. and to release specific buffers so an update can be made if some variable changes. Most of the properties in that Handler class have some pretty decent calculations that need to be made. hence the reason why I store what I can.

The single largest thing that causes the mouse lag is the detection of the position of the mouse relative to the knob to calculate a value any time you deal with something that is round and you need to keep the movement of that object in sync with where the mouse is is an expensive process to do. There are ways I can code around the issue using threading, I thought it best to not to spawn threads and squeeze as much performance as I could using the main thread.

The purpose to the demo was only to collect some user feedback on the design aspect of it, and to see if there are any ideas for things that should be added to it.

kdschlosser commented 4 years ago

@Metallicow

A far as a range Slider is concerned. It would not be to difficult to add a second thumb to the control and draw a colored bar showing the user adjustable range. I have never seen what a use case would be for a control like that so I never thought of adding that ability.

I also just thought of a feature to add to the knob control. numbers over the tick marks like what is shown in @ruckyboy 's control.

@ruckyboy I do like not having to use pre-made images when creating a control it limits integrating a control to look like the rest of the UI. No that being said. masks can be created that would aide in adding some of this functionality. I do not kno ho to use the masks and regions in xPython. I have not spent the time to learn as of yet (haven't had the need to)

kdschlosser commented 4 years ago

I have been dinking around with the gradients some more and trying to get a good handle on hat I can do with them..

This is a joystick control modeled after the PlayStation joystick. I saw a similar image when searching around and thought I would have a crack at rendering it in wxPython.

I think it came out pretty decent. would be nice to have this kind of a control available that would generate "analog" events. so you would have the angle of movement in degrees 0-359 0 being north and moving clockwise, then then you have the speed which would be how far the joy is from center. probably a range of 0-100

image

ruckyboy commented 4 years ago

Nice! I've not thought about a joystick control, should be pretty straight forward, just implement mouse tracking relative to centre. I'd treat it like a 4 way slider, then do the maths... or leave the 'user' to decide what they want to do with the raw outs?

Metallicow commented 4 years ago

Umm the demo does have a joystick ctrl thingie if you want to check it out, but I usually don't think it applies to most default stuff unless you remap a "gamepad" and write some extra code. Most folks have a Xbox360 controller or a Logitech generic gamepad. Not sure if a Playstation pad workes with pc as a configurable(maybe the new ones...) without extra stuff. I do recall that Logitech actually sells a actual Joystick but I don't got one, so good luck testing anything with it without writing docs or something. I do have their old generic gamepad and a steam controller(which is moddable and works as a mouse keyboard also, ... could fly a drone with the touch pads) The render looks similar enough I think. Nice use of the gradients!

kdschlosser commented 4 years ago

It only looks like the PlayStation analog joystick not meant to work like one exactly. Tho to make it work like one that would be pretty easy as well. +32000 to -32000 range for x and y axis this gives both direction and speed. That's how the analog sticks work for the most part. the range will vary depending on the manufacturer

could use for controlling a PTZ security camera or something like that I didn't know that wxPython had a joystick. I can't recall ever coming across one in the demo.

kdschlosser commented 4 years ago

another use case..

Fade/Balance control all in a single knob!!!

kdschlosser commented 4 years ago

@ruckyboy

a user could always do their own mouse tracking if they wanted to. bind the wx.EVT_MOVE

I was thinking of this

      -100

-100    0    +100

      +100

so a position would be x, y 50, -50 would be 45 degree upper right half way to the corner.

could also provide the degree and a 0-100 for distance. this would be nice if using it to move a security camera.

have the option for a spring type return to center with an adjustable return rate or have the stick stay in position.

right click would depress the joystick to provide a "button"