Legacy GM How to make draggable window

paulog

Member
Hello! I'm making a game in GM1.4 and I want to make a draggable window but I'm facing some issues.

The draggable part I have figured out, but there are two problems:

1 - The game uses a view smaller than the room, and the player can drag the view across the room. I want the window to stay in the same place in relation to the view.

2 - The game allows the player to rotate the view by 90º at a time, but I also wanted the window to stay in the same place in relation to the view after this happens.

Is there a way to make this possible? I thought about making the window appear fixed in the middle of the view, but I really wanted to make it draggable. Besides I want to be able to open multiple windows at a time. I also thought about using a different view, like a GUI view, and rendering it at the top of the main game view, but then I can't interact with the game view. Is there a way to make a view "transparent" so I can interact with the view "below" it?

I really hope someone can give me some ideas. Thanks in advance!
 
You don't need to "render" the GUI for it to be displayed. It is automatically displayed. And it doesn't stop interaction with anything. The GUI is precisely the tool you need for what you are describing.
 

paulog

Member
You don't need to "render" the GUI for it to be displayed. It is automatically displayed. And it doesn't stop interaction with anything. The GUI is precisely the tool you need for what you are describing.
I tried using the Draw GUI event but it creates two sprites in the room (I think one is the Draw GUI and the other is just the Draw). The Draw acts the way I want to, but the Draw GUI moves faster than the mouse in the y axis (I think because my view is streched out).

And by using this my previous code does not work anymore.

I had in the step event:
GML:
dx = mouse_x - x
dy = mouse_y - y
and in the left mouse button event:
GML:
x=mouse_x - dx
y=mouse_y - dy
when I put the second code in the Draw GUI nothing happens, only if I change it to just "mouse_x and mouse_y", but then it gets the origin of the sprite, not the point the mouse clicked. And also there's the two sprites issue
 
You do not use mouse_x or mouse_y to get the GUI coordinates for the mouse. The usual method is device_mouse_x_to_gui(0) and device_mouse_y_to_gui(0). This is because the GUI layer does not move with your views. There's no need to subtract coordinates from the mouse or anything. If your room is 1000x1000 and your view is 500x500 and your view is centered on your room (meaning the top left of your view is at coordinates x:250,y:250), the top left of the GUI layer will still be at x:0,y:0 (but the GUI will still be overlaid perfectly on your screen). Read more about the GUI layer here (EDIT: sorry, linked the wrong link here, I've fixed it now).
 
Last edited:

paulog

Member
You do not use mouse_x or mouse_y to get the GUI coordinates for the mouse. The usual method is device_mouse_x_to_gui(0) and device_mouse_y_to_gui(0). This is because the GUI layer does not move with your views. There's no need to subtract coordinates from the mouse or anything. If your room is 1000x1000 and your view is 500x500 and your view is centered on your room (meaning the top left of your view is at coordinates x:250,y:250), the top left of the GUI layer will still be at x:0,y:0 (but the GUI will still be overlaid perfectly on your screen). Read more about the GUI layer here (EDIT: sorry, linked the wrong link here, I've fixed it now).
That does help a lot, thanks!

Still I can't figure out how to move the window relative to the mouse position, just for it to jump when I click. I looked up some tutorials but they all have the same code that I posted earlier, that does not seem to work with draw GUI event.

Also, how do I code so that the mouse click only registers when above the object when its been drawn in the GUI? I tried "if point_in_rectangle" but that did not work.
 
You'll need to subtract the view coordinates from the instance coordinates to get the GUI positioning, something along the lines of:
Code:
// Get dx
dx = (x-view_xview[0])-device_mouse_x_to_gui(0);
dy = (y-view_yview[0])-device_mouse_y_to_gui(0);

// Set position
x=(device_mouse_x_to_gui(0)-dx)+view_xview[0];
y=(device_mouse_y_to_gui(0)-dy)+view_yview[0];
The same is probably true for your point_in_rectangle. If your sprite origin is top left, then this would be the way to do it for the gui:
Code:
var mgx = device_mouse_x_to_gui(0);
var mgy = device_mouse_y_to_gui(0);
var x1 = x-view_xview[0];
var y1 = y-view_yview[0];
var x2 = x1+sprite_width;
var y2 = y1+sprite_height;
if (point_in_rectangle(mgx,mgy,x1,y1,x2,y2)) {
   // Code and stuff
}
Pretty sure all that should work, though it's kind of hard to tell what you're doing. If you're working with a popup interface, it shouldn't be positioning itself according to the room, it should be positioned according to where the GUI would be. So if you want something in the center of your screen, and your gui width and height is w:500,h:500, then you'd place it at coordinates x:250,y:250in the room. Having stuff that's "outside" of the area the GUI should cover and then trying to draw it onto the GUI is just giving yourself an unnecessary headache.
 

paulog

Member
You'll need to subtract the view coordinates from the instance coordinates to get the GUI positioning, something along the lines of:
Code:
// Get dx
dx = (x-view_xview[0])-device_mouse_x_to_gui(0);
dy = (y-view_yview[0])-device_mouse_y_to_gui(0);

// Set position
x=(device_mouse_x_to_gui(0)-dx)+view_xview[0];
y=(device_mouse_y_to_gui(0)-dy)+view_yview[0];
The same is probably true for your point_in_rectangle. If your sprite origin is top left, then this would be the way to do it for the gui:
Code:
var mgx = device_mouse_x_to_gui(0);
var mgy = device_mouse_y_to_gui(0);
var x1 = x-view_xview[0];
var y1 = y-view_yview[0];
var x2 = x1+sprite_width;
var y2 = y1+sprite_height;
if (point_in_rectangle(mgx,mgy,x1,y1,x2,y2)) {
   // Code and stuff
}
Pretty sure all that should work, though it's kind of hard to tell what you're doing. If you're working with a popup interface, it shouldn't be positioning itself according to the room, it should be positioned according to where the GUI would be. So if you want something in the center of your screen, and your gui width and height is w:500,h:500, then you'd place it at coordinates x:250,y:250in the room. Having stuff that's "outside" of the area the GUI should cover and then trying to draw it onto the GUI is just giving yourself an unnecessary headache.
If I use this code the object keeps jumping from one side of the screen to the other. I don't know what I'm doing wrong :/

Yeah I'm trying to make a popup interface
 
Can you please post the code you are actually using to create the popup interface? I can show you how to alter it so that it will work with the GUI layer properly.
 

paulog

Member
Can you please post the code you are actually using to create the popup interface? I can show you how to alter it so that it will work with the GUI layer properly.
Sure!

This in step event:
GML:
dx = (x-view_xview[0])-device_mouse_x_to_gui(0);
dy = (y-view_yview[0])-device_mouse_y_to_gui(0);

var mgx = device_mouse_x_to_gui(0);
var mgy = device_mouse_y_to_gui(0);
var x1 = x-view_xview[0];
var y1 = y-view_yview[0];
var x2 = x1+sprite_width;
var y2 = y1+sprite_height;

if (point_in_rectangle(mgx,mgy,x1,y1,x2,y2))
{

if (mouse_check_button_pressed(mb_left))
{

x=(device_mouse_x_to_gui(0)-dx)+view_xview[0];
y=(device_mouse_y_to_gui(0)-dy)+view_yview[0];
}
}
And I'm using "draw_self()" in the Draw GUI event and a blank code in the Draw event
 
Ok, so the only other thing I'll need is the code you are using to create that instance, that is going to be where the fix is really needed.

I'll try to give you a better explanation of how to use the GUI layer. Firstly, we really shouldn't be messing around with view positions when we're dealing with the GUI. Initially, I wasn't entirely sure what you were trying to do, so I gave you a brief explanation and some raw code that could be adapted. But, in general, you use GUI coords for GUI stuff and view coords for room stuff and never the twain shall meet.

So, the GUI layer is a screen that is super-imposed on top of the game window. You can find out how large your GUI is by using the display_get_gui_width() and display_get_gui_height() commands. Everything you want drawn on the GUI should be created and drawn between x:0,y:0 and the width and height of your GUI. This means if your gui is w:1920,h:1080 and you want something in the center of it, you can simply do this:
Code:
instance_create(display_get_gui_width()/2,display_get_gui_height()/2,object); // display_get_gui_width()/2 = 960 if you GUI is w:1920
You then can simply put:

Draw GUI Event:
Code:
draw_self();
And it will draw itself in the middle of the screen. It doesn't matter if that instance is being created by another instance that's at x:5000,y:3000 in the room, it'll still be drawn at the center of the screen. Always remember the GUI itself never moves and (generally) doesn't change size. So if you want something to show on it, you create it between x:0,y:0 and x:gui_width,y:gui_height.

If you wanted something on the GUI layer to popup above an instance in your room (say a speech bubble above a character), you would use this code to create the popup:
Code:
instance_create(x-view_xview[0],y-view_yview[0],popup);
Draw GUI Event:
Code:
draw_self();
That'll take the position on the screen your character is at (x,y) and subtract the view coords from it, which will end up somewhere between x:0,y:0 and the width and height of your view (which is defaulted to the width and height of the GUI layer, unless you have specifically changed scaling).

All of this might seem obtuse, perhaps, but getting a handle on the GUI layer makes dealing with "interfaces" and things like that much easier. It also allows you to have high-res UI elements overlaid on top of low-res but scaled room elements. It'll allow you to add things in like UI scaling that is completely separate from the actual scale of the game. And learning how to use it is just another necessary step in mastering GMS in the long run.
 

paulog

Member
Ok, so the only other thing I'll need is the code you are using to create that instance, that is going to be where the fix is really needed.

I'll try to give you a better explanation of how to use the GUI layer. Firstly, we really shouldn't be messing around with view positions when we're dealing with the GUI. Initially, I wasn't entirely sure what you were trying to do, so I gave you a brief explanation and some raw code that could be adapted. But, in general, you use GUI coords for GUI stuff and view coords for room stuff and never the twain shall meet.

So, the GUI layer is a screen that is super-imposed on top of the game window. You can find out how large your GUI is by using the display_get_gui_width() and display_get_gui_height() commands. Everything you want drawn on the GUI should be created and drawn between x:0,y:0 and the width and height of your GUI. This means if your gui is w:1920,h:1080 and you want something in the center of it, you can simply do this:
Code:
instance_create(display_get_gui_width()/2,display_get_gui_height()/2,object); // display_get_gui_width()/2 = 960 if you GUI is w:1920
You then can simply put:

Draw GUI Event:
Code:
draw_self();
And it will draw itself in the middle of the screen. It doesn't matter if that instance is being created by another instance that's at x:5000,y:3000 in the room, it'll still be drawn at the center of the screen. Always remember the GUI itself never moves and (generally) doesn't change size. So if you want something to show on it, you create it between x:0,y:0 and x:gui_width,y:gui_height.

If you wanted something on the GUI layer to popup above an instance in your room (say a speech bubble above a character), you would use this code to create the popup:
Code:
instance_create(x-view_xview[0],y-view_yview[0],popup);
Draw GUI Event:
Code:
draw_self();
That'll take the position on the screen your character is at (x,y) and subtract the view coords from it, which will end up somewhere between x:0,y:0 and the width and height of your view (which is defaulted to the width and height of the GUI layer, unless you have specifically changed scaling).

All of this might seem obtuse, perhaps, but getting a handle on the GUI layer makes dealing with "interfaces" and things like that much easier. It also allows you to have high-res UI elements overlaid on top of low-res but scaled room elements. It'll allow you to add things in like UI scaling that is completely separate from the actual scale of the game. And learning how to use it is just another necessary step in mastering GMS in the long run.
That is trully helpful, thank you so much for the explanation.

I did not realize that the popup window object needed to have a code for its creation, I was just placing the object in the room with the room editor. I was planning to have a button that created the popup window, but I thought I could code that later, after I had the window moving figured out.
 
You can place it with the room editor, it just still has to be where the GUI layer "would" be (so between x:0,y:0 and however big your GUI layer is, possibly x:1920,y:1080).
 
Here's an example with my level editor:

You can see that even though my room is "bigger" than my UI would be, I still place all the UI elements in the top left corner where they should be. Just imagine that when you first load into the room, your view will be at x:0,y:0 and that initial "view" of the room you would have is where you need to place all your UI objects. And here's my code that is in the Step Event for my draggable UI objects:
Code:
var mx = device_mouse_x_to_gui(0);
var my = device_mouse_y_to_gui(0);

#region drag
if (can_drag && active) {
    if (point_in_rectangle(mx,my,x,y,x+sprite_width,y+drag_grab_height)) {
        if (lclick_press) {
            drag = true;
            drag_xoffset = mx-x;
            drag_yoffset = my-y;
        }
    }
    if (drag) {
        global.can_click = false;
        if (lclick_release) {
            drag = false;
            lclick_release = false;
            global.can_click = true;
        }
        x = mx-drag_xoffset;
        y = my-drag_yoffset;
        x = clamp(x,0,1920-sprite_width);
        y = clamp(y,sprite_get_height(spr_top_bar_bg),1080-sprite_height);
    }
}
#endregion
 

paulog

Member
Here's an example with my level editor:

You can see that even though my room is "bigger" than my UI would be, I still place all the UI elements in the top left corner where they should be. Just imagine that when you first load into the room, your view will be at x:0,y:0 and that initial "view" of the room you would have is where you need to place all your UI objects. And here's my code that is in the Step Event for my draggable UI objects:
Code:
var mx = device_mouse_x_to_gui(0);
var my = device_mouse_y_to_gui(0);

#region drag
if (can_drag && active) {
    if (point_in_rectangle(mx,my,x,y,x+sprite_width,y+drag_grab_height)) {
        if (lclick_press) {
            drag = true;
            drag_xoffset = mx-x;
            drag_yoffset = my-y;
        }
    }
    if (drag) {
        global.can_click = false;
        if (lclick_release) {
            drag = false;
            lclick_release = false;
            global.can_click = true;
        }
        x = mx-drag_xoffset;
        y = my-drag_yoffset;
        x = clamp(x,0,1920-sprite_width);
        y = clamp(y,sprite_get_height(spr_top_bar_bg),1080-sprite_height);
    }
}
#endregion
I'm a bit confused as when some of those variables have to be created. I did some reserch and it seems maybe some of this code only works in GM2.0 and not in GM1.4, but I'm note sure.
 

paulog

Member
Ah yeah, that does nothing to the code, it's just a visual thing for yourself as the coder.
I messed with the code a little and it took me some time, but it's finally working! Here's what I ended up using:
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), self))
{
dx=device_mouse_x_to_gui(0)-x
dy=device_mouse_y_to_gui(0)-y
follow=1
}
}

if device_mouse_check_button_released(0, mb_left)
{
if (position_meeting(device_mouse_x_to_gui(0), device_mouse_y_to_gui(0), self))
{
follow=0
}
}

if follow= 1
{
x=device_mouse_x_to_gui(0)-dx
y=device_mouse_y_to_gui(0)-dy
}
Thanks so much for your time and patience! Everything you said was really helpful.

I have a completely unrelated question about mouse movement that I can't find anywhere, but I'm going to create another thread for it.
 
Top