Cannot access mouse coords relative to CANVAS widget - linux

I am working with SBCL for Linux on an AMD64 machine.
Function ANIMTEST instantiates an LTK window with a CANVAS widget. Two items, BARRIER and FOLLOWER, live in the canvas. Both spin continuously, with BARRIER at the center of the canvas and FOLLOWER intended to follow the mouse, which is not working as intended. My first attempt (see comment) resulted in absolute screen coordinates of the mouse being interpreted as relative coordinates within the canvas with no account of the offset between the two. After searching through ltk.lisp and docs, I found SCREEN-MOUSE-X/Y (Second Attempt, see comment). I feel like I am using SCREEN-MOUSE-X & -Y according to the documentation, but why is it not working?
= Note =
The file that contains ANIMTEST and the packages that support it load and run with no errors.
Functions I have defined (UCTK-BEAM, etc.) are tested and run fine.
(defun animtest ()
"Test a spinning figure in LTK"
(with-ltk ()
(let* ((cnvs (make-instance 'canvas :width 400 :height 400))
(barrier (uctk-beam 200 200 40 20))
(follower (uctk-beam 0 40 40 20))
(slp-time 50) ; in ms
(bar-theta 0)
(fol-theta 0))
(labels ((update ()
(draw barrier nil)
(draw follower nil)
(incf bar-theta (/ pi 15))
(incf fol-theta (/ pi 15))
(geo:set-theta barrier bar-theta)
(geo:set-theta follower fol-theta)
(geo:set-center follower
;== FIRST ATTEMPT ==
(cons (screen-mouse-x cnvs)
(screen-mouse-y cnvs)))
; == SECOND ATTEMPT ==
;(cons (canvasx cnvs (screen-mouse-x cnvs))
; (canvasy cnvs (screen-mouse-y cnvs))))
(after slp-time #'update)))
(pack cnvs :fill :both :expand 1)
(update)))))
Thanks in advance!

To grab the mouse position in a canvas widget, I don't call the
screen-mouse functions, but rather bind the motion and button press
events. The callback gets passed the event structure which contains
the slots event-x and event-y which are canvas coordinates. Not only
are you getting the right values directly this way, but also it is
more efficient, as you don't have to poll the mouse position - you get
updates automatically when it changes. In your case you could either
choose to update the barrier on mouse-move or alternatively, just
store the mouse coordinates in a variable you read inside your update
loop.

Although it still appears that the CANVASX/Y functions do not work as intended, LTK offers WINDOW-X/-Y to return the X and Y screen coordinates of a widget such that you can write the following to achieve the desired effect:
(cons (- (screen-mouse-x) (window-x cnvs))
(- (screen-mouse-y) (window-y cnvs)))
This assumes that the mouse cursor is on the same screen as the canvas widget named CNVS.

Related

Why does rock image does not move alongside the mouse when in motion although I called the node and got its position-Godot Game Engine

I am trying to get my rock.png to move to the side(+x axis) when my mouse is moving. You can see that _target is the position of the event and the event is the mouse moving.
I then got the node image and set it to a variable.
In the else statement I made the rock.position the position of the _target and gave it somee space away from the _target
I want this to move because my camera moves and I want it to move with the flow of the camera
``
extends Camera2D
const zone = 10
onready var rock = get_node("rocks_png_1")
func _input(event: InputEvent) -> void:
if event is InputEventMouseMotion:
var _target = event.position
if _target.length() < zone:
self.position = Vector2(640,350)
else:
self.position = _target.normalized() * (_target.length() - zone) * 0.5
rock.position = Vector2(int(_target)+40, int(_target)+20)
``
From the code I used above I get this error
Invalid get Index 'position' (on base: 'TextureRact')
I tried just using the same code as I used in my self.position for the camera that made it move, but when I try it for the rock.position it gives me an error that tells me I need a Vector?
Invalid get Index 'position' (on base: 'TextureRact')
The error is telling you that TextureRect does not have a position property.
What happens is that a TextureRect is a Control which are positioned differently from Node2D (Camera2D is a Node2D). You can try using rect_position with it. Although I think you would be better off using a Sprite instead of a TextureRect (Sprite is a Node2D).
Notice that your TextureRect is a child of the Camera2D in the scene tree. Well, a Node2D would move with its parent automatically. So changing it to Sprite would negate the need of the line of code you are using to move it.
… Actually, due to the way Camera2D works (it has drag and limits) you might still want do something (depending what your goal is). So, know that the get_camera_screen_center method will give you the actual visual center of the screen (which is not the target of the Camera2D, again due to drag and limits).

Different wallpaper for each monitor in a multi-monitor setup in Windows 10

There are a number of questions and answers about setting wallpapers programmatically on multi-monitor setups in Windows, but I'm asking specifically for Windows 10 (and maybe Windows 8) because it seems to work differently from all the explanations I found.
Raymond Chen has an article "How do I put a different wallpaper on each monitor?" (https://devblogs.microsoft.com/oldnewthing/?p=25003), also quoted in Monitors position on Windows wallpaper. The core concepts is that Windows places the top-left corner of the provided bitmap at the top-left corner of the primary monitor, and wraps around to fill any desktop space to the left and/or above that. I understand that, I wrote a little program using that knowledge, and it works beautifully in Windows 7.
How it works: I create a bitmap that conceptually covers the whole desktop space, as the user sees it. I draw the contents of each monitor to that bitmap in its appropriate position (the program is written in C++ using VCL, but the principle remains the same in other programming environments):
TRect GetMonitorRect_WallpaperCoords(int MonitorNum)
{
Forms::TMonitor *PrimaryMonitor = Screen->Monitors[0];
Forms::TMonitor *Monitor = Screen->Monitors[MonitorNum];
// Get the rectangle in desktop coordinates
TRect Rect(Monitor->Left, Monitor->Top, Monitor->Left + Monitor->Width, Monitor->Top + Monitor->Height);
// Convert to wallpaper coordinates
Rect.Left += PrimaryMonitor->Left - Screen->DesktopLeft;
Rect.Top += PrimaryMonitor->Top - Screen->DesktopTop;
Rect.Right += PrimaryMonitor->Left - Screen->DesktopLeft;
Rect.Bottom += PrimaryMonitor->Top - Screen->DesktopTop;
return Rect;
}
std::unique_ptr<Graphics::TBitmap> CreateWallpaperBitmap_WallpaperCoords()
{
std::unique_ptr<Graphics::TBitmap> Bmp(new Graphics::TBitmap);
Bmp->PixelFormat = pf24bit;
Bmp->Width = Screen->DesktopWidth;
Bmp->Height = Screen->DesktopHeight;
// Draw background (not that we really need it: it will never be visible)
Bmp->Canvas->Brush->Style = bsSolid;
Bmp->Canvas->Brush->Color = clBlack;
Bmp->Canvas->FillRect(TRect(0, 0, Bmp->Width, Bmp->Height));
for (int MonitorNum = 0; MonitorNum < Screen->MonitorCount; ++MonitorNum)
{
TDrawContext DC(Bmp->Canvas, GetMonitorRect_WallpaperCoords(MonitorNum));
DrawMonitor(DC);
}
return Bmp;
}
(The draw context uses a coordinate translation rect so that the code int DrawMonitor function can draw in a rectangle like (0, 0, 1920, 1080) without having to wonder where in the full bitmap it is drawing, and with a clip rect so that DrawMonitor can not accidentally draw outside of the monitor it's drawing on).
Then I convert that bitmap to an image that will properly wrap around when placed at the top-left corner of the primary monitor (as Raymond Chen describes in his article):
std::unique_ptr<Graphics::TBitmap> ConvertWallpaperToDesktopCoords(std::unique_ptr<Graphics::TBitmap> &Bmp_WallpaperCoords)
{
std::unique_ptr<Graphics::TBitmap> Bmp_DesktopCoords(new Graphics::TBitmap);
Bmp_DesktopCoords->PixelFormat = Bmp_WallpaperCoords->PixelFormat;
Bmp_DesktopCoords->Width = Bmp_WallpaperCoords->Width;
Bmp_DesktopCoords->Height = Bmp_WallpaperCoords->Height;
// Draw Bmp_WallpaperCoords to Bmp_DesktopCoords at four different places to account for all
// possible ways Windows wraps the wallpaper around the left and bottom edges of the desktop
// space
Bmp_DesktopCoords->Canvas->Draw(Screen->DesktopLeft, Screen->DesktopTop, Bmp_WallpaperCoords.get());
Bmp_DesktopCoords->Canvas->Draw(Screen->DesktopLeft + Screen->DesktopWidth, Screen->DesktopTop, Bmp_WallpaperCoords.get());
Bmp_DesktopCoords->Canvas->Draw(Screen->DesktopLeft, Screen->DesktopTop + Screen->DesktopHeight, Bmp_WallpaperCoords.get());
Bmp_DesktopCoords->Canvas->Draw(Screen->DesktopLeft + Screen->DesktopWidth, Screen->DesktopTop + Screen->DesktopHeight, Bmp_WallpaperCoords.get());
return Bmp_DesktopCoords;
}
Then I install that bitmap as a wallpaper by writing the appropriate values in the registry and calling SystemParametersInfo with SPI_SETDESKWALLPAPER:
void InstallWallpaper(const String &Fn)
{
// Install wallpaper:
// There are 3 name/data pairs that have an effect on the desktop wallpaper, all under HKCU\Control Panel\Desktop:
// - Wallpaper (REG_SZ): file path and name of wallpaper
// - WallpaperStyle (REG_SZ):
// . 0: Centered
// . 1: Tiled
// . 2: Stretched
// - TileWallpaper (REG_SZ):
// . 0: Don't tile
// . 1: Tile
// We don't use the Wallpaper value itself; instead we use SystemParametersInfo to set the wallpaper.
// The file name needs to be absolute!
assert(Ioutils::TPath::IsPathRooted(Fn));
std::unique_ptr<TRegistry> Reg(new TRegistry);
Reg->RootKey = HKEY_CURRENT_USER;
if (Reg->OpenKey(L"Control Panel\\Desktop", false))
{
Reg->WriteString(L"WallpaperStyle", L"1");
Reg->WriteString(L"TileWallpaper", L"1");
Reg->CloseKey();
}
SystemParametersInfoW(SPI_SETDESKWALLPAPER, 1, Fn.c_str(), SPIF_UPDATEINIFILE | SPIF_SENDCHANGE);
}
But when I test it in Windows 10, it doesn't work properly anymore: Windows 10 puts the wallpaper completely in the wrong place. Seeing as other people have asked questions about multi-monitor wallpapers in the past, I'm hoping there are people with experience of it on Windows 10.
As far as I can see, Windows 10 places the top-left corner of the provided bitmap at the top-left corner of the desktop space (by which I mean the bounding rectangle of all monitors), instead of the top-left corner of the primary monitor. In code, that means: I leave out the ConvertWallpaperToDesktopCoords step, and then it works fine as far as I can see.
But I can't find any documentation on this, so I don't know if this is officially explanation of how Windows 10 does it. Use with care. Also I don't know when this different behavior started: in Windows 10, or maybe earlier in Windows 8.

navigation tilemaps without placing walkable tiles manually

I have a tilemap in godot, but all tiles are obstacles and provide collision. Only cells without any tile are walkable. I'm now trying to add navigation via Navigation2D node. As far as I see there is no way to tell it "everything is walkable, but not where these tiles are" (all one can say is "this part of the tile is walkable", but in my current setup there is no tile at the walkable space).
As a workaround I tried to set every cell with no tile to a "dummy tile" which is fully walkable with the following code:
func _ready():
for x in size.x:
for y in size.y:
var cell = get_cell(x, y)
if cell == -1:
set_cell(x, y, WALKABLE)
But the Navigation2D node does not recognize these tiles. If I place the WALKABLE tile manually, everything works as expected.
I think I might be hitting this issue, and need to call update_dirty_quadrants() but this does not fix the problem.
I tried this with the versions 3.0.6stable, 3.1Alpha2 and a recent commit 9a8569d (provided by godotwild), and the result was always the same.
Is there anyway to get navigation with tilemaps working, without placing every tile beforehand manually?
For anyone coming across this in the future, the 'dummy navigation tile' solution works now (using Godot 3.2.3). Put this script on the tilemap in question:
extends TileMap
# Empty/invisible tile marked as completely walkable. The ID of the tile should correspond
# to the order in which it was created in the tileset editor.
export(int) var _nav_tile_id := 0
func _ready() -> void:
# Find the bounds of the tilemap (there is no 'size' property available)
var bounds_min := Vector2.ZERO
var bounds_max := Vector2.ZERO
for pos in get_used_cells():
if pos.x < bounds_min.x:
bounds_min.x = int(pos.x)
elif pos.x > bounds_max.x:
bounds_max.x = int(pos.x)
if pos.y < bounds_min.y:
bounds_min.y = int(pos.y)
elif pos.y > bounds_max.y:
bounds_max.y = int(pos.y)
# Replace all empty tiles with the provided navigation tile
for x in range(bounds_min.x, bounds_max.x):
for y in range(bounds_min.y, bounds_max.y):
if get_cell(x, y) == -1:
set_cell(x, y, _nav_tile_id)
# Force the navigation mesh to update immediately
update_dirty_quadrants()
I couldn't find the ID from my tile in the autotiler (when checking in the editor, having the tilemap selected, these all show the same imagename.png 0). So instead of using the ID, I used the coordinates of the walkable blank tile in the Tileset. The code remains the same as LukeZaz', but with set_cell replaced by:
set_cell(x, y, 0, false, false, false, Vector2(7,4)); where Vector2(7,4) is the coordinate of the blank tile in my tileset that has the navigationpolygon on it. (Remember that coördinates start at 0)
(Godot 3.2.3.stable)

Why isn't my path2d position updating?

I created a new path2d following the instructions in the my first game article: http://docs.godotengine.org/en/3.0/getting_started/step_by_step/your_first_game.html
I wanted to move the "box" on screen so that I could see how the mobs spawn in, but when I ran the scene, it stayed off screen.
I created a new path2d, centered this one in the middle of the screen, and it works like I wanted it to, but now I moving this one in the editor doesn't update the position in game.
What's going on?
Thanks
func _on_mobtimer_timeout():
$mobtimer.wait_time = 0.1 + randf() / 2
$mobspawn/moblocation.set_offset(randi())
var mob = Mob.instance()
add_child(mob)
var direction = $mobspawn/moblocation.rotation + PI/2
mob.position = $mobspawn/moblocation.position
direction += rand_range(-PI/8, PI/8)
mob.rotation = direction
mob.set_linear_velocity(Vector2(rand_range(200, 200 + score * 30), 0).rotated(direction))
A Node2D's position property is relative to it's parent's position. The code from the Dodge The Creeps tutorial assumes that MobPath is located at 0, 0 and fails when that assumption is false.
In your case you are taking a MobSpawnLocation's position relative to MobPath and then setting it as the new Mob's global position.
Luckily Node2D's have another property that we can use in these circumstances global_position. It can be used like this:
mob.position = $mobspawn/moblocation.global_position
http://docs.godotengine.org/en/stable/classes/class_node2d.html#member-variables
This isn't a full solution, but I found a weird workaround. Instead of changing the position in the editor, if you use the nodes on the orange box (at the intersection of orange and blue), you can kind of alternate to move the box around.

How to make a paper draggable

If the paper is too big for the div it's shown in, I'd like to make the paper draggable.
I tried the papers blank:pointerdown and pointerup events but was not able to just follow the mousemovement. I also tried to make the element of the paper draggable via jquery, but nothing seems to do the trick...
Is there any way to do this?
This can be achieved with a combination of JointJS events and document events. The graph display is encapsulated in a div:
<div id='diagram'></div>
Then add the event handlers for JointJS, first the pointerdown event where we store the start position of the drag:
paper.on('blank:pointerdown',
function(event, x, y) {
dragStartPosition = { x: x, y: y};
}
);
Then the end of the drag (pointerup) when we delete the variable we store the position in (it also acts as a flag whether there is an active drag in progress):
paper.on('cell:pointerup blank:pointerup', function(cellView, x, y) {
delete dragStartPosition;
});
As JointJS does not expose a "paper pointer move" event, we need to use the mousemove document event. For example, with JQuery:
$("#diagram")
.mousemove(function(event) {
if (dragStartPosition)
paper.translate(
event.offsetX - dragStartPosition.x,
event.offsetY - dragStartPosition.y);
});
We get the drag start coordinates and the current pointer position and update the paper position using the paper.translate() call.
WARNING: if you scale the paper (using paper.scale()), you have to also scale the starting position of the drag:
var scale = V(paper.viewport).scale();
dragStartPosition = { x: x * scale.sx, y: y * scale.sy};
The calls to paper.translate() will then update to the proper position.
I know this is a slightly old thread, but I was stuck with this for a while and came across a neat solution using the SVG Pan and Zoom library. Git hub link here
EDIT - I created a plunker of the steps below (plus some extras) here:
SVG panning with Jointjs
First step after creating the paper is to initialise SVG Pan and Zoom:
panAndZoom = svgPanZoom(targetElement.childNodes[0],
{
viewportSelector: targetElement.childNodes[0].childNodes[0],
fit: false,
zoomScaleSensitivity: 0.4,
panEnabled: false
});
Where targetElement is the div that the jointjs paper has gone into. Jointjs will create a SVG element within that (hence the childNodes[0]) and within that element the first element is the viewport tag (hence childNodes[0].childNodes[0] in the viewportselector). At this stage pan is disabled, because in my use case it would intefer with drag and drop elements on the paper. Instead what I do is keep a reference to the panAndZoom object and then switch pan on and off on the blank:pointerdown and blank:pointerup events:
paper.on('blank:pointerdown', function (evt, x, y) {
panAndZoom.enablePan();
});
paper.on('cell:pointerup blank:pointerup', function(cellView, event) {
panAndZoom.disablePan();
});
Just another way of tackling the issue I guess, but I found it a bit easier, plus it gives you zoom too and you can adjust the sensitivity etc.
I suggest the following:
register a handler for the paper blank:pointerdown event that will initiate the paper dragging (store a flag which you'll use in your mousemove handler to recognize the paper is in the "panning" state).
Put the big paper in a <div> container with CSS overflow: auto. This <div> will be your little window to the large paper.
register a handler for document body mousemove event (because you most likely want the paper to be dragged even if the mouse cursor leaves the paper area?). In this handler, you'll be setting the scrollLeft and scrollTop properties of your <div> container making the paper "panning". For adjusting the scrollLeft and scrollTop properties, you'll use the clientX and clientY properties of the event object together with the same properties that you stored previously in your blank:pointerdown handler. (in other words, you need those to find the offset of the panning from the last mousemove/blank:pointerdown).
register a handler for document body mouseup and in this handler, clear your paper dragging flag that you set in step 1.

Resources