Avalonia keep window position after resize - c#

I want to keep the window's position after the window has been resized. E.g, like how JPEGView handles resizing when changing image.
In WPF this code would accomplish it:
protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
{
base.OnRenderSizeChanged(sizeInfo);
//Calculate half of the offset to move the window
if (sizeInfo.HeightChanged)
Top += (sizeInfo.PreviousSize.Height - sizeInfo.NewSize.Height) / 2;
if (sizeInfo.WidthChanged)
Left += (sizeInfo.PreviousSize.Width - sizeInfo.NewSize.Width) / 2;
}
How would you accomplish the same in AvaloniaUI?
Update:
I've tried subscribing to ClientSizeProperty.Changed, this just forces the window to be centered on the screen
ClientSizeProperty.Changed.Subscribe(
x =>
{
var newSize = new Size(
ClientSize.Width + (x.OldValue.Value.Width - x.NewValue.Value.Width) / 2,
ClientSize.Height + (x.OldValue.Value.Height - x.NewValue.Value.Height) / 2);
var rect = new PixelRect(
PixelPoint.Origin,
PixelSize.FromSize(newSize, scaling));
var screen = Screens.ScreenFromPoint(owner?.Position ?? Position);
if (screen != null)
{
Position = screen.WorkingArea.CenterRect(rect).Position;
}
});

I figured it out.
ClientSizeProperty.Changed.Subscribe(size =>
{
var x = (size.OldValue.Value.Width - size.NewValue.Value.Width) / 2;
var y = (size.OldValue.Value.Height - size.NewValue.Value.Height) / 2;
Position = new PixelPoint(Position.X + (int)x, Position.Y + (int)y);
});

Related

Why when adding Handles.ArrowHandleCap to a line it's not drawing the ArrowHandleCap?

I'm using Editor Window maybe that's the problem ?
The idea is when connecting two nodes is also to make an arrow at the end position that will show the connecting flow direction.
In the screenshot when I'm connecting two nodes for example Window 0 to Window 1
So there should be an arrow at the end of the line near Window 1 showing indicating that Window 0 is connected to Window 1 so the flow is from Window 0 to Window 1.
But it's not drawing any ArrowHandleCap.
I don't mind to draw another simple white arrow at the end position but it's not working at all for now. Not drawing an arrow at all.
This is my Editor Window code :
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using UnityEditor.Graphs;
using UnityEngine.UI;
public class NodeEditor : EditorWindow
{
List<Rect> windows = new List<Rect>();
List<int> windowsToAttach = new List<int>();
List<int> attachedWindows = new List<int>();
int tab = 0;
float size = 10f;
[MenuItem("Window/Node editor")]
static void ShowEditor()
{
const int width = 600;
const int height = 600;
var x = (Screen.currentResolution.width - width) / 2;
var y = (Screen.currentResolution.height - height) / 2;
GetWindow<NodeEditor>().position = new Rect(x, y, width, height);
}
void OnGUI()
{
Rect graphPosition = new Rect(0f, 0f, position.width, position.height);
GraphBackground.DrawGraphBackground(graphPosition, graphPosition);
int selected = 0;
string[] options = new string[]
{
"Option1", "Option2", "Option3",
};
selected = EditorGUILayout.Popup("Label", selected, options);
if (windowsToAttach.Count == 2)
{
attachedWindows.Add(windowsToAttach[0]);
attachedWindows.Add(windowsToAttach[1]);
windowsToAttach = new List<int>();
}
if (attachedWindows.Count >= 2)
{
for (int i = 0; i < attachedWindows.Count; i += 2)
{
DrawNodeCurve(windows[attachedWindows[i]], windows[attachedWindows[i + 1]]);
}
}
BeginWindows();
if (GUILayout.Button("Create Node"))
{
windows.Add(new Rect(10, 10, 200, 40));
}
for (int i = 0; i < windows.Count; i++)
{
windows[i] = GUI.Window(i, windows[i], DrawNodeWindow, "Window " + i);
}
EndWindows();
}
void DrawNodeWindow(int id)
{
if (GUILayout.Button("Attach"))
{
windowsToAttach.Add(id);
}
GUI.DragWindow();
}
void DrawNodeCurve(Rect start, Rect end)
{
Vector3 startPos = new Vector3(start.x + start.width, start.y + start.height / 2, 0);
Vector3 endPos = new Vector3(end.x, end.y + end.height / 2, 0);
Vector3 startTan = startPos + Vector3.right * 50;
Vector3 endTan = endPos + Vector3.left * 50;
Color shadowCol = new Color(255, 255, 255);
for (int i = 0; i < 3; i++)
{// Draw a shadow
//Handles.DrawBezier(startPos, endPos, startTan, endTan, shadowCol, null, (i + 1) * 5);
}
Handles.DrawBezier(startPos, endPos, startTan, endTan, Color.white, null, 5);
Handles.color = Handles.xAxisColor;
Handles.ArrowHandleCap(0, endPos, Quaternion.LookRotation(Vector3.right), size, EventType.Repaint);
}
}
The problem is that the arrow is always behind the e.g. Window 0 since you call DrawNodeWindow always after DrawNodeCurve.
It happens because the arrow is always drawen starting from the endPos to the right direction with length = size so you always overlay it with the window later ... you have to change
// move your endpos to the left by size
var endPos = new Vector3(end.x - size, end.y + end.height / 2 , 0);
in order to have it start size pixels left before the actual end.x position.
However, as you can see it is still really small since it usually is used to display the arrow in 3D space - not using Pixel coordinates .. you might have to tweak arround or use something completely different.
How about e.g. simply using a GUI.DrawTexture instead with a given Arrow sprite?
// assign this as default reference via the Inspector for that script
[SerializeField] private Texture2D aTexture;
// ...
// since the drawTexture needs a rect which is not centered on the height anymore
// you have to use endPos.y - size / 2 for the Y start position of the texture
GUI.DrawTexture(new Rect(endPos.x, endPos.y - size / 2, size, size), aTexture, ScaleMode.StretchToFill);
like mentioned in the comment for all serialized fields in Unity you can already reference default assets for the script itself (in contrary to doing it for each instance like for MonoBehaviours) so with the NodeEditor script selected simply reference a downloaded arrow texture
If using a white arrow as texture you could then still change its color using
var color = GUI.color;
GUI.color = Handles.xAxisColor;
GUI.DrawTexture(new Rect(endPos.x, endPos.y - size / 2, size, size), aTexture, ScaleMode.StretchToFill);
GUI.color = color;
Result
P.S.: Arrow icon usedfor the example: https://iconsplace.com/red-icons/arrow-icon-14 you can already change the color directly on that page before downloading the icon ;)

how to control mouse pointer with head movement using emguCV C#?

I am developing a system just like Camera mouse or other face control mouse, I have implemented all the functionality, the mouse pointer is also moving well, but I want to create the movement smooth just like the mouse control the pointer. the code I am using is:
if (startButton == true)
{
try
{
cap = new Capture();
pictureBox1.Image = cap.QueryFrame().ToImage<Bgr, Byte>().Bitmap;
}
catch (Exception exp)
{
MessageBox.Show("Error:" + exp);
}
_cascadeClassifier = new CascadeClassifier(Application.StartupPath + "/haarcascade_frontalface_default.xml");
eye_cascadeClassifier = new CascadeClassifier(Application.StartupPath + "/haarcascade_eye.xml");
timer1.Start();
}
private void timer1_Tick(object sender, EventArgs e)
{
using (var imageFrame = cap.QueryFrame().ToImage<Bgr, Byte>().Flip(FlipType.Horizontal))
{
if (imageFrame != null)
{
var grayframe = imageFrame.Convert<Gray, byte>();
var faces = _cascadeClassifier.DetectMultiScale(grayframe, 1.1, 10, Size.Empty); //the actual face detection happens here
foreach (var face in faces)
{
if(Configure.FaceBoxCheck==true)
imageFrame.Draw(face, new Bgr(Color.LightGreen), 2); //the detected face(s) is highlighted here using a box that is drawn around it/them
Int32 yCoordStartSearchEyes = face.Top + (face.Height * 3 / 11);
Point startingPointSearchEyes = new Point(face.X, yCoordStartSearchEyes);
Size searchEyesAreaSize = new Size(face.Width, (face.Height * 3 / 11));
Rectangle possibleROI_eyes = new Rectangle(startingPointSearchEyes, searchEyesAreaSize);
int widthNav = (imageFrame.Width / 11 * 3);
int heightNav = (imageFrame.Height / 11 * 3);
Rectangle nav = new Rectangle(new Point(imageFrame.Width / 2 - widthNav / 2, imageFrame.Height / 2 - heightNav / 2), new Size(widthNav, heightNav));
imageFrame.Draw(nav, new Bgr(Color.Lavender), 3);
Point cursor = new Point(face.X + searchEyesAreaSize.Width / 2, yCoordStartSearchEyes + searchEyesAreaSize.Height / 2);
grayframe.ROI = possibleROI_eyes;
var eyes = eye_cascadeClassifier.DetectMultiScale(grayframe, 2.15, 3, Size.Empty);
foreach (var eye in eyes)
{
//imageFrame.Draw(eye, new Bgr(Color.Red), 2);
if(Configure.EyeBoxCheck==true)
imageFrame.Draw(possibleROI_eyes, new Bgr(Color.DarkGreen), 2);
if (nav.Left < cursor.X && cursor.X < (nav.Left + nav.Width) && nav.Top < cursor.Y && cursor.Y < nav.Top + nav.Height)
{
LineSegment2D CursorDraw = new LineSegment2D(cursor, new Point(cursor.X, cursor.Y + 1));
imageFrame.Draw(CursorDraw, new Bgr(Color.White), 3);
//we compute new cursor coordinate using a simple scale based on frame width and height
int xCoord = (imageFrame.Width * (cursor.X - nav.Left)) / nav.Width;
int yCoord = (imageFrame.Height * (cursor.Y - nav.Top)) / nav.Height;
//We set our new cursor position
Cursor.Position = new Point(xCoord * 2, yCoord *2);
}
}
}
Ok, I'm sure there are a lot of other better ways, but this is a quick&dirty way of moving cursor position in a "Smooth" way from point a to point b. Of course this implementation can and should be optimized using a different thread instead of using Application.DoEvents() to avoid blocking the UI thread, but i hope this gets you on the track. First, how you should use it. Instead of:
Cursor.Position = new Point(xCoord * 2, yCoord *2);
You should do this:
MoveCursorSmooth(Cursor.Position, new Point(xCoord * 2, yCoord *2));
Now, the implementation of MoveCursorSmooth:
private void MoveCursorSmooth(Point a, Point b)
{
var step = 5;
var left = Math.Min(a.X, b.X);
var right = Math.Max(a.X, b.X);
int width = right - left;
var top = a.Y;
var bottom = b.Y;
int height = bottom - top;
if (width > height)
{
double slope = (double)height / (double)width;
if (a.X <= b.X)
for (int x = 1; x < width; ++x)
{
Cursor.Position = new Point((left + x), (a.Y + ((int)(slope * x + 0.5))));
System.Threading.Thread.Sleep(step);
Application.DoEvents();
}
else
for (int x = 1; x < width; ++x) // xOffset
{
Cursor.Position = new Point((right - x), (a.Y + ((int)(slope * x + 0.5))));
System.Threading.Thread.Sleep(step);
Application.DoEvents();
}
}
else
{
double slope = (double)width / (double)height;
if (a.X <= b.X)
{
for (int y = 1; y < height; ++y)
{
Cursor.Position = new Point((a.X + ((int)(slope * y + 0.5))), (top + y));
System.Threading.Thread.Sleep(step);
Application.DoEvents();
}
}
else
{
for (int y = 1; y < height; ++y)
{
Cursor.Position = new Point((b.X + ((int)(slope * y + 0.5))), (bottom - y));
System.Threading.Thread.Sleep(step);
Application.DoEvents();
}
}
}
}
This method is based on this answer

How to enable zoom in and zoom out on a contentpage in xamarin forms?

How can I enable zoom on a contentpage in xamarin forms? Is it possible to enable it on the entire contentpage? Or is it only possible to zoom in on images?
You can use the pinch gesture inside a ContentPage, here is the official page:
https://developer.xamarin.com/guides/xamarin-forms/user-interface/gestures/pinch/
and for the entire project sample:
https://github.com/xamarin/xamarin-forms-samples/tree/master/WorkingWithGestures/PinchGesture
Here is an example of what you can achieve:
Xamarin.Forms Pinch Example
Try this Class , this solution do not scroll when you zoom .
Source found here:
Source Code
public class PinchToZoomContainer : ContentView {
double currentScale = 1;
double startScale = 1;
double xOffset = 0;
double yOffset = 0;
public PinchToZoomContainer ()
{
var pinchGesture = new PinchGestureRecognizer ();
pinchGesture.PinchUpdated += OnPinchUpdated;
GestureRecognizers.Add (pinchGesture);
}
void OnPinchUpdated (object sender, PinchGestureUpdatedEventArgs e)
{
if (e.Status == GestureStatus.Started) {
// Store the current scale factor applied to the wrapped user interface element,
// and zero the components for the center point of the translate transform.
startScale = Content.Scale;
Content.AnchorX = 0;
Content.AnchorY = 0;
}
if (e.Status == GestureStatus.Running) {
// Calculate the scale factor to be applied.
currentScale += (e.Scale - 1) * startScale;
currentScale = Math.Max (1, currentScale);
// The ScaleOrigin is in relative coordinates to the wrapped user interface element,
// so get the X pixel coordinate.
double renderedX = Content.X + xOffset;
double deltaX = renderedX / Width;
double deltaWidth = Width / (Content.Width * startScale);
double originX = (e.ScaleOrigin.X - deltaX) * deltaWidth;
// The ScaleOrigin is in relative coordinates to the wrapped user interface element,
// so get the Y pixel coordinate.
double renderedY = Content.Y + yOffset;
double deltaY = renderedY / Height;
double deltaHeight = Height / (Content.Height * startScale);
double originY = (e.ScaleOrigin.Y - deltaY) * deltaHeight;
// Calculate the transformed element pixel coordinates.
double targetX = xOffset - (originX * Content.Width) * (currentScale - startScale);
double targetY = yOffset - (originY * Content.Height) * (currentScale - startScale);
// Apply translation based on the change in origin.
Content.TranslationX = targetX.Clamp (-Content.Width * (currentScale - 1), 0);
Content.TranslationY = targetY.Clamp (-Content.Height * (currentScale - 1), 0);
// Apply scale factor
Content.Scale = currentScale;
}
if (e.Status == GestureStatus.Completed) {
// Store the translation delta's of the wrapped user interface element.
xOffset = Content.TranslationX;
yOffset = Content.TranslationY;
}
}
}
Helper DoubleExtensions
public static class DoubleExtensions
{
public static double Clamp (this double self, double min, double max)
{
return Math.Min (max, Math.Max (self, min));
}
}
You can try using the Scale Api on the Content page. This seemed to work for me on a small test app.
public App ()
{
// The root page of your application
var scaleUp = new Button {
Text = "Scale Up"
};
scaleUp.Clicked += (sender, e) => {
this.MainPage.Scale += 1;
};
var scaleDown = new Button {
Text = "Scale Down"
};
scaleDown.Clicked += (object sender, EventArgs e) => {
this.MainPage.Scale -= 1;
};
MainPage = new ContentPage {
Content = new StackLayout {
VerticalOptions = LayoutOptions.Center,
Children = {
scaleUp,
scaleDown
}
}
};
}
Trying to use this in conjunction with other controls that take the Pan/scroll gesture seems to fail. ScrollView and ListView for example grab the Pinch gesture even though they themselves don't Pinch but they do scroll/pan. So it seems like Pinch inherits/uses that gesture so it is grabbed by the wrapped control. This seems to make Pinch a very niche feature that can only work on very static views of Label and Image for example, but nothing more complex.
You can use gestures.
Sample here : http://arteksoftware.com/gesture-recognizers-with-xamarin-forms/

Alternative to GetWindowRect() for multi-monitor usage?

I am trying to reposition a form to bottom/right point of a control.
public void SetAutoLocation()
{
Rect rect;
GetWindowRect(referenceControl.Handle, out rect);
Point targetPoint;
targetPoint = new Point(rect.left, rect.top + referenceControl.Height);
if (rect.left + referenceControl.Width - this.Width < 0) //Outside left border
{
targetPoint.X = 0;
}
else
{
targetPoint.X = rect.left - this.Width + referenceControl.Width;
}
if (targetPoint.X + this.Width > System.Windows.Forms.SystemInformation.WorkingArea.Right) //Outside right border
{
targetPoint.X = System.Windows.Forms.SystemInformation.WorkingArea.Right - this.Width;
}
else if (targetPoint.X < 0)
targetPoint.X = 0;
if (targetPoint.Y + this.Height > System.Windows.Forms.SystemInformation.WorkingArea.Bottom) //Outside below border
{
targetPoint.Y = rect.top - this.Height;
}
if (targetPoint.Y < 0)
{
targetPoint.Y = 0;
}
if (targetPoint.X < 0)
{
targetPoint.X = 0;
}
this.Location = targetPoint;
this.Refresh();
}
The above code works well in single monitor display. But when the parent form is opened in dual monitor display, the form positions itself on the 1st monitor, as GetWindowRect() returns the rectangle inside the primary display.
So looking for some alternative to GetWindowRect() that might work on multi-display.
Use the Screen class to get the WorkingArea for the monitor that the control is on:
var screen = Screen.FromControl(referenceControl);
var area = screen.WorkingArea;
var rect = referenceControl.RectangleToScreen(
new Rectangle(0, 0, referenceControl.Width, referenceControl.Height));
// etc..
Note how RectangleToScreen can help you avoid having to pinvoke GetWindowRect().
If you consult MSDN it's clearly stated that SystemInformation.WorkingArea returns informations only for primary monitor :
WorkingArea always returns the work area of the primary monitor. If
you need the work area of a monitor in a multiple display environment,
you can call one of the overloads of Screen.GetWorkingArea.

Trying to make a zoom effect

I am trying to make a zoom effect on a picturebox with mouse wheel. Everything is OK except that when I use mouse middle button to zoom in or zoom out, it is ok, but it does not zoom in or zoom out the point which the mouse cursor is on. When I zoom in the point, I want it always slide. Please help me add a code snippet to make it work.
Here is my code:
int i = 5;
int index = 10;
private double[] zoomfactor = { .25, .33, .50, .66, .80, 1, 1.25, 1.5, 2.0, 2.5, 3.0 };
private void Zoom(int i)
{
double new_Zoom = zoomfactor[i];
imgBox.Width = Convert.ToInt32(imgBox.Image.Width * new_Zoom);
imgBox.Height = Convert.ToInt32(imgBox.Image.Height * new_Zoom);
}
private void On_wheel(object sender, System.Windows.Forms.MouseEventArgs e)
{
i = i + e.Delta / 120;
if (i < 0)
{
i = 0;
}
else
{
if (i <= index)
i = i;
else
i = index;
}
Zoom(i);
}
You need to adjust the picture box location based on the mouse position relative to the form.
Here is a rough but working example of how you might do this:
var i = 5;
var zoomfactor = new[] {.25, .33, .50, .66, .80, 1, 1.25, 1.5, 2.0, 2.5, 3.0};
var origin = new Point(100, 100);
var image = Image.FromFile(#"c:\example.png");
var imgBox = new PictureBox {
Location = origin,
Size = image.Size,
Image = image,
SizeMode = PictureBoxSizeMode.StretchImage
};
var form = new Form {
Size = new Size(800, 600),
Controls = {imgBox}
};
form.MouseWheel += (sender, e) => {
i += e.Delta/120;
if (i < 0) {
i = 0;
}
if (i >= zoomfactor.Length) {
i = zoomfactor.Length - 1;
}
var newZoom = zoomfactor[i];
imgBox.Width = (int) (imgBox.Image.Width*newZoom);
imgBox.Height = (int) (imgBox.Image.Height*newZoom);
imgBox.Left = (int) (e.X - newZoom*(e.X - origin.X));
imgBox.Top = (int) (e.Y - newZoom*(e.Y - origin.Y));
};
form.ShowDialog();
You are not taking the mouse coordinates into account.
The MouseEventArgs class tells you where the mouse is (X, Y and Location properties), so you need to adjust accordingly.

Categories