ID:2540543
 
I don't have much to say about this one. It occurred to me that working with the params argument of mouse procs really sucks. Yeah, it'd be nice if we could transition it to something better, but that creates a lot of overhead for information that's gonna be thrown away.

Thankfully, BYOND 512 and 513 have freed us from the majority of this problem. With operator overloading, we can now take matters into our own hands and free ourselves from the majority of the boilerplate that infects working with the mouse without having to parse information we don't want to use.

Let's open by setting up some initial defines. If you already have values like this in your project, you can omit these.

#ifndef TILE_WIDTH
#define TILE_WIDTH 32
#endif

#ifndef TILE_HEIGHT
#define TILE_HEIGHT 32
#endif


I hate dealing with the screen-loc parameter. We can take a lot of the work out of it by declaring a utility proc:

var/regex/__rgx_sloc = regex(@"[:,]","g")

proc
parse_screen_loc(sloc)
var/list/l = splittext(sloc,__rgx_sloc)
switch(l.len)
if(5)
return list((text2num(l[2])-1) * TILE_WIDTH + text2num(l[3]),(text2num(l[4])-1) * TILE_HEIGHT + text2num(l[5]),l[1])
if(4)
return list((text2num(l[1])-1) * TILE_WIDTH + text2num(l[2]),(text2num(l[3])-1) * TILE_HEIGHT + text2num(l[4]),null)
else
return list(0,0,null)


Next, let's create a mouse_params datum. What we're going to do, is wrap the param string in a datum. We're going to use this datum to make working with the string a lot easier. yes, this will involve some extra overhead. However, if both the client and the atom involved in a mouse proc need to read the paramslist, we are getting cost-savings by working with a mutable datum rather than an immutable string and two separate lists. We are also saving time by generalizing common tasks you will have to do to work with the params string.

#define MOUSE_NONE     0
#define MOUSE_BUTTON1 1
#define MOUSE_BUTTON2 2
#define MOUSE_BUTTON3 4

#define MOUSE_BUTTONS 7

#define MOUSE_SHIFT 8
#define MOUSE_CTRL 16
#define MOUSE_ALT 32

#define MOUSE_MODIFIERS 56

#define MOUSE_NOT_DIRTY 0
#define MOUSE_PARAMS_DIRTY 1
#define MOUSE_VIS_DIRTY 2
#define MOUSE_SCREEN_DIRTY 4
#define MOUSE_ICON_DIRTY 8
#define MOUSE_BUTTONS_DIRTY 16
#define MOUSE_DIRTY 31

mouse_params
var
string
params
screen_pos
vis_pos
icon_pos
buttons = MOUSE_NONE

dirty_flags = MOUSE_DIRTY

New(str)
string = str
dirty_flags = MOUSE_DIRTY
..()

proc
operator[](idx)
//TODO

operator[]=(idx,value)
//TODO


This is the basic structure of the mouse_params datum. As you can see, we added some procs called operator[] and operator[]=. This is a BYOND 512 feature that allows you to change how using operators on a datum works. Since we overrode [] and []=, we're going to be interacting with this object as though it were an associative list, for the most part.

The dirty_flags variable is important to how this object is going to function. We want to make some of the values easier to work with. I don't like using text2num() everywhere, or parsing screen-locs when working with mouse procs. I don't like hunting for which buttons are on or off, so I've created some storage spaces for storing more useful versions of these parameters. dirty_flags will tell you what has been parsed and what has not, so that we can parse what's needed when we need it. This saves a little bit of processing when we don't need all the data, and it costs a little extra when we do need all of the data, but again, the cost savings overall will be significant because most of the time, we're discarding unneeded data rather than working with every parameter.

Let's take a look at how we should implement these objects in the client:

client
MouseDown(atom/object,atom/location,control,params)
params = new/mouse_params(params)
..()


That's it. You need to implement this behavior in any Mouse proc your project actually uses. MouseDown(), MouseUp(), Click(), DoubleClick(), MouseDrag(), MouseMove(), MouseWheel(), MouseDrop(), MouseEnter(), and MouseExit().

So let's loop back to actually implementing the operator overloads one at a time:

    proc
operator[](idx)
if(dirty_flags&MOUSE_PARAMS_DIRTY && idx!="params")
params = params2list(string)
dirty_flags &= ~MOUSE_PARAMS_DIRTY

switch(idx)
if("params")
return string
if("params-list")
return params
if("screen-loc","left","middle","right","ctrl","shift","alt","drag-cell","drop-cell","drag","link")
return params[idx]
if("screen-pos")
if(dirty_flags&MOUSE_SCREEN_DIRTY)
screen_pos = parse_screen_loc(params["screen-loc"])
dirty_flags &= ~MOUSE_SCREEN_DIRTY
return screen_pos
if("vis-pos","vis-x","vis-y")
if(dirty_flags&MOUSE_VIS_DIRTY)
var/vx = params["vis-x"], vy = params["vis-y"], list/l
if(!vx||!vy)
if(dirty_flags&MOUSE_ICON_DIRTY)
vx = params["icon-x"]; vy = params["icon-y"]
l = list(text2num(vx),text2num(vy))
dirty_flags &= ~MOUSE_ICON_DIRTY
else
l = icon_pos
vis_pos = l||(l = list(text2num(vx),text2num(vy)))
dirty_flags &= ~MOUSE_VIS_DIRTY
switch(idx)
if("vis-pos")
return vis_pos
if("vis-x")
return vis_pos[1]
if("vis-y")
return vis_pos[2]
if("icon-pos","icon-x","icon-y")
if(dirty_flags&MOUSE_ICON_DIRTY)
icon_pos = list(text2num(params["icon-x"]),text2num(params["icon-y"]))
dirty_flags &= ~MOUSE_ICON_DIRTY
switch(idx)
if("icon-pos")
return icon_pos
if("icon-x")
return icon_pos[1]
if("icon-y")
return icon_pos[2]
if("buttons")
if(dirty_flags&MOUSE_BUTTONS_DIRTY)
var/b = 0
if(params["left"])
b += MOUSE_BUTTON1
if(params["middle"])
b += MOUSE_BUTTON3
if(params["right"])
b += MOUSE_BUTTON2
if(params["shift"])
b += MOUSE_SHIFT
if(params["ctrl"])
b += MOUSE_CTRL
if(params["alt"])
b += MOUSE_ALT
buttons = b
dirty_flags &= ~MOUSE_BUTTONS_DIRTY
return buttons
else
return null


This proc will handle both the parsing and formatting of the demanded data available in the param string. Notice how anywhere we parse data, we turn off the dirty_flag associated with that data, so we only parse the demanded data a single time.

Now let's implement index assignment, just for parity. We don't really need to do this, but you never know.

        operator[]=(idx,value)
switch(idx)
if("params","params-list")
if(istext(value))
string = value
dirty_flags = MOUSE_DIRTY
else
params = value
dirty_flags = MOUSE_DIRTY & ~MOUSE_PARAMS_DIRTY
if("screen-loc")
if(dirty_flags & MOUSE_PARAMS_DIRTY)
params = params2list(string)
dirty_flags &= ~MOUSE_PARAMS_DIRTY
dirty_flags |= MOUSE_SCREEN_DIRTY
params[idx] = value
if("left","middle","right","ctrl","shift","alt")
if(dirty_flags & MOUSE_PARAMS_DIRTY)
params = params2list(params)
dirty_flags &= ~MOUSE_PARAMS_DIRTY
dirty_flags |= MOUSE_BUTTONS_DIRTY
params[idx] = value
if("drag-cell","drop-cell","drag","link")
if(dirty_flags & MOUSE_PARAMS_DIRTY)
params = params2list(params)
dirty_flags &= ~MOUSE_PARAMS_DIRTY
params[idx] = value
if("vis-x","vis-y")
if(dirty_flags & MOUSE_PARAMS_DIRTY)
params = params2list(params)
dirty_flags &= ~MOUSE_PARAMS_DIRTY
dirty_flags |= MOUSE_VIS_DIRTY
params[idx] = "[value]"
if("vis-pos")
if(dirty_flags & MOUSE_PARAMS_DIRTY)
params = params2list(params)
dirty_flags &= ~MOUSE_PARAMS_DIRTY
dirty_flags &= ~MOUSE_VIS_DIRTY
vis_pos = value
params["vis-x"] = "[value[1]]"
params["vis-y"] = "[value[2]]"
if("icon-x","icon-y")
if(dirty_flags & MOUSE_ICON_DIRTY)
params = params2list(params)
dirty_flags &= ~MOUSE_PARAMS_DIRTY
dirty_flags |= MOUSE_ICON_DIRTY
params[idx] = "[value]"
if("icon-pos")
if(dirty_flags & MOUSE_PARAMS_DIRTY)
params = params2list(params)
dirty_flags &= ~MOUSE_PARAMS_DIRTY
dirty_flags &= ~MOUSE_ICON_DIRTY
icon_pos = value
params["icon-x"] = "[value[1]]"
params["icon-y"] = "[value[2]]"
else
return value
return value


This one's pretty repetitive, and actually is likely unnecessary, but it can't hurt to define it.

Now I hear you: "But Ter, I already do things differently, and I'm used to it. Why should I spend all the time changing?" I have an answer for you: You don't have to:

proc
params2listx(str)
return params2list(str)

var/__paramstring
#define params2list(x) (istype((__paramstring=(x)),/mouse_params) ? (__paramstring = __paramstring["params-list"]) : params2listx(__paramstring))


Put this define anywhere after mouse_params and her operator overloads are declared, and now we can treat a param string and a mouse_params datum as the same thing. Yes, this will generate a little extra overhead until you migrate away from calling params2list() on mouse procs, but it's a good temporary fix while you implement the new behavior a bit at a time.


So with all of that done, let's take a look at what this allows you to do:


We can now quickly figure out what mouse buttons have been pressed without complex if-else chains:
turf
Click(atom/location,control,params)
switch(params["buttons"] & MOUSE_BUTTONS)
if(MOUSE_BUTTON1)
world.log << "left click"
if(MOUSE_BUTTON2)
world.log << "right click"
if(MOUSE_BUTTON3)
world.log << "middle click"


We can also do the same with mouse-modifiers:

turf
Click(atom/location,control,params)
switch(params["buttons"] & MOUSE_MODIFIERS)
if(MOUSE_SHIFT)
world.log << "shift held"
if(MOUSE_CTRL)
world.log << "ctrl held"
if(MOUSE_ALT)
world.log << "alt held"
if(MOUSE_SHIFT|MOUSE_CTRL)
world.log << "ctrl+shift held"
if(MOUSE_SHIFT|MOUSE_ALT)
world.log << "alt+shift held"
if(MOUSE_SHIFT|MOUSE_ALT|MOUSE_CTRL)
world.log << "ctrl+alt+shift held"


We can now get the icon pos as a number directly:

turf
Click(atom/location,control,params)
world.log << "[params["icon-pos"][1]] [params["icon-pos"][2]]"
//or:
world.log << "[params["icon-x"]] [params["icon-y"]]"
//or:
var/list/l = params["icon-pos"]
world.log << "[l[1]] [l[2]]"


Same for vis pos

turf
Click(atom/location,control,params)
world.log << "[params["vis-pos"][1]] [params["vis-pos"][2]]"
//or:
world.log << "[params["vis-x"]] [params["vis-y"]]"
//or:
var/list/l = params["vis-pos"]
world.log << "[l[1]] [l[2]]"


We can also get the screen-loc in pixel coordinates easily:

turf
Click(atom/location,control,params)
var/list/l = params["screen-pos"]
world.log << "[l[1]] [[l[2]]"


You can get the params string:

turf
Click(atom/location,control,params)
world.log << params["params"]


You can also get the params list:

turf
Click(atom/location,control,params)
var/list/l = params["params-list"]


Every other property can be accessed the same way.

In addition, in the case where you access any of the variables of the params, it will persist down the call chain, so it does not need to be re-parsed by overrides further up, so it will reduce wasted cycles handling data that's already been handled and discarded.

Once the mouse proc ends, the param datum is discarded automatically. No fuss, no muss.
Don't forget that screen-loc can begin with "map_id:" sometimes. That extra colon can screw up your indices.
In response to Kaiochao
Kaiochao wrote:
Don't forget that screen-loc can begin with "map_id:" sometimes. That extra colon can screw up your indices.

Whores. [fixed]. Thanks Kaio. I never use multiple map controls, so... Oops.
I also wanted to drop a couple additional pieces of code in here, because I don't like dealing with MouseDrag().

Implementing MouseDragStart()

Detecting when a mouse drag action has begun is something that's very helpful for handling mouse actions. It's easy, but there's some weird gotchas. Here's a simple implementation of it:

#define MOUSE_HOVER    0
#define MOUSE_HOLD 1
#define MOUSE_RELEASE 2
#define MOUSE_DRAG 3
#define MOUSE_DROP 4

client
var
mouse_state
list/mouse_action

MouseDown(atom/object,atom/location,control,params)
mouse_state = MOUSE_HOLD
mouse_action = args.Copy()
mouse_action += mouse_action
..()

MouseUp(atom/object,atom/location,control,params)
..()
if(mouse_state==MOUSE_DRAG)
//fix cases where MouseDrop isn't called.
if(!control)
MouseDrop(mouse_action[1],object,mouse_action[2],location,mouse_action[3],control,params)
spawn(0)
if(mouse_action==action)
MouseDrop(mouse_action[1],object,mouse_action[2],location,mouse_action[3],control,params)
mouse_state = MOUSE_HOVER
mouse_action = null

MouseDrag(atom/src_object,atom/over_object,atom/src_location,atom/over_location,src_control,over_control,params)
mouse_action[5] = over_object
mouse_action[6] = over_location
mouse_action[7] = over_control
mouse_action[8] = params
if(mouse_state!=MOUSE_DRAG)
MouseDragStart(mouse_action[1],object,mouse_action[2],location,mouse_action[3],control,mouse_action[4],params)
..()

MouseDrop(atom/src_object,atom/over_object,atom/src_location,atom/over_location,src_control,over_control,params)
mouse_action[5] = over_object
mouse_action[6] = over_location
mouse_action[7] = over_control
mouse_action[8] = params
..()

proc
MouseDragStart(atom/src_object,atom/over_object,atom/src_location,atom/over_location,src_control,over_control,start_params,drag_params)
set waitfor = 0
mouse_state = MOUSE_DRAG
src_object.MouseDragStart(over_object,src_location,over_location,src_control,over_control,start_params,drag_params)

atom
proc
MouseDragStart(atom/over_object,atom/src_location,atom/over_location,src_control,over_control,start_params,drag_params)
set waitfor = 0


The reason we're storing the current mouse action args in a list is so we can access them inside of MouseDragStart(), and just do a while(client.mouse_action==action) loop to update the drag position of the mouse a single time every frame. This prevents performing wasted logic from the many mouseDrag() calls that DM supplies per frame. The first four elements in the list store the start object, location, control, and params, and the last four elements store the most recent over_object, location, control, and params. This makes tracking the mouse during a drag action so much easier than it otherwise would be.

But this kind of sucks to work with, so let's give it the datum treatment and make it nicer:

mouse_action
var
list/data
state = MOUSE_HOVER

New(status,start_object=null,start_location=null,start_control=null,start_params=null)
data = list(start_object,start_location,start_control,start_params,start_object,start_location,start_control,start_params)
state = status
..()

proc
Move(over_object,over_location,over_control,params)
data[5] = over_object
data[6] = over_location
data[7] = over_control
data[8] = params
state = MOUSE_HOVER

Release()
state = MOUSE_RELEASE

Drag(over_object,over_location,over_control,params)
data[5] = over_object
data[6] = over_location
data[7] = over_control
data[8] = params
state = MOUSE_DRAG

Drop(over_object,over_location,over_control,params)
data[5] = over_object
data[6] = over_location
data[7] = over_control
data[8] = params
state = MOUSE_DROP

proc
operator[](idx)
if(isnum(idx))
return data[idx]
else if(istext(idx))
switch(idx)
if("object","src-object","start-object")
return data[1]
if("location","src-location","start-location")
return data[2]
if("control","src-control","start-control")
return data[3]
if("params","src-params","start-params")
return data[4]
if("over-object","end-object")
return data[5]
if("over-location","end-location")
return data[6]
if("over-control","end-control")
return data[7]
if("over-params","end-params")
return data[8]
if("state")
return state
else
return null

operator[]=(idx,value)
if(isnum(idx))
return data[idx] = value
else if(istext(idx))
switch(idx)
if("object","src-object","start-object")
data[1] = value
if("location","src-location","start-location")
data[2] = value
if("control","src-control","start-control")
data[3] = value
if("params","src-params","start-params")
data[4] = value
if("over-object","end-object")
data[5] = value
if("over-location","end-location")
data[6] = value
if("over-control","end-control")
data[7] = value
if("over-params","end-params")
data[8] = value
if("state")
state = value
return value
else
return null


Okay, that's a little bit better. Let's adjust the client procs to suit it now:

client
var
list/mouse_action

MouseDown(atom/object,atom/location,control,params)
mouse_action = new/mouse_action(MOUSE_HOLD,object,location,control,params)
..()

MouseUp(atom/object,atom/location,control,params)
..()
if(mouse_action.state==MOUSE_DRAG)
//fix cases where MouseDrop isn't called.
var/action = mouse_action
if(!control)
MouseDrop(mouse_action[1],object,mouse_action[2],location,mouse_action[3],control,params)
spawn(0)
if(mouse_action==action)
MouseDrop(mouse_action[1],object,mouse_action[2],location,mouse_action[3],control,params)
else
mouse_action.Release()
mouse_action = new/mouse_action(MOUSE_HOVER,null,null,control,params)

MouseDrag(atom/src_object,atom/over_object,atom/src_location,atom/over_location,src_control,over_control,params)
var/os = mouse_action.state
mouse_action.Drag(over_object,over_location,over_control,params)
if(os!=MOUSE_DRAG)
MouseDragStart(mouse_action[1],object,mouse_action[2],location,mouse_action[3],control,mouse_action[4])
..()

MouseDrop(atom/src_object,atom/over_object,atom/src_location,atom/over_location,src_control,over_control,params)
mouse_action.Drop(over_object,over_location,over_control,params)
..()

MouseMove(atom/object,atom/location,atom/control,params)
mouse_action.Move(object,location,control,params)
..()

proc
MouseDragStart(atom/object,atom/location,control,params)
set waitfor = 0
src_object.MouseDragStart(object,location,control,params)

atom
proc
MouseDragStart(atom/object,atom/location,control,params)
set waitfor = 0


Finally, you can interaction with the mouse_action datum by indexing the following keys:

"object"/"start-object"/"src-object" - the object under the mouse where the action started.
"location"/"start-location"/"src-location" - the location the object under the mouse was in when the action started.
"control"/"start-control"/"src-control" - the control the object under the mouse was in when the action started.
"params"/"start-params"/"src-params" - the params of the mouse action when it began.

"over-object"/"end-object" - the most recent object the mouse was over while the action was current.
"over-location"/"end-location" - the most recent over object's loc while the action was current.
"over-control"/"end-control" - the most recent over object's control while the action was current.
"over-params"/"end-params" - the params of the most recent mouse action while the action was current.
And since we love copy-pasta, here's the merger of both systems:

#ifndef TILE_WIDTH
#define TILE_WIDTH 32
#endif

#ifndef TILE_HEIGHT
#define TILE_HEIGHT 32
#endif

#define MOUSE_HOVER 0
#define MOUSE_HOLD 1
#define MOUSE_RELEASE 2
#define MOUSE_DRAG 3
#define MOUSE_DROP 4

#define MOUSE_BUTTON1 1
#define MOUSE_BUTTON2 2
#define MOUSE_BUTTON3 4
#define MOUSE_SHIFT 8
#define MOUSE_CTRL 16
#define MOUSE_ALT 32

#define MOUSE_PARAMS_DIRTY 1
#define MOUSE_VIS_DIRTY 2
#define MOUSE_SCREEN_DIRTY 4
#define MOUSE_ICON_DIRTY 8
#define MOUSE_BUTTONS_DIRTY 16
#define MOUSE_DIRTY 31

mouse_action
var
list/data
state = MOUSE_HOVER

New(status,start_object=null,start_location=null,start_control=null,start_params=null)
data = list(start_object,start_location,start_control,start_params,start_object,start_location,start_control,start_params)
state = status
..()

proc
Move(over_object,over_location,over_control,params)
data[5] = over_object
data[6] = over_location
data[7] = over_control
data[8] = params
state = MOUSE_HOVER

Release()
state = MOUSE_RELEASE

Drag(over_object,over_location,over_control,params)
data[5] = over_object
data[6] = over_location
data[7] = over_control
data[8] = params
state = MOUSE_DRAG

Drop(over_object,over_location,over_control,params)
data[5] = over_object
data[6] = over_location
data[7] = over_control
data[8] = params
state = MOUSE_DROP

proc
operator[](idx)
if(isnum(idx))
return data[idx]
else if(istext(idx))
switch(idx)
if("object","src-object","start-object")
return data[1]
if("location","src-location","start-location")
return data[2]
if("control","src-control","start-control")
return data[3]
if("params","src-params","start-params")
return data[4]
if("over-object","end-object")
return data[5]
if("over-location","end-location")
return data[6]
if("over-control","end-control")
return data[7]
if("over-params","end-params")
return data[8]
if("state")
return state
else
return null

operator[]=(idx,value)
if(isnum(idx))
return data[idx] = value
else if(istext(idx))
switch(idx)
if("object","src-object","start-object")
data[1] = value
if("location","src-location","start-location")
data[2] = value
if("control","src-control","start-control")
data[3] = value
if("params","src-params","start-params")
data[4] = value
if("over-object","end-object")
data[5] = value
if("over-location","end-location")
data[6] = value
if("over-control","end-control")
data[7] = value
if("over-params","end-params")
data[8] = value
if("state")
state = value
return value
else
return null

mouse_params
var
string
params
screen_pos
vis_pos
icon_pos
buttons

dirty_flags

New(str)
string = str
dirty_flags = MOUSE_DIRTY
..()

proc
operator[](idx)
if(dirty_flags&MOUSE_PARAMS_DIRTY)
params = params2list(string)
dirty_flags &= ~MOUSE_PARAMS_DIRTY

switch(idx)
if("params")
return string
if("params-list")
return params
if("screen-loc","left","middle","right","ctrl","shift","alt","drag-cell","drop-cell","drag","link")
return params[idx]
if("screen-pos")
if(dirty_flags&MOUSE_SCREEN_DIRTY)
screen_pos = parse_screen_loc(params["screen-loc"])
dirty_flags &= ~MOUSE_SCREEN_DIRTY
return screen_pos
if("vis-pos","vis-x","vis-y")
if(dirty_flags&MOUSE_VIS_DIRTY)
var/vx = params["vis-x"], vy = params["vis-y"], list/l
if(!vx||!vy)
if(dirty_flags&MOUSE_ICON_DIRTY)
vx = params["icon-x"]; vy = params["icon-y"]
l = list(text2num(vx),text2num(vy))
dirty_flags &= ~MOUSE_ICON_DIRTY
else
l = icon_pos
vis_pos = l||(l = list(text2num(vx),text2num(vy)))
dirty_flags &= ~MOUSE_VIS_DIRTY
switch(idx)
if("vis-pos")
return vis_pos
if("vis-x")
return vis_pos[1]
if("vis-y")
return vis_pos[2]
if("icon-pos","icon-x","icon-y")
if(dirty_flags&MOUSE_ICON_DIRTY)
icon_pos = list(text2num(params["icon-x"]),text2num(params["icon-y"]))
dirty_flags &= ~MOUSE_ICON_DIRTY
switch(idx)
if("icon-pos")
return icon_pos
if("icon-x")
return icon_pos[1]
if("icon-y")
return icon_pos[2]
if("buttons")
if(dirty_flags&MOUSE_BUTTONS_DIRTY)
var/b = 0
if(params["left"])
b += MOUSE_BUTTON1
if(params["middle"])
b += MOUSE_BUTTON3
if(params["right"])
b += MOUSE_BUTTON2
if(params["shift"])
b += MOUSE_SHIFT
if(params["ctrl"])
b += MOUSE_CTRL
if(params["alt"])
b += MOUSE_ALT
buttons = b
dirty_flags &= ~MOUSE_BUTTONS_DIRTY
return buttons
else
return null

operator[]=(idx,value)
switch(idx)
if("params","params-list")
if(istext(value))
string = value
dirty_flags = MOUSE_DIRTY
else
params = value
dirty_flags = MOUSE_DIRTY & ~MOUSE_PARAMS_DIRTY
if("screen-loc")
if(dirty_flags & MOUSE_PARAMS_DIRTY)
params = params2list(string)
dirty_flags &= ~MOUSE_PARAMS_DIRTY
dirty_flags |= MOUSE_SCREEN_DIRTY
params[idx] = value
if("left","middle","right","ctrl","shift","alt")
if(dirty_flags & MOUSE_PARAMS_DIRTY)
params = params2list(params)
dirty_flags &= ~MOUSE_PARAMS_DIRTY
dirty_flags |= MOUSE_BUTTONS_DIRTY
params[idx] = value
if("drag-cell","drop-cell","drag","link")
if(dirty_flags & MOUSE_PARAMS_DIRTY)
params = params2list(params)
dirty_flags &= ~MOUSE_PARAMS_DIRTY
params[idx] = value
if("vis-x","vis-y")
if(dirty_flags & MOUSE_PARAMS_DIRTY)
params = params2list(params)
dirty_flags &= ~MOUSE_PARAMS_DIRTY
dirty_flags |= MOUSE_VIS_DIRTY
params[idx] = "[value]"
if("vis-pos")
if(dirty_flags & MOUSE_PARAMS_DIRTY)
params = params2list(params)
dirty_flags &= ~MOUSE_PARAMS_DIRTY
dirty_flags &= ~MOUSE_VIS_DIRTY
vis_pos = value
params["vis-x"] = "[value[1]]"
params["vis-y"] = "[value[2]]"
if("icon-x","icon-y")
if(dirty_flags & MOUSE_ICON_DIRTY)
params = params2list(params)
dirty_flags &= ~MOUSE_PARAMS_DIRTY
dirty_flags |= MOUSE_ICON_DIRTY
params[idx] = "[value]"
if("icon-pos")
if(dirty_flags & MOUSE_PARAMS_DIRTY)
params = params2list(params)
dirty_flags &= ~MOUSE_PARAMS_DIRTY
dirty_flags &= ~MOUSE_ICON_DIRTY
icon_pos = value
params["icon-x"] = "[value[1]]"
params["icon-y"] = "[value[2]]"
else
return value
return value

proc
params2listx(str)
return params2list(str)

var/__paramstring
#define params2list(x) (istype((__paramstring=(x)),/mouse_params) ? (__paramstring = __paramstring["params-list"]) : params2listx(__paramstring))

var/regex/__rgx_sloc = regex(@"[:,]","g")

proc
parse_screen_loc(sloc)
var/list/l = splittext(sloc,__rgx_sloc)
switch(l.len)
if(5)
return list((text2num(l[2])-1) * TILE_WIDTH + text2num(l[3]),(text2num(l[4])-1) * TILE_HEIGHT + text2num(l[5]),l[1])
if(4)
return list((text2num(l[1])-1) * TILE_WIDTH + text2num(l[2]),(text2num(l[3])-1) * TILE_HEIGHT + text2num(l[4]),null)
else
return list(0,0,null)

parse_size(sz)
var/list/l = splittext(sz,"x")
return list(text2num(l[1]),text2num(l[2]))

parse_pos(pos)
var/list/l = splittext(pos,",")
return list(text2num(l[1]),text2num(l[2]))

client
var
mouse_action/mouse_action = new/mouse_action(MOUSE_HOVER,null,null,null,null)
mouse_params/mouse_params = new/mouse_params("screen-loc=1:0,1:0;icon-x=0;icon-y=0")

proc
MouseDragStart(atom/object,atom/location,control,params)
set waitfor = 0
object.MouseDragStart(location,control,params)

MouseDown(atom/object,atom/location,control,params)
mouse_params = params = new/mouse_params(params)
mouse_action = new/mouse_action(MOUSE_HOLD,object,location,control,params)
..()

MouseUp(atom/object,atom/location,control,params)
mouse_params = params = new/mouse_params(params)
..()
if(mouse_action.state==MOUSE_DRAG)
var/action = mouse_action
if(!control)
MouseDrop(mouse_action[1],object,mouse_action[2],location,mouse_action[3],control,params)
spawn(0)
if(mouse_action==action)
MouseDrop(mouse_action[1],object,mouse_action[2],location,mouse_action[3],control,params)
else
mouse_action.Release()
mouse_action = new/mouse_action(MOUSE_HOVER,null,null,control,params)

MouseDrop(atom/src_object,atom/over_object,atom/src_location,atom/over_location,src_control,over_control,params)
mouse_params = params = new/mouse_params(params)
mouse_action.Drop(over_object,over_location,over_control,params)
..()

MouseDrag(atom/src_object,atom/over_object,atom/src_location,atom/over_location,src_control,over_control,params)
mouse_params = params = new/mouse_params(params)
var/os = mouse_action.state
mouse_action.Drag(over_object,over_location,over_control,params)
if(os!=MOUSE_DRAG)
MouseDragStart(mouse_action[1],mouse_action[2],mouse_action[3],mouse_action[4])
..()

MouseMove(atom/object,atom/location,control,params)
mouse_params = params = new/mouse_params(params)
mouse_action.Move(object,location,control,params)
..()

Click(atom/object,atom/location,control,params)
mouse_params = params = new/mouse_params(params)
..()

DblClick(atom/object,atom/location,control,params)
mouse_params = params = new/mouse_params(params)
..()

MouseWheel(atom/object,delta_x,delta_y,atom/location,control,params)
mouse_params = params = new/mouse_params(params)
..()

MouseEntered(atom/object,atom/location,control,params)
mouse_params = params = new/mouse_params(params)
..()

MouseExited(atom/object,atom/location,control,params)
mouse_params = params = new/mouse_params(params)
..()

atom
proc
MouseDragStart(atom/location,control,params)
set waitfor = 0
As for why I went through all this effort, it's so that I could have an easier time adding things like draggable/resizable HUD controls:

    MouseDragStart(atom/location,control,params)
window.mouse_opacity = 0

//store action-wide variables
var/client/c = usr.client
var/mouse_action/action = c.mouse_action

//calculate the starting point of the drag action
var/list/l = params["screen-pos"]
var/matrix/m = window.transform
var/sx = l[1]-m.c, sy = l[2]-m.f

//calculate the max/min offset that we can drag the window
var/handle_x = params["icon-x"], handle_y = params["icon-y"]
var/minx = -sx + handle_x, miny = -sy + window.height - slice_h + handle_y
var/maxx = c.bound_width - window.width + handle_x - sx, maxy = c.bound_height - slice_h + handle_y - sy

//while the action is current, update the position of the window.
while(c?.mouse_action==action)
l = action["over-params"]["screen-pos"]
m.c = clamp(l[1]-sx,minx,maxx)
m.f = clamp(l[2]-sy,miny,maxy)
window.transform = m
sleep(world.tick_lag)

//when the action has expired, finalize the position of the window.
l = action["end-params"]["screen-pos"]
m.c = clamp(l[1]-sx,minx,maxx)
m.f = clamp(l[2]-sy,miny,maxy)
window.transform = m

window.mouse_opacity = 1


The work I did to overhaul mouse controls makes working with them for ongoing actions a breeze, and minimizes the amount of additional infrastructure you will need to define on a per-object basis that will make doing stuff like this a major hassle.

UIlib is coming...

Good job. Simple and useful.
Amazing share. Thank you. Ton of great stuff in here. That window work is fantastic. Really look forward to UILib because it's something I've been working/wishing on for a long time.
Oh!!!..., that resize clipping is super hot. I really want to know how that's being done.
Hit my discord dms and I can send you my experimental environment, pops.
In response to Ter13
Ter13 wrote:
Hit my discord dms and I can send you my experimental environment, pops.

??? What's your discord name?