Legacy GM Grab only window on top

P

paulog

Guest
Hello! I'm making a game in GMS1.4 with a draggable windows system but I'm facing an issue. When I click in a window that is behind another window, I want it to come to the top, but when I click somewhere where two windows are overlaping, the one at the bottom, if it was created first, comes to the top, instead of the one that is already on top staying on top. I read some tutorials about it but haven't really got the code to work properly.

I'd be really greatful if someone could give me some ideas. Thanks in advance!

this is the code I'm using:

"obj_ui_mov" is the parent object of all windows

GML:
if device_mouse_check_button_pressed(0, mb_left)
{

if position_meeting(device_mouse_x_to_gui(0),device_mouse_y_to_gui(0),id)
{

obj_ui_mov.depth = -5
depth = -10

}
}
 
You'd need to go through all the elements at that position and check them for depth, selecting whichever one has the lowest. I don't think GMS1.4 had instance_position_list (if it does, this becomes a lot simpler), but you can jimmy around that by moving each instance after checking it and then moving them all back to their original positions:
Code:
var _inst = instance_position(mouse_x,mouse_y,object);
var _inst_list = ds_list_create(); // We need this list to keep track of all the instances we move
var _depth = 10000; // 10000 is arbitrary, pick a depth high enough that all your instances are under it
var _current_lowest = noone; // The current lowest instance

while (instance_exists(_inst)) {

   ds_list_add(_inst_list,[_inst,_inst.x]); // Add the id of the instance being checked and it's current x position as an array to our ds_list

   if (_depth > _inst.depth) { // Check if the instance being checked's depth is lower than the current lowest
      _current_lowest = _inst; // If it is, it becomes the new current lowest
      _depth = _inst.depth; // And the depth to check against becomes its depth
   }

   _inst.x += 10000; // Move the instance that was just checked so we can get to instances underneath it

   _inst = instance_position(mouse_x,mouse_y,object); // Check the position again
}

/* Now _current_lowest holds the ID of the instance that is "on top" of everything else
    and we just need to loop back through the list we made to move everything back to
    it's original position */

for (var i=0;i<ds_list_size(_inst_list);i++) {
   _inst_arr = _inst_list[| i];
   _inst = _inst_arr[0];
   _inst.x = _inst_arr[1];
}
ds_list_destroy(_inst_list);
That should work I think, I haven't tested it though.
 

TheouAegis

Member
Use a ds_list to store windows as they are created. Treat list position 0 as the topmost window. When you click the mouse, loop through the list starting with position 0. For each id in the list, check if the mouse is over it. If it is, move the id up to position 0 (GM will move everything else down) and set the depth.

By the way, your current depth sorting is unreliable. Instances at the same depth will be drawn as they are ordered internally, not by what order you clicked on them. You should set depth based on position in the list.
 
P

paulog

Guest
You'd need to go through all the elements at that position and check them for depth, selecting whichever one has the lowest. I don't think GMS1.4 had instance_position_list (if it does, this becomes a lot simpler), but you can jimmy around that by moving each instance after checking it and then moving them all back to their original positions:
Code:
var _inst = instance_position(mouse_x,mouse_y,object);
var _inst_list = ds_list_create(); // We need this list to keep track of all the instances we move
var _depth = 10000; // 10000 is arbitrary, pick a depth high enough that all your instances are under it
var _current_lowest = noone; // The current lowest instance

while (instance_exists(_inst)) {

   ds_list_add(_inst_list,[_inst,_inst.x]); // Add the id of the instance being checked and it's current x position as an array to our ds_list

   if (_depth > _inst.depth) { // Check if the instance being checked's depth is lower than the current lowest
      _current_lowest = _inst; // If it is, it becomes the new current lowest
      _depth = _inst.depth; // And the depth to check against becomes its depth
   }

   _inst.x += 10000; // Move the instance that was just checked so we can get to instances underneath it

   _inst = instance_position(mouse_x,mouse_y,object); // Check the position again
}

/* Now _current_lowest holds the ID of the instance that is "on top" of everything else
    and we just need to loop back through the list we made to move everything back to
    it's original position */

for (var i=0;i<ds_list_size(_inst_list);i++) {
   _inst_arr = _inst_list[| i];
   _inst = _inst_arr[0];
   _inst.x = _inst_arr[1];
}
ds_list_destroy(_inst_list);
That should work I think, I haven't tested it though.
This error happens in the array part

" Error in Object obj_window_control, Event Step, Action 1 at Line 8, Position 28: Unexpected symbol in expression "
 
You could try:
Code:
var _arr;
_arr[0] = _inst;
_arr[1] = _inst.x;
ds_list_add(_inst_list,_arr); // Add the id of the instance being checked and it's current x position as an array to our ds_list
Instead.

However, I misread your original post, I thought you wanted to select whatever is the lowest depth instance, but on re-reading it seems as though you want to change depths between instances when clicked. @TheouAegis has the right solution for that. Add instances to a list (you could even use a priority list if you wanted and use the priority as the depth) as you create them and then manipulate the depth of instances based on their position in the list.
 
P

paulog

Guest
You could try:
Code:
var _arr;
_arr[0] = _inst;
_arr[1] = _inst.x;
ds_list_add(_inst_list,_arr); // Add the id of the instance being checked and it's current x position as an array to our ds_list
Instead.

However, I misread your original post, I thought you wanted to select whatever is the lowest depth instance, but on re-reading it seems as though you want to change depths between instances when clicked. @TheouAegis has the right solution for that. Add instances to a list (you could even use a priority list if you wanted and use the priority as the depth) as you create them and then manipulate the depth of instances based on their position in the list.
I'll give that a try. Thanks!

Use a ds_list to store windows as they are created. Treat list position 0 as the topmost window. When you click the mouse, loop through the list starting with position 0. For each id in the list, check if the mouse is over it. If it is, move the id up to position 0 (GM will move everything else down) and set the depth.

By the way, your current depth sorting is unreliable. Instances at the same depth will be drawn as they are ordered internally, not by what order you clicked on them. You should set depth based on position in the list.
Thanks for the idea! I did some resarch, but I seem to not figure out how to implement this into code. How would I loop through a ds list? Adding values should be something like " ds_list_add(ds_depth, id, depth) "?
 

TheouAegis

Member
Code:
for(var lim = ds_list_size( list ), i=0; i<lim; i++;) {
    with list[|i] {
        if position_meeting(mouse_x, mouse_y, id) {
            ds_list_delete(list, i);
            list[|i] = id;
            i = lim;
        }
    }
}
for(i = 0; i<lim; i++;) {
    list[|i].depth = -10 + i;
}
 
P

paulog

Guest
Code:
for(var lim = ds_list_size( list ), i=0; i<lim; i++;) {
    with list[|i] {
        if position_meeting(mouse_x, mouse_y, id) {
            ds_list_delete(list, i);
            list[|i] = id;
            i = lim;
        }
    }
}
for(i = 0; i<lim; i++;) {
    list[|i].depth = -10 + i;
}
Besides this code, do I have to create the ds list in the create event? I tried that and nothing happened.
(I also changed the mouse_x and mouse_y to device_mouse_x_to_gui(0) and device_mouse_y_to_gui(0) since it's been drawn in the gui layer)
 

TheouAegis

Member
Your create event would need
Code:
list = ds_list_create();

If you want the objects to be drawn in the GUI and use the mouse_to_gui conversion, then you won't be using position_meeting(), since that handles room coordinates. You are basically saying, "If the coordinates in the GUI are the same as the coordinates in the room," which is obviously not correct. Out of curiosity, what is your code for dragging the windows around?
 
P

paulog

Guest
Your create event would need
Code:
list = ds_list_create();

If you want the objects to be drawn in the GUI and use the mouse_to_gui conversion, then you won't be using position_meeting(), since that handles room coordinates. You are basically saying, "If the coordinates in the GUI are the same as the coordinates in the room," which is obviously not correct. Out of curiosity, what is your code for dragging the windows around?
Then what should I use instead of position meeting?

The coding for dragging is the following:

GML:
if device_mouse_check_button_pressed(0, mb_left)
{
if ( point_in_rectangle ( device_mouse_x_to_gui(0) , device_mouse_y_to_gui(0) , x , y , x+sprite_get_width(sprite_index) , y+16 ) )
{
dx=device_mouse_x_to_gui(0)-x
dy=device_mouse_y_to_gui(0)-y
windrag = 1
}
}

if device_mouse_check_button_released(0, mb_left)
{
windrag = 0
}

if windrag = 1
{
x=device_mouse_x_to_gui(0)-dx
y=device_mouse_y_to_gui(0)-dy
}
I then draw a rectangle in those coordinates.
 

TheouAegis

Member
position_meeting() might work fine as long as the windows are technically within the bounds of the room as defined by the size of your GUI. So if the GUI is 1024x768, for example, you wouldn't want the windows to ever have any coordinates greater than 1024 horizontally or 768 vertically. If you have moving views, you're better off just snapping things relative to the views instead of using the GUI layer. But the GUI layer by default has some odd scaling, so unless you call device_gui_set_maximise() at the start of the program, you may get some coordinate mismatching toward the bottom-right edges of the screen.

Code:
if device_mouse_check_button_pressed(0,mb_left) {
    for(var lim = ds_list_size( list ), i=0; i<lim; i++;) {
        with list[|i] {
            if position_meeting(device_mouse_x_to_gui(0), device_mouse_y_to_gui(0), id) {
                ds_list_delete(list, i);
                list[|i] = id;
                i = lim;
                if clamp(device_mouse_y_to_gui(0), 0, y+16) == device_mouse_y_to_gui(0) {
                    other.windrag = id;
                    other.dx = device_mouse_x_to_gui(0) - x;
                    other.dy = device_mouse_y_to_gui(0) - y;
                }
            }
        }
    }
    for(i = 0; i<lim; i++;) {
        list[|i].depth = -10 + i;
}
else
if device_mouse_check_button_released(0,mb_left)
    windrag = noone;
else
if device_mouse_check_button(0,mb_left) {
    with windrag {
        x = device_mouse_x_to_gui(0) - other.dx;
        y = device_mouse_y_to_gui(0) - other.dy;
    }
}
 
P

paulog

Guest
position_meeting() might work fine as long as the windows are technically within the bounds of the room as defined by the size of your GUI. So if the GUI is 1024x768, for example, you wouldn't want the windows to ever have any coordinates greater than 1024 horizontally or 768 vertically. If you have moving views, you're better off just snapping things relative to the views instead of using the GUI layer. But the GUI layer by default has some odd scaling, so unless you call device_gui_set_maximise() at the start of the program, you may get some coordinate mismatching toward the bottom-right edges of the screen.

Code:
if device_mouse_check_button_pressed(0,mb_left) {
    for(var lim = ds_list_size( list ), i=0; i<lim; i++;) {
        with list[|i] {
            if position_meeting(device_mouse_x_to_gui(0), device_mouse_y_to_gui(0), id) {
                ds_list_delete(list, i);
                list[|i] = id;
                i = lim;
                if clamp(device_mouse_y_to_gui(0), 0, y+16) == device_mouse_y_to_gui(0) {
                    other.windrag = id;
                    other.dx = device_mouse_x_to_gui(0) - x;
                    other.dy = device_mouse_y_to_gui(0) - y;
                }
            }
        }
    }
    for(i = 0; i<lim; i++;) {
        list[|i].depth = -10 + i;
}
else
if device_mouse_check_button_released(0,mb_left)
    windrag = noone;
else
if device_mouse_check_button(0,mb_left) {
    with windrag {
        x = device_mouse_x_to_gui(0) - other.dx;
        y = device_mouse_y_to_gui(0) - other.dy;
    }
}
I tried this code, but it gives this messege

" Error in Object, Event End Step, Action 1 at Line 21, Position 2: Unexpected symbol in expression. "
 

Nidoking

Member
Does the list accessor ([|i]) exist in your version of Game Maker? You may need to use list accessor functions instead.

Oh, and there's an extra semicolon at the end of the for loop there. I expect that's breaking things.
 

TheouAegis

Member
Code:
if device_mouse_check_button_pressed(0,mb_left) {
    for(var lim = ds_list_size( list ), i=0; i<lim; i++;) {
        with list[|i] {
            if position_meeting(device_mouse_x_to_gui(0), device_mouse_y_to_gui(0), id) {
                ds_list_delete(list, i);
                list[|i] = id;
                i = lim;
                if clamp(device_mouse_y_to_gui(0), 0, y+16) == device_mouse_y_to_gui(0) {
                    other.windrag = id;
                    other.dx = device_mouse_x_to_gui(0) - x;
                    other.dy = device_mouse_y_to_gui(0) - y;
                }
            }
        }
    }
    for(i = 0; i<lim; i++;) 
        list[|i].depth = -10 + i;
}
else
if device_mouse_check_button_released(0,mb_left)
    windrag = noone;
else
if device_mouse_check_button(0,mb_left) {
    with windrag {
        x = device_mouse_x_to_gui(0) - other.dx;
        y = device_mouse_y_to_gui(0) - other.dy;
    }
}
Edit: I deleted a bracket on line 16.
I don't think I pressed the TAB key at any time during my code, but try deleting the any empty space at the start of line 21.
 
Top