Move layer with mouse

Find quick help here to get you started with Hollywood
Post Reply
User avatar
Clyde
Posts: 348
Joined: Sun Feb 14, 2010 12:38 pm
Location: Dresden / Germany

Move layer with mouse

Post by Clyde »

Hi guys,

I feel quite dump to ask this, but ... What I want to do is to have serveral brush layers, click on a one of the layer and move the layer/brush while I hold down the mouse button. Just like you do in standard graphic programms like Photoshop and the like to place the image somewhere else.

So, this is the code I came up with, but 1) it looks very complicated and 2) it does not work well:

Code: Select all

@VERSION 8,0

@BRUSH 1, "Limo1_freigestellt.png" ; this is a 100 x 105 px graphic

EnableLayers()

DisplayBrush(1, #CENTER, #CENTER)

mouseIsPressed = False
currentMousePosition = {}
layerID = 0

Function handleMouseEvents(msg)
    
    Switch msg.Action
        Case "OnMouseDown":

	    ; get curent mouse position
            currentMousePosition = {
                x = MouseX(),
                y = MouseY()
            }

            ; set flag, rhat mouse button is pressed
            mouseIsPressed = True

            ; get the layer that is at the current mouse position
            layerID = GetLayerAtPos(currentMousePosition.x, currentMousePosition.y)

            If (layerID > 0)

                ; get the layer position on the screen
                layerXPos = GetAttribute(#LAYER, layerID, #ATTRXPOS)
                layerYPos = GetAttribute(#LAYER, layerID, #ATTRYPOS)

                ; get layer dimensions
                Local layerWidth = GetAttribute(#LAYER, layerID, #ATTRWIDTH)
                Local layerHeight = GetAttribute(#LAYER, layerID, #ATTRHEIGHT)

                ; calculate the relative click position on the layer
                relativeLayerXPos = currentMousePosition.x - layerXPos
                relativeLayerYPos = currentMousePosition.y - layerYPos

                ; with the calculated relative position set the new anchor point to where you clicked with the mouse
                Local newAnchorX = (relativeLayerXPos) / layerWidth
                Local newAnchorY = (relativeLayerYPos) / layerHeight

                SetLayerAnchor(layerID, newAnchorX, newAnchorY)

		; unfortunately as SetLayerAnchor moves the image, set it back to its orginal position
                MoveLayer(layerID, #USELAYERPOSITION, #USELAYERPOSITION, layerXPos + relativeLayerXPos, layerYPos + relativeLayerYPos, {Speed = #FASTSPEED})

            EndIf

        Case "OnMouseUp":

	    ; reset all values and set anchor back to standard
            currentMousePosition = {}
            mouseIsPressed = False

            SetLayerAnchor(layerID, 0, 0)

	    ; unfortunately move the layer back as SetLayerAnchor changes the position
            MoveLayer(layerID, #USELAYERPOSITION, #USELAYERPOSITION, #USELAYERPOSITION - relativeLayerXPos, #USELAYERPOSITION - relativeLayerYPos, {Speed = #FASTSPEED})

            layerID = 0

        Case "OnMouseMove":

            If mouseIsPressed = True

                If (layerID > 0)

            	    ; the layer should follow the mouse now (but it is very laggy)
		    MoveLayer(layerID, #USELAYERPOSITION, #USELAYERPOSITION, msg.x, msg.y, {Speed = #FASTSPEED})

                EndIf
            EndIf

    EndSwitch
EndFunction

InstallEventHandler({OnMouseUp = handleMouseEvents, OnMouseDown = handleMouseEvents, OnMouseMove = handleMouseEvents})

Repeat
    WaitEvent
Forever
Sorry it is not shorter. I tried to shorten it as good as I could. In fact, I use it in a Hollywood display in RapaGUI.

So, the functional problems are that because of SetLayerAnchor() moves the image position, I have to re-position the image, which does not look nice.
Also, when I move the mouse the image follows quite slowly/laggy which is really bad. :-(

So, I assume that this is a standard task to do (let an object follow the mouse instantly). What is the best way to achieve it/what am I missing?

Thanks a lot!
Currently using: Hollywood 9 with Windows IDE and Hollywood 9 with Visual Studio Code and hw4vsc
User avatar
Clyde
Posts: 348
Joined: Sun Feb 14, 2010 12:38 pm
Location: Dresden / Germany

Re: Move layer with mouse

Post by Clyde »

*I mean "dumb", not "dump". Not sure why I can't edit my post. :-/
Currently using: Hollywood 9 with Windows IDE and Hollywood 9 with Visual Studio Code and hw4vsc
PEB
Posts: 567
Joined: Sun Feb 21, 2010 1:28 am

Re: Move layer with mouse

Post by PEB »

This is how I've done it in the past:

Code: Select all

@VERSION 8,0

@BRUSH 1, "Test.png" 

EnableLayers()

DisplayBrush(1, #LEFT, #TOP, {Name="Layer1"})
DisplayBrush(1, #RIGHT, #TOP, {Name="Layer2"})
DisplayBrush(1, #LEFT, #BOTTOM, {Name="Layer3"})
DisplayBrush(1, #RIGHT, #BOTTOM, {Name="Layer4"})
DisplayBrush(1, #CENTER, #CENTER, {Name="Layer5"})

Function p_MoveLayer(msg)
	ShowLayer("Layer"..msg.userdata, msg.x-CompX, msg.y-CompY)
EndFunction

Function p_ButtonFunc(msg)
	Switch msg.action
		Case "OnMouseDown"
			CompX=MouseX()-GetAttribute(#LAYER, "Layer"..msg.id, #ATTRXPOS)
			CompY=MouseY()-GetAttribute(#LAYER, "Layer"..msg.id, #ATTRYPOS)
			InstallEventHandler({OnMouseMove=p_MoveLayer}, msg.id)
		Case "OnMouseUp"
			InstallEventHandler({OnMouseMove=0})
	EndSwitch
EndFunction

evttable={OnMouseDown=p_ButtonFunc, OnMouseUp=p_ButtonFunc}

For Local x=1 To 5
	MakeButton(x, #LAYERBUTTON, "Layer"..x, True, True, evttable)
Next

Repeat
	WaitEvent
Forever
User avatar
Clyde
Posts: 348
Joined: Sun Feb 14, 2010 12:38 pm
Location: Dresden / Germany

Re: Move layer with mouse

Post by Clyde »

This looks sweet, @PEB! Will test it when I am home.

So you use ShowLayer() instead of MoveLayer() - nice. And your shortcut of installing the move-eventhandler in mousdown is very cool!

You code should make its way to the "Code snippets area" here!
Currently using: Hollywood 9 with Windows IDE and Hollywood 9 with Visual Studio Code and hw4vsc
Bugala
Posts: 1178
Joined: Sun Feb 14, 2010 7:11 pm

Re: Move layer with mouse

Post by Bugala »

Yeah, PEBs idea on using eventhandler is pretty good.

And yes, If you use MoveLayer, then that is by default a slow method as its purpose is to move the layer using Hollywoods own move function thing, more like a script command. Showlayer simply shows it on that exact spot you want.

I remembered one time I did the same, although bit differently, copy-pasting the relevant parts of code here so you can look if you are interested.

This is actually meant to be moving Group of Layers, like Layers attached together.

Here is first the LayerGroup code:

Code: Select all

LayerGroup = { Groups = {} }


Function LayerGroup:New()
	Local d = {}
  
	SetMetaTable(d, self)
	self.__index = self

	Return(d)
EndFunction




Function LayerGroup:CreateGroup(GroupID, LayerID) /* is the first Layer, upon which rest will be relative to */
	self.Groups[GroupID] = {}
	self.Groups[GroupID][1] = LayerID
	Local t = GetLayerStyle(layerID)
	userdata = { originalposition = { x = t.x, y = t.y, w = t.width, h = t.height } }
	SetObjectData(#LAYER, LayerID, "userdata", userdata)
EndFunction



Function LayerGroup:DeleteGroup(GroupID)
	self.groups[GroupID] = Nil
EndFunction



Function LayerGroup:AddLayer(GroupID, ...) /* Each ... is LayerID to be added to the specified group */
	Local base_t = GetLayerStyle(LayerGroup.groups[GroupID][1]) /*Baselayers current stylesettings */
	Local userdata = GetObjectData(#LAYER, LayerGroup.groups[GroupID][1], "userdata")
	Local t_orig = userdata.originalposition
	Local origwidthmultiplier  = t_orig.w / base_t.width
	Local origheightmultiplier = t_orig.h / base_t.height

	For Local n = 0 To arg.n-1
		Local t = GetLayerStyle(arg[n])
		Local userdata = { 
				   originaldata = { x = (t.x - base_t.x) * origwidthmultiplier,   y = (t.y - base_t.y) * origheightmultiplier,   w = t.width * origwidthmultiplier,   h = t.height * Origheightmultiplier },  
				   lastposition = { x = t.x,   y = t.y,   w = t.width,   h = t.height } 
				 }
		SetObjectData(#LAYER, arg[n], "userdata", userdata)
		InsertItem(self.Groups[GroupID], arg[n])
	Next
EndFunction



Function LayerGroup:RemoveLayer(GroupID, ...) /* each ... is LayerID to be removed from specified Group */
	For Local n = 0 To arg.n-1
		Local found = -1
		ForEach(self.Groups[GroupID], Function (Index, LayerID)
							If arg[n] = LayerID Then found = Index
					      EndFunction) 
		If found <> -1 Then RemoveItem(self.Groups[GroupID], found)
	Next	
EndFunction



Function LayerGroup:ShowGroup(GroupID, Positiontable)
	Local baselayer = self.groups[GroupID][1]
	Local userdata = GetObjectData(#LAYER, baselayer, "userdata")
	baseorig = userdata.originalposition
	
	Local t = GetLayerStyle(baselayer)  /* Can use GetLayerStyle instead of userdata, because baselayer is always on round() pixels. */

	Local new_x = t.x
	Local new_y = t.y
	Local new_w = t.width
	Local new_h = t.height
	
	If HaveItem(Positiontable, "x") Then new_x = Positiontable.x
	If HaveItem(Positiontable, "y") Then new_y = Positiontable.y
	If HaveItem(Positiontable, "w") Then new_w = Positiontable.w
	If HaveItem(Positiontable, "h") Then new_h = Positiontable.h

	SetLayerStyle(baselayer, { x = new_x, y = new_y, width = new_w, height = new_h } )

	widthmultiplier  = new_w / baseorig.w
	heightmultiplier = new_h / baseorig.h

	Local changedpositionlist = {}
	
	ForEach(self.groups[GroupID], Function (index, layerID)
		If layerID <> baselayer
			Local t = GetLayerStyle(LayerID)
			Local userdata = GetObjectData(#LAYER, layerID, "userdata")
			Local lastposition = userdata.lastposition
			If t.x <> lastposition.x Or t.y <> lastposition.y Or t.width  <> lastposition.w Or  t.height <> lastposition.h  Then InsertItem(changedpositionlist, layerID)	 
		EndIf
				      EndFunction)


	ForEach(changedpositionlist, Function (index, layerID)
			LayerGroup:RemoveLayer(GroupID, layerID)
			LayerGroup:AddLayer(GroupID, layerID)
			     EndFunction)

		
	ForEach(self.groups[GroupID], Function (index, layerID)
			If layerID <> baselayer
			  Local userdata = GetObjectData(#LAYER, layerID, "userdata")
			  Local orig_t = userdata.originaldata
			  
			  Local xpos = new_x + ( orig_t.x * widthmultiplier  )
			  Local ypos = new_y + ( orig_t.y * heightmultiplier )
			  Local wpos = orig_t.w * widthmultiplier
			  Local hpos = orig_t.h * heightmultiplier
			  SetLayerStyle(LayerID, {  x = xpos,  y = ypos,  width = wpos,   height = hpos} )
			  Local temp = GetLayerStyle(LayerID)
			  Local lastposition = { x = temp.x, y = temp.y, w = temp.width, h = temp.height }
			  userdata.lastposition = lastposition
			  SetObjectData(#LAYER, layerID, "userdata", userdata)
			EndIf
				      EndFunction)		
EndFunction

Function LayerGroup:LayerGroupToFront(GroupID)
	Local baselayer = self.groups[GroupID][1]
	SetLayerZPos(baselayer, 0)
	ForEach(self.groups[GroupID], Function (index, layerID)
		If layerID <> baselayer Then SetLayerZPos(layerID, 0)
		      EndFunction)
EndFunction
Using it works following:

First you use LayerGroup:CreateGroup(GroupID, LayerID), here you need to give name to your new group (there can be several existing at same time) and also the first layer which will work as the main layer. Meaning that all future layers attached after, will be in relative positions to this first one. THat if first one is at position 100, 100, and second one is at position 300, 300, that means that from now on, the second layer will always be displayed on position +200, +200 relative to first one.

Example: LayerGroup:CreateGroup("mygroup1", "MyLayer1")


after this you add more layers using:
LayerGroup:AddLayer(GroupName, ...) In this Groupname is a group name already existing, as example the one you just created previous step. Then add as many layers you like to that group.

example: LayerGroup:AddLayer("mygroup1", "mylayer2", "mylayer3", "mylayer4")

Now you would have 4 different layers in that group.

To display these layers somehwere, just use:
LayerGroup:ShowGroup(Groupname, positiontable) - in here groupname the name of the group you wish to show, as example "mygroup1" positiontable is a table that can contain 4 different variables, x, y, width, height. If one of these is not defined, it will use the current one (which is most likely the case with width and height), if defined, will use these new ones. Remember that width and height are based upon the first layers width and height. So if original widht and height were 100, 100, then by using 200, 200, you would be doubling all layers size in that group and their relative distance to first layer.

as example: LayerGroup("MyGroup1", {x=500, y=800})



As to the actual Layer movement thing, here is the moving part from one code of mine that is using this layergroup to do the actual moving:

Code: Select all

Function GameBoard.Board(msg)

Switch msg.action
   Case "OnMouseDown":
	gameboard.movingboard_x = MouseX()
	gameboard.movingboard_y = MouseY()
	Local id = SetInterval(Nil, gameboard.move, 1)
	t_interval["movinggameboard"] = id
   EndSwitch
EndFunction


Function GameBoard.Move()

   If IsLeftMouse() = False
	If HaveItem(t_interval, "movinggameboard") = True	/* Might be better to move it at beginning of function and return if false */	
		ClearInterval(t_interval["movinggameboard"])
		t_interval["movinggameboard"] = Nil
	EndIf
   EndIf
   Local xmovement = MouseX() - gameboard.movingboard_x
   Local ymovement = MouseY() - gameboard.movingboard_y
   gameboard.movingboard_x = MouseX()
   gameboard.movingboard_y = MouseY()
			
	
	
   gameboard.x = gameboard.x + xmovement
   gameboard.y = gameboard.y + ymovement
   LayerGroup:ShowGroup("GameBoard", { x = gameboard.x, y = gameboard.y, w=gameboard.w, h = gameboard.h } )
   /*SetLayerStyle("gameboard", {x = gameboard.x, y = gameboard.y})*/

EndFunction
This is actually using my own t_interval function, but I guess it is easy enough to figure out what it is doing, it is adding an interval to be played, just using this t_interval to have more control about intervals, like being able to name them.

edit: Forgot to mention still, so that width and height in that grouplayer were so that I could shrink or expand some layergroups. Like when some button is not active, they can shrunk like windows programs to that bottom bar, when being the active buttons to choose from, i could expand them in front of the screen begging for attention.
User avatar
Clyde
Posts: 348
Joined: Sun Feb 14, 2010 12:38 pm
Location: Dresden / Germany

Re: Move layer with mouse

Post by Clyde »

Wow, thanks Bugala for your work on this post! It is interesting to see others code. E.g. I should more look into the object orientated aspects of Hollywood. But it is also interesting how you manage the layer stuff. I am not sure whether I will need that complexity, but good to know that I can have a look here.
Currently using: Hollywood 9 with Windows IDE and Hollywood 9 with Visual Studio Code and hw4vsc
User avatar
Clyde
Posts: 348
Joined: Sun Feb 14, 2010 12:38 pm
Location: Dresden / Germany

Re: Move layer with mouse

Post by Clyde »

PEB, your code works like a charm and I could easily adapt it to my code. Thanks a lot again!

I am not sure whether this is new with Hollywood 8, but the object that is passed to the event handler callback (p_ButtonFunc()) has a property called "layername" (msg.layername), so don't have to build you layername wth "Layer"..msg.id but you can instead use msg.layername. This property seems not to be documented.
Currently using: Hollywood 9 with Windows IDE and Hollywood 9 with Visual Studio Code and hw4vsc
PEB
Posts: 567
Joined: Sun Feb 21, 2010 1:28 am

Re: Move layer with mouse

Post by PEB »

Thanks for the information about msg.layername.
That's really helpful (and should be documented).
User avatar
airsoftsoftwair
Posts: 5433
Joined: Fri Feb 12, 2010 2:33 pm
Location: Germany
Contact:

Re: Move layer with mouse

Post by airsoftsoftwair »

PEB wrote: Tue Oct 01, 2019 11:22 pm That's really helpful (and should be documented).
viewtopic.php?f=4&t=1040&start=30#p12249
LarsB
Posts: 72
Joined: Sat May 06, 2017 4:37 pm

Re: Move layer with mouse

Post by LarsB »

@PEB sweet! Could be very handy for mindgames. Thank you.
Post Reply