Jump to content

GDI - Interactive Fader and Knob controls


MattyD
 Share

Recommended Posts

I like their design but the knobs aren't working as expected. If you click a knob and move the mouse to the left side of the control, the indicator goes the other way. Or you can replicate the behavior by just clicking the indicator of a know and the indicator will immediately shift position to the other side of the control. Is this how it's intended to work?

When the words fail... music speaks.

Link to comment
Share on other sites

Hey, thanks Andreik,

So the knobs are controlled by the Y Axis of the mouse, which by memory should be the norm? but happy to be corrected!

I agree they can be a little smarter - at present the knobs will snap to a value on mouse down.  I was going to get it to only react only once you start dragging, but meh, good enough for this time of night!

Link to comment
Share on other sites

7 hours ago, MattyD said:

Hey, thanks Andreik,

So the knobs are controlled by the Y Axis of the mouse, which by memory should be the norm? but happy to be corrected!

I agree they can be a little smarter - at present the knobs will snap to a value on mouse down.  I was going to get it to only react only once you start dragging, but meh, good enough for this time of night!

It's fine, just wanted to be sure it's the intended behavior.

When the words fail... music speaks.

Link to comment
Share on other sites

10 hours ago, argumentum said:

Could on mouse over be controlled by the wheel mouse ? < request :)

Yeah, that sounds like a good idea!

The mouse wheel should be easy enough to implement - the hit-testing could be more difficult... The static control won't notify when a mouse moves over it, so I think I'd need to store the locations of every control. Then we'd check if anything matches when the wheel moves.

I'm not really sold on this approach though - just feels horribly inefficient.  If anyone has had a crack at hit-testing controls in the past I'm all ears!

But as a semi-solution we can totally do a: click on the control to select, then wheel to change though.

Link to comment
Share on other sites

2 hours ago, argumentum said:

also, attend to HiDpi Scaling

Ah, yep - looks like thats a problem. Thanks!

For those playing at home, the control should more or less only react while the mouse is within its bounds...
So if you drag a fader, the thumb should sit nicely under the mouse pointer.

Something is getting thrown when the scaling is not set at %100, so this obviously needs fixing.

Edited by MattyD
Link to comment
Share on other sites

So a new file to address the issues previously mentioned -

  1. The scaling issue is hopefully now fixed!
  2. Knob controls will no longer snap to an absolute value on mouse down. They'll only start moving when you begin dragging on the Y-Axis
  3. The mouse wheel can now manupulate controls. - Click to select the control, then wheel to change the value.
    • Clicking away from a control should de-select it.
Link to comment
Share on other sites

Thank you for sharing with us.

That's very nice work, congratulations.

It's nice to have more graphical elements, as an option to enrich the GUI.

The responsiveness of the knobs has improved a lot compared to the previous one . :thumbsup:

suggestions:

Spoiler

suggestion for more realistic movement:

Switch $iCurCtrlType
    Case $GDICTL_TYPE_KNOB
        If $aiMousePos[$X] > $__g_aiSelCtrlPos[$X] + ($__g_aiSelCtrlPos[$WIDTH] / 2) Then
            $iNewValue = $__g_bSelCtrlInitVal + ($aiTgtImg[$Y] - $__g_aiSelCtrlOffset[$Y])
        Else
            $iNewValue = $__g_bSelCtrlInitVal - ($aiTgtImg[$Y] - $__g_aiSelCtrlOffset[$Y])
        EndIf

    Case $GDICTL_TYPE_HFADER
        $iNewValue = $aiTgtImg[$X]

    Case $GDICTL_TYPE_VFADER
        $iNewValue = 127 - $aiTgtImg[$Y]

EndSwitch

 

and a function for the outside world to update the element:

Func _GUICtrlGDI_SetData($ControlID, $iValue)
    Local $aBmps, $iBmpIdx, $hWnd

    $iValue = Int($iValue)
    If $iValue > 127 Then $iValue = 127
    If $iValue < 0 Then $iValue = 0

    For $i = 0 To UBound($__g_avCtrls) - 1
        If $ControlID = $__g_avCtrls[$i][$GDICTL_DUMMY] Then
            $hWnd = $__g_avCtrls[$i][$GDICTL_HWND]
            ExitLoop
        EndIf
    Next
    If $i = UBound($__g_avCtrls) Then Return SetError(1, 0, False)

    If $__g_avCtrls[$i][$GDICTL_VALUE] = $iValue Then Return
    $__g_avCtrls[$i][$GDICTL_VALUE] = $iValue
    $iBmpIdx = $__g_avCtrls[$i][$GDICTL_BMPIDX]
    $aBmps = $__g_avCtrlBmps[$iBmpIdx][$GDICTL_BMPS]

    _SendMessage($hWnd, $STM_SETIMAGE, $IMAGE_BITMAP, $aBmps[$iValue])
    GUICtrlSendToDummy($__g_avCtrls[$i][$GDICTL_DUMMY], $iValue)
EndFunc

 

 

Edited by ioa747

I know that I know nothing

Link to comment
Share on other sites

Thanks heaps ioa - really appreaciate it :)

Yeah, certainly we need a way to set a value from outside the GUI.
I was going to try and see if I can get GuiCtrlSetData() to trigger something similar to what you've written, but I still need to do the groundwork to see if that's viable.

As for the other suggestion, I appreaciate where you're coming from. Pushing the left side of the control up works the same pulling the right side down... I think we do need to choose a direction though and stick with it - just from a usability standpoint.

If I cross the centre line while pulling upwards I probably don't want the control to reverse.. Or if I'm using a touch screen my finger might cover the entire control - each time i grab something it could seemingly behave differently (depending on if I'm a little bit left or right of center) .

 

Edited by MattyD
Link to comment
Share on other sites

7 hours ago, MattyD said:

If I cross the centre line while pulling upwards I probably don't want the control to reverse..

this was the first approach ... after that I went a little further

Spoiler
Func WH_MOUSE_LL($iNotifCode, $wParam, $lParam)

    Local $tLLHook, $aiMousePos[2], $iWheelDelta
    Local $bReleaseCtrl, $iNewValue, $aiTgtImg[2]

    If $iNotifCode < 0 Then Return _WinAPI_CallNextHookEx($__g_pMouseHook, $iNotifCode, $wParam, $lParam)
    If $__g_iSelCtrl < 0 Then Return _WinAPI_CallNextHookEx($__g_pMouseHook, $iNotifCode, $wParam, $lParam)

    Local $iCurCtrlHwnd, $iCurCtrlType, $iCurCtrlValue
    Local Static $iMouseX = MouseGetPos(0)

    $iCurCtrlHwnd = $__g_avCtrls[$__g_iSelCtrl][$GDICTL_HWND]
    $iCurCtrlType = $__g_avCtrls[$__g_iSelCtrl][$GDICTL_TYPE]
    $iCurCtrlValue = $__g_avCtrls[$__g_iSelCtrl][$GDICTL_VALUE]

    Switch $wParam

        Case $WM_LBUTTONDOWN
            $iMouseX = MouseGetPos(0)

            $__g_iSelCtrl = -1
            Return _WinAPI_CallNextHookEx($__g_pMouseHook, $iNotifCode, $wParam, $lParam)

        Case $WM_LBUTTONUP
            $bReleaseCtrl = True
            ContinueCase

        Case $WM_MOUSEMOVE
            If Not $__g_bSelCtrlHeld Then Return _WinAPI_CallNextHookEx($__g_pMouseHook, $iNotifCode, $wParam, $lParam)
            $tLLHook = DllStructCreate($tagMSLLHOOKSTRUCT, $lParam)
            $aiMousePos[$X] = DllStructGetData($tLLHook, "pt", 1)
            ConsoleWrite("$aiMousePos[$X]=" & $aiMousePos[$X] & @CRLF)
            $aiMousePos[$Y] = DllStructGetData($tLLHook, "pt", 2)
            $aiTgtImg[$X] = Round(127 * ($aiMousePos[$X] - $__g_aiSelCtrlPos[$X]) / $__g_aiSelCtrlPos[$WIDTH])
            $aiTgtImg[$Y] = Round(127 * ($aiMousePos[$Y] - $__g_aiSelCtrlPos[$Y]) / $__g_aiSelCtrlPos[$HEIGHT])

            Switch $iCurCtrlType
                Case $GDICTL_TYPE_KNOB
                    If $iMouseX > $__g_aiSelCtrlPos[$X] + ($__g_aiSelCtrlPos[$WIDTH] / 2) Then
                        $iNewValue = $__g_bSelCtrlInitVal + ($aiTgtImg[$Y] - $__g_aiSelCtrlOffset[$Y])
                    Else
                        $iNewValue = $__g_bSelCtrlInitVal - ($aiTgtImg[$Y] - $__g_aiSelCtrlOffset[$Y])
                    EndIf

                Case $GDICTL_TYPE_HFADER
                    $iNewValue = $aiTgtImg[$X]

                Case $GDICTL_TYPE_VFADER
                    $iNewValue = 127 - $aiTgtImg[$Y]

            EndSwitch

        Case $WM_MOUSEWHEEL
            $tLLHook = DllStructCreate($tagMSLLHOOKSTRUCT, $lParam)
            $iWheelDelta = BitShift(DllStructGetData($tLLHook, "mouseData"), 16) / 120
            $iNewValue = $iCurCtrlValue + $iWheelDelta

        Case Else
            Return _WinAPI_CallNextHookEx($__g_pMouseHook, $iNotifCode, $wParam, $lParam)

    EndSwitch

    __GUICtrlGDI_SetData($iCurCtrlHwnd, $iNewValue)
    If $bReleaseCtrl Then $__g_bSelCtrlHeld = False

    Return _WinAPI_CallNextHookEx($__g_pMouseHook, $iNotifCode, $wParam, $lParam)
EndFunc   ;==>WH_MOUSE_LL

 


more is to share the idea  :)

 

Edited by ioa747

I know that I know nothing

Link to comment
Share on other sites

Ok so take 3...

there is little to no QA happening here so heads up!!

  1. Reversed the dragging diretion for the knob controls (so up is more, down is less).
     
  2. GuiCtrlSetData() now should work - Its not the most elegant solution, but hey, this whole project is kindof hacky anyway!
     
  3. added a _GUICtrlGDI_Destroy($iCtrlID) funtion for those crazy people who like to clean up after themselves.
     
  4. added _GUICtrlGDI_FwdMsg($iType, $sFunc) - Basically we're already handling $WM_COMMAND when we include the file. So if this is needed elsewhere, call _GUICtrlGDI_FwdMsg($WM_COMMAND, "myFunc").

Edit - I'm second guessing myself now...  Does anyone have any strong opinions as to which way the mouse wheel should work on the faders?

Edited by MattyD
Link to comment
Share on other sites

Hi MattyD,

Love the approach, you've traded memory for low CPU making it smooth.

One note! I believe you may have a memory leak, you need to release the old bitmap object when you replace it with sendmessage().

Something like:

Local $vRet = _SendMessage($hWnd, $STM_SETIMAGE, $IMAGE_BITMAP, $aBmps[$iValue])
If $vRet Then _WinAPI_DeleteObject($vRet)

Many thanks for sharing 👍

Link to comment
Share on other sites

1 hour ago, Jardz said:

Hi MattyD,

Love the approach, you've traded memory for low CPU making it smooth.

One note! I believe you may have a memory leak, you need to release the old bitmap object when you replace it with sendmessage().

Something like:

Local $vRet = _SendMessage($hWnd, $STM_SETIMAGE, $IMAGE_BITMAP, $aBmps[$iValue])
If $vRet Then _WinAPI_DeleteObject($vRet)

Many thanks for sharing 👍

My pleasure - and thankyou kindly, :) you are indeed correct.

Link to comment
Share on other sites

Thanks heaps ptrex, yeah why not!

As you may have guessed, this has actually sprung from working with that midi project -
Basically I had this vision mixer, but the audio controls were all tucked behind some menus on the device. So I "fixed" it by writing my own audio interface :)

This was going to be part of that project, but then I sortof got sidetracked...

In all honesty dB meters are probably not a bad idea too 👍 -  We probably couldn't use GuiCtrlSetData() on them the way I've implemented it though.  I've got an adlib function to check the dummy control's values, then update the associated static control if need be.  I would say this method would be too slow for the meters to show anything meaningful.  If you set the static control directly I'd imagine it could be workable though.

Link to comment
Share on other sites

6 hours ago, argumentum said:

@MattyD, it would be good to have your 2 files in a ZIP file with a version or date in the name :) 

Ask and thou shall recieve!

Just be aware the "versioning" should be treated very loosely.
I'm still treating this all as early development; so its just the same file that has been updated and re-uploaded on my side...

That being said, changes since last time:

  • We've now plugged that memory leak previously mentioned.
  • We're now disposing sets of bitmaps if we've destroyed all controls that reference them.
  • Reduced the adlib interval for updating controls. (GUICtrlSetData() should be more responsinve)

 

Edited by MattyD
Link to comment
Share on other sites

hmm, new issue... when you move a control fast there may be a problem,

Say we do a quick sweep of a fader from 0 to 127: We check mousemove messages to see if theres a new value send, and if need be fire a SendToDummy. This doesn't nessecarily trigger on every vlaue between 0 & 127, but it can be pretty granular.

So this becomes an issue with GuiGetMessage() in a loop, there's that built-in delay to prevent CPU ramping.  In the space of one loop, you could've had, say, 10 mousemoves fire. If we're only reading values with GuiCtrlRead() when we process the GGM message, theres a good chance we're too late for the party.

In practice, you end up reading a few values along the way during a sweep, but then a bunch of "127"s as we process the backlog of messages.

I'm starting to think these dummy controls may be more trouble than they're worth!

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...