May 10, 2024, 12:06:33 PM

News:

IonicWind Snippit Manager 2.xx Released!  Install it on a memory stick and take it with you!  With or without IWBasic!


Mouseover and change cursor

Started by Bruce Peaslee, November 29, 2007, 02:45:21 PM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Bruce Peaslee

In Aurora I was able to get the cursor to change to a hand when it went over a static control, but I can't get anywhere with EBasic. Does anyone have any experience in this area?

Thanks.
Bruce Peaslee
"Born too loose."
iTired (There's a nap for that.)
Well, I headed for Las Vegas
Only made it out to Needles

maurice1

This code by Jos de Jong has an example of a mouseover effect ...

/*
Mass spring model
the model is true to life, including mass, stiffness and damping.
The model is created by using Lagrange equations in matlab.

Requires DirectX 7.0 or higher

author: Jos de Jong, may 2007
IBasic pro code
*/

AUTODEFINE "Off"

'declarations
DECLARE IMPORT, LoadCursor ALIAS LoadCursorA(hInstance AS INT,lpCursorName AS POINTER),INT
CONST IDC_HAND = 32649
UINT hndCursorHand
hndCursorHand = LoadCursor(NULL, IDC_HAND)


'define some colors
CONST WHITE = 0xFFFFFF
CONST GRAY  = 0x303030
CONST BLACK = 0x000000
CONST RED   = 0x0000FF
CONST BLUE  = 0xFF0000

'define variables for the model
DOUBLE pi
DOUBLE g, m, k, b_air, b_spring, L0, r_mass, k_width, k_windings
DOUBLE A[4,4], B[4,1], C[2,4], D[2,1]
DOUBLE X[4,1], Xdot[4,1], X_eq[4,1], X_init[4,1]
DOUBLE dt

model_init()


'create a window
WINDOW win
OPENWINDOW win, 0, 0, 640, 480, @NOAUTODRAW | @MINBOX, 0, "Mass-spring model (grab the mass with your mouse)", &handlerMain
CENTERWINDOW win

'attach a directx window
INT L, T, W, H
GETCLIENTSIZE win, L, T, W, H
IF(ATTACHSCREEN(win, W, H, TRUE) < 0)
MESSAGEBOX win,"Couldn't create DirectX window","Error"
CLOSEWINDOW win
END
ENDIF

'load background image
POINTER background
background = LOADSPRITE(GETSTARTPATH + "background.gif")

'define variables for the conversion from meters to screen position
INT x0, y0
DOUBLE x_scale, y_scale
INT grabbed :'is true if the user holds the mass, else false
DOUBLE global_mousex, global_mousey

screen_init()

'start a timer to refresh the dx window
STARTTIMER(win, dt/1000)

'run the program until the window is closed
WAITUNTIL win=0
END


SUB handlerMain
SELECT @MESSAGE
CASE @IDCLOSEWINDOW
CLOSESCREEN
CLOSEWINDOW win

CASE @IDLBUTTONDN
'left mouse button down.
'check if the user grabbed the mass
grabbed = mouseOnMass()

CASE @IDMOUSEMOVE
global_mousex = @MOUSEX
global_mousey = @MOUSEY

IF grabbed
'adjust the position of the mass to the mouse position if it is grabbed
set_position_mass(get_x_mouse(), get_y_mouse())
ENDIF

CASE @IDLBUTTONUP
'left mouse button up
grabbed = False

CASE @IDTIMER
'evaluate the model and paint the mass and spring again
model_eval()

'change the mouse cursor when the mouse is upon the mass
IF mouseOnMass()
SETCURSOR(win, @CSCUSTOM, hndCursorHand)
ELSE
SETCURSOR(win, @CSARROW)
ENDIF

ENDSELECT
RETURN
ENDSUB

/**
this function checks if the
@return True if the mouse is upon the mass, else returns false
*/
SUB mouseOnMass(), INT
DOUBLE distance

distance = SQRT((get_x_mouse() - get_x_mass())^2 + (get_y_mouse() - get_y_mass())^2)

RETURN (distance < r_mass)
ENDSUB

/**
this function returns the x position of the mouse in meters
@return x_mouse in meters
*/
SUB get_x_mouse(), DOUBLE
RETURN 1.0 * (global_mousex - X0) / x_scale
ENDSUB


/**
this function returns the y position of the mouse in meters
@return y_mouse in meters
*/
SUB get_y_mouse(), DOUBLE
RETURN 1.0 * (global_mousey - Y0) / Y_scale
ENDSUB


/**
this function returns the x position of the mass in meters
@return x_mass
*/
SUB get_x_mass(), DOUBLE
DOUBLE theta, r
DOUBLE x_mass

theta = X[0,0] + X_eq[0,0]
r     = X[1,0] + X_eq[1,0]

x_mass =  r * sin(theta)

RETURN x_mass
ENDSUB


/**
this function returns the y position of the mass in meters
@return x_mass
*/
SUB get_y_mass(), DOUBLE
DOUBLE theta, r
DOUBLE y_mass

theta = X[0,0] + X_eq[0,0]
r     = X[1,0] + X_eq[1,0]

y_mass = -r * cos(theta)

RETURN y_mass
ENDSUB


/**
this function sets the position of the mass to the given coordinates (in meters)
@param x_mass desired x position of the mass in meters
@param y_mass desired y position of the mass in meters
*/
SUB set_position_mass(x_mass:DOUBLE, y_mass:DOUBLE)
DOUBLE theta, r

'calculate the current angle and radius
theta = atan2(x_mass, -y_mass)
r = SQRT(x_mass*x_mass + y_mass*y_mass)

'adjust the position (first two states), and set the speed to zero (third and fourth state)
X[0,0] = theta - X_eq[0,0]
X[1,0] = r - X_eq[1,0]
X[2,0] = 0
X[3,0] = 0

RETURN
ENDSUB



/**
initialize the statespace model
*/
SUB model_init()

'step time in seconds. Must be sufficiently small
dt = 0.1

'define the parameters
pi = 3.141592654
g = 9.81 :' [m/s^2] gravity
m = 500 :' [kg] mass
k = 2000 :' [N/m] stiffness of the spring
b_air = 10 :' [N*s/m] damping, air friction. If the damping is to small, the system will get unstable due to the linearization and the large steptime
b_spring = 200 :' [N*s/m] damping in the spring
L0 = 8 :' [m] unstressed length of the spring
r_mass = 1 :' [m] the radius of the mass (just for the painting)
k_width = 1 :' [m] width of the spring (just for painting)
k_windings = 5 :' [-] number of windings for painting the spring

'calcuate the equilibrium position
X_eq[0,0] = 0 :'theta, the angle of the spring
X_eq[1,0] = L0+m*g/k :'r, the length of the spring
X_eq[2,0] = 0 :'angular speed of the spring
X_eq[3,0] = 0 :'speed of the radius of the spring (the length)

'choose an initial state (you can change this
X_init[0,0] = 0.2*pi
X_init[1,0] = L0-2
X_init[2,0] = 0
X_init[3,0] = 0

X[0,0] = X_init[0,0] - X_eq[0,0]
X[1,0] = X_init[1,0] - X_eq[1,0]
X[2,0] = X_init[2,0] - X_eq[2,0]
X[3,0] = X_init[3,0] - X_eq[3,0]

'set up the matrices for the state space model
' X_dot = A * X + B * u
' Y     = C * X + D * u

A[0,0] = 0 : A[0,1] = 0 :A[0,2] = 1 :A[0,3] = 0
A[1,0] = 0 : A[1,1] = 0 :A[1,2] = 0 :A[1,3] = 1
A[2,0] = -g*k/(L0*k+m*g): A[2,1] = 0 :A[2,2] = -(b_air)*(L0+m*g/k)/m :A[2,3] = 0
A[3,0] = 0 : A[3,1] = -1/m*k :A[3,2] = 0 :A[3,3] = -1/m*(b_air+b_spring)

B[0,0] = 0
B[1,0] = 0
B[2,0] = 0
B[3,0] = 0

C[0,0] = 1 : C[0,1] = 0 :C[0,2] = 0 :C[0,3] = 0
C[1,0] = 0 : C[1,1] = 1 :C[1,2] = 0 :C[1,3] = 0

D[0,0] = 0
D[1,0] = 0

RETURN
ENDSUB


/**
evaluate the statespace model for the next step
*/
SUB model_eval()
DOUBLE theta, r
DOUBLE x_mass, y_mass
INT row, col

CONST stepCount = 5
DOUBLE dt_small
INT steps

'calculate the position of the mass from the current state
theta = X[0,0] + X_eq[0,0]
r     = X[1,0] + X_eq[1,0]

x_mass =  r * sin(theta)
y_mass = -r * cos(theta)

'Paint the mass-spring in it's current position on the screen
paint(x_mass, y_mass)

IF grabbed = False

'to make the model more accurate, the model is evaluated in 5 steps with
'a steptime of dt/5
dt_small = dt/stepCount
FOR steps = 1 TO stepCount
'Evaluate the model for the next timestep
'Xdot = A * X;
FOR row = 0 TO 3
Xdot[row,0] = 0
FOR col = 0 TO 3
Xdot[row,0] += A[row, col] * X[col,0]
NEXT col
NEXT row

'X = X + Xdot * dt;
FOR row = 0 TO 3
X[row,0] += Xdot[row,0] * dt_small
NEXT row
NEXT steps
ELSE
'the mass is currently hold by the user (mouse down on mass)

ENDIF

RETURN
ENDSUB


/**
Initialize the position of the origin on the screen, and the scale
*/
SUB screen_init()
'calculate the position of the origin where the spring is connected to the world
x0 = 317
y0 = 90

'choose a scale to go from meters to pixels
x_scale = 20
y_scale = -20

grabbed = false

RETURN
ENDSUB



/**
repaint background, mass, spring
*/
SUB paint(x_mass:DOUBLE, y_mass:DOUBLE)

MOVESPRITE(background, -2, -25)
DRAWSPRITE(background)
paint_spring(x0, y0, x0+x_mass*x_scale, y0+y_mass*y_scale, k_windings, k_width)
paint_mass(x0+x_mass*x_scale, y0+y_mass*y_scale)

FLIP

RETURN
ENDSUB


/**
repaint the mass at position (x,y) on the screen
@param x the x coordinate of the center of the mass
@param y the y coordinate of the center of the mass
*/
SUB paint_mass(x:INT, y:INT)
DOUBLE r
r = r_mass * ABS(x_scale)

SETLINESTYLE BackBuffer, @LSSOLID, 1
CIRCLE(BackBuffer, x, y, r, BLACK, BLACK)
CIRCLE(BackBuffer, x-0.3*r, y-.5*r, r/6, WHITE, WHITE)

RETURN
ENDSUB


/**
repaint the spring
@param x1 the x coordinate of the start point of the spring
@param y1 the y coordinate of the start point of the spring
@param x2 the x coordinate of the end point of the spring
@param y2 the y coordinate of the end point of the spring
@param n the number of windings that the spring has
@param width the width of the spring in meters
*/
SUB paint_spring(x1:INT, y1:INT, x2:INT, y2:INT, n:INT, width:INT)
DOUBLE theta, L, sw
DOUBLE xw, yw, alphaw, Lw
DOUBLE c1, c2
INT nw

SETLINESTYLE BackBuffer, @LSSOLID, 2

'calculate the width of the spring in pixels
sw = width * ABS(x_scale)

xw = x1
yw = y1
L = SQRT((x2-x1)^2 + (y2-y1)^2)
theta = atan2(x2-x1, (y2-y1))

c1 = (sw/2)
c2 = (L/(n+2)/4)
Lw = SQRT(c1^2 + c2^2) * 2
alphaw = ATAN(c1 / c2)

FRONTPEN BackBuffer, GRAY

MOVE(BackBuffer, xw, yw)
xw += c2*3 * SIN(theta)
yw += c2*3 * COS(theta)
LINETO(BackBuffer, xw, yw)
xw += Lw/2 * SIN(theta + alphaw)
yw += Lw/2 * COS(theta + alphaw)
LINETO(BackBuffer, xw, yw)

FOR nw = 0 TO n-1
xw += Lw * SIN(theta - alphaw)
yw += Lw * COS(theta - alphaw)
LINETO(BackBuffer, xw, yw)

xw += Lw * SIN(theta + alphaw)
yw += Lw * COS(theta + alphaw)
LINETO(BackBuffer, xw, yw)
NEXT nw

xw += Lw/2 * SIN(theta - alphaw)
yw += Lw/2 * COS(theta - alphaw)
LINETO(BackBuffer, xw, yw)
xw += c2*3 * SIN(theta)
yw += c2*3 * COS(theta)
LINETO(BackBuffer, xw, yw)

CIRCLE(BackBuffer, x1, y1, 3, GRAY, GRAY)
CIRCLE(BackBuffer, x2, y2, 3, GRAY, GRAY)

RETURN
ENDSUB


/**
'calculates the inverse tangent, with respect to the right quadrant
'returns the angle of the vector (x,y) in radians, in the range [-pi, Pi)
*/
SUB atan2(y:DOUBLE,x:DOUBLE), DOUBLE
IF x>=0
RETURN ATAN(y/x) :'(x>0,y>0)    and (x>0,y<0)
ELSE
IF y<0
RETURN ATAN(y/x) - pi :'third quadrant
ELSE
RETURN ATAN(y/x) + pi :'second quadrant
ENDIF
ENDIF
RETURN 0
ENDSUB
Maurice

maurice1

and you need this background in the source folder to compile
Maurice

Bruce Peaslee

Thanks for the reply. 

The program shows how to change the cursor which is useful, but it computes the location of the mouse to simulate a mouseover effect. We're halfway there. Windows can be made to send a message when the mouse is over a control and this is what I would still like to see.
Bruce Peaslee
"Born too loose."
iTired (There's a nap for that.)
Well, I headed for Las Vegas
Only made it out to Needles

sapero

November 30, 2007, 12:40:35 PM #4 Last Edit: November 30, 2007, 12:52:53 PM by sapero
If a cursor can be changed for all windows with same class name (like edit, button), the simplest way is to change the cursor for window class:
SetClassLong(hwndEdit, GCL_HCURSOR, cursor_handle)
Note: if you change class cursor for a button class, it will affect all radiobuttons, line controls and checkboxes, because these are buttons.

If you need to change cursor for just one window then subclass it, detect and "eat" WM_SETCURSOR, but other messages pass back to the provious window procedure:

hwnd = GetControlHandle(...)

// global defined variable of type int/uint
g_EditWndProc = SetWindowLong(hwnd, GWL_WNDPROC, &MyEditProc)

sub MyEditProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam)

if (uMsg = WM_SETCURSOR)
_SetCursor(g_MyCursor) /* aliased api command SetCursor */
return 0 /* do not forward */
endif

if (uMsg = WM_DESTROY)
/* remove sublass */
SetWindowLong(hwnd, GWL_WNDPROC, g_EditWndProc)
endif

/* forward all other messages */
return CallWIndowProc(g_EditWndProc, hwnd, uMsg, wParam, lParam)
endsub


If you change GWL_WNDPROC for x different windows with same window-class like "edit", you need to save the return value (g_EditWndProc) only once, because all standard edit controls have same window proc.

LarryMc

Sapero

I understand Bruce's question to be that he wants to change the cursor when he hovers over a control WITHIN a window.

I believe the NM_HOVER constant has to be used along with the contents of the NMHDR structure which will have the controls ID number in it.

It involves WM_NOTIFY but I'm not smart enough to write the code to make it all work.

Larry
LarryMc
Larry McCaughn :)
Author of IWB+, Custom Button Designer library, Custom Chart Designer library, Snippet Manager, IWGrid control library, LM_Image control library

Bruce Peaslee

Quote from: Larry McCaughn on November 30, 2007, 12:50:44 PM
Sapero

I understand Bruce's question to be that he wants to change the cursor when he hovers over a control WITHIN a window.

I believe the NM_HOVER constant has to be used along with the contents of the NMHDR structure which will have the controls ID number in it.

It involves WM_NOTIFY but I'm not smart enough to write the code to make it all work.

Larry

Exactly.

This is the Aurora code I am trying to convert:


class StaticLink:CStatic
...
StaticLink::OnMouseMove(int x, int y, int flags), int
{
   TRACKMOUSEEVENT tme;
   tme.cbSize      = len(TRACKMOUSEEVENT);
   tme.dwFlags     = TME_HOVER | TME_LEAVE;
   tme.hwndTrack   = m_hwnd;
   tme.dwHoverTime = 25;
   _TrackMouseEvent(tme);
   return true;
}
...
StaticLink::WndProc(unsigned int message, unsigned int wparam, unsigned int lparam),int
{
   select message
   {
      case WM_MOUSEHOVER:
         ::SetCursor(LoadCursorA(null,IDC_HAND));
   }
   return CStatic!!WndProc(message,wparam,lparam);
}

Bruce Peaslee
"Born too loose."
iTired (There's a nap for that.)
Well, I headed for Las Vegas
Only made it out to Needles

Ionic Wind Support Team

You need to subclass the static control in Emergence.   

In Aurora all controls are subclassed on creation and the class/derived classes get a wack at any messages they want to intercept just by overriding the virtual WndProc method.  It makes things a bit more convenient.

I am writing up an example for Emergence now, since I was just working on a subclassed edit contol using a class that attaches itself to the ID of the control.  Doing the same thing for a static is just as simple....

Paul.

Ionic Wind Support Team

Ionic Wind Support Team

Bruce,
Pretty sure this does what you want.

include file CStaticLink.inc

Class CStaticLink
declare CStaticLink()
declare _CStaticLink()
declare Attach(parent as window,id as INT)
declare wndproc(hwnd as UINT, uMsg as UINT, wParam as UINT, lParam as UINT),UINT
'members
UINT m_lpfn
UINT m_hControl
endclass


Class file CStaticLink.eba


$include "windows.inc"
$include "CStaticLink.inc"

DECLARE IMPORT,SetPropA(hwnd as UINT,lpString as STRING,hData as UINT),INT
DECLARE IMPORT,GetPropA(hwnd as UINT,lpString as STRING),UINT
DECLARE IMPORT,RemovePropA(hwnd as UINT,lpString as STRING),UINT
DECLARE IMPORT,LoadCursorA(hInstance AS INT,lpCursorName AS POINTER),INT

CONST IDC_HAND = 32649

SUB CStaticLink::CStaticLink()
m_lpfn = NULL
        m_hControl = NULL
ENDSUB

SUB CStaticLink::_CStaticLink()
'if control is still alive when we die, then remove subclass
IF m_lpfn AND m_hControl
_SetWindowLong(m_hControl,-4,m_lpfn)
m_lpfn = NULL
ENDIF
ENDSUB

SUB CStaticLink::Attach(parent as window,id as INT)

'support reusability
IF m_lpfn AND m_hControl
_SetWindowLong(m_hControl,-4,m_lpfn)
m_lpfn = NULL
ENDIF
m_hControl = GetControlHandle(parent,id)
IF m_hControl
m_lpfn = _SetWindowLong(m_hControl,-4,&CStaticLink_wndproc)
SetPropA(m_hControl,"THIS",this)
ENDIF
ENDSUB



SUB CStaticLink_wndproc(hwnd as UINT, uMsg as UINT, wParam as UINT, lParam as UINT),UINT
'map to object space
pointer pThis:pThis = GetPropA(hwnd,"THIS")
SETTYPE pThis,CStaticLink
if pThis <> NULL
RETURN #pThis.wndproc(hwnd, uMsg, wParam, lParam)
endif
RETURN 0
ENDSUB

SUB CStaticLink::wndproc(hwnd as UINT, uMsg as UINT, wParam as UINT, lParam as UINT),UINT
select uMsg
case @IDDESTROY
IF m_lpfn <> 0
RemovePropA(m_hControl,"THIS")
_SetWindowLong(m_hControl,-4,m_lpfn)
m_hControl = NULL
ENDIF
case WM_SETCURSOR
_SetCursor(LoadCursorA(null,IDC_HAND))
Return 0
endselect
IF m_lpfn <> 0
RETURN _CallWindowProc(m_lpfn,hwnd,uMsg,wParam,lParam)
ENDIF
RETURN 0
ENDSUB


And a test program.  CstaticLink_test.eba


$include "CStaticLink.eba"

DECLARE IMPORT, GetSysColor(clr as INT),UINT
CONST STATIC_1 = 100
CONST BUTTON_2 = 2
DIALOG d1
CREATEDIALOG d1,0,0,300,202,0x80C80080,0,"Caption",&d1_handler
CONTROL d1,@STATIC,"Static link test",67,49,166,16,0x5000010B|SS_NOPREFIX,STATIC_1
CONTROL d1,@BUTTON,"Close",115,128,70,20,0x50000000,BUTTON_2
CStaticLink stl

DoModal d1

SUB d1_handler
SELECT @MESSAGE
CASE @IDINITDIALOG
CENTERWINDOW d1
/* Initialize any controls here */
SetControlColor d1,STATIC_1,0xFF0000,GetSysColor(15)
SetFont d1,"Arial",10,400,@SFUNDERLINE,STATIC_1
stl.Attach(d1,STATIC_1)
CASE @IDCLOSEWINDOW
CLOSEDIALOG d1,@IDOK
CASE @IDCONTROL
SELECT @CONTROLID
CASE BUTTON_2
IF @NOTIFYCODE = 0
/*button clicked*/
CLOSEDIALOG d1,@IDOK
ENDIF
ENDSELECT
ENDSELECT
RETURN
ENDSUB


Note that I broke one of my own rules here.  I $included code for the purposes of the test program.  In an actual project you would add CStaticLink.eba to the project and just $include "CStaticLink.inc" where ever you wanted to use the class.

You could make this class more robust by setting the font and colors automatically, respond to mouseclicks in CStaticLink::wndproc, etc.

Using the class means you don't have to worry about when to subclass the control, when to remove the subclass, etc.  It is all handled for you automatically ;)

Paul.
Ionic Wind Support Team

LarryMc

It works but I don't understand why WM_SETCURSOR was used instead of a HOVER message.

What caused the WM_SETCURSOR message to be sent?

Larry
LarryMc
Larry McCaughn :)
Author of IWB+, Custom Button Designer library, Custom Chart Designer library, Snippet Manager, IWGrid control library, LM_Image control library

Ionic Wind Support Team

Windows sends that message to a window whenever the mouse cursor is within the confines of window.   It is the correct message to use if you are just looking to specify a cursor for your window, and works on all MS OS's.

Of course information about the message is provided in more detail from the horses mouth:

http://msdn2.microsoft.com/en-us/library/ms648382.aspx

WM_MOUSEHOVER, on the other hand, is generally used to provide timed pop-up information.  Such as tool-tips. It has been available since Win 98. 

http://msdn2.microsoft.com/en-us/library/ms645613.aspx

Paul.
Ionic Wind Support Team

Bruce Peaslee

It work's alright.

I'll have to study it a bit more before I fully understand it.

Thanks for all the responses and thanks Paul.
Bruce Peaslee
"Born too loose."
iTired (There's a nap for that.)
Well, I headed for Las Vegas
Only made it out to Needles