Click here to Skip to main content
Click here to Skip to main content

Tagged as

Go to top

Excerpt from the book World of Movable Objects

, 10 Nov 2010
Rate this:
Please Sign up or sign in to vote.
Overlapping Prevention

This is an excerpt from the book “World of Movable Objects” which is available together with the whole project of the accompanying application (all codes are in C#) at www.sourceforge.net in the project MoveableGraphics (names of projects are case sensitive there!). World of Movable Objects

Preface

Suppose that you are sitting at the writing table. In front of you there are books, text-books, pens, pencils, pieces of paper with some short notes, and a lot of other things. You need to do something, you have to organize your working place, and for this you will start moving the things around the table. There are no restrictions on moving all these things and there are no regulations on how they must be placed. You can put some items side by side or atop each other, you can put several books in an accurate pile, or you can move the bunch of small items to one side in a single movement and forget about them, because you do not need them at the moment. You do not need any instructions for the process of organizing your working place; you simply start putting the things in such an order, which is the best for you at this particular moment. Later, if something does not satisfy you or if you do not need some items in their places, you will move some of the items around and rearrange everything in whatever order you need without even paying attention to this process. You make your working place comfortable for your work at each moment and according to your wish.

The majority of those who are going to read this text nearly forgot everything about paper, books, hand writing… A personal computer became the only instrument and the working place for millions of people. Screens of our computers are occupied with different programs; the area of each program is filled with many different objects. Whenever you need to do something, you start the needed program and in each of them you know exactly, what you want to do and what you can do. Did it ever come to your attention that in any program you know exactly the whole set of possible movements and actions? Have you ever understood that the set of allowed actions is extremely small and you try to do your work within a strictly limited area of allowed steps? Those limits are the same in all the programs; that is why there are no questions about the fairness of such situation. You can press a button; you can select a line or several lines in a list; you can do several other typical things, but you never go out of the standard actions which you do in any other program. You and everyone else know exactly what they are allowed to do. Other things are neither tried, nor discussed. They simply do not exist.

Now try to forget for a minute that for many years you were taught what you could do. Let us say that you know, how to use the mouse (press – move – release), but there are no restrictions on what you are allowed to do with a mouse. Switch off the rules of your behaviour “in programs” according to which all your work was going for years.

Try the new set of rules:

  • You can press ANY object and move it to any new location.
  • You can press the border of any object and change its size in the same easy way; there are no limitations on such resizing (except some obvious and natural).
  • A lot of objects you can reconfigure in the same simple way by moving one side or another.

It must be obvious to you that the reaction of a program on your clicking a button does not depend on the screen position of this button, so if you change the size or location of a button or a list it is not going to change any code that is linked with clicking a button, selecting a line in the list or making a choice between several positions. All the programs are still going to work according to their purposes. Buttons and lists represent a tiny part of objects that occupy the screens. Now make one more step and imagine the programs, in which ALL the objects are under your control in exactly the same way. You can do whatever you want with the objects, while the programs continue to work according to their purposes.

What would you say about such a world? Do not be in a hurry with your answer. You never worked with such programs; you would better try before giving an answer. This book is about the screen world of movable and resizable objects. Those objects can be of very different shapes, behaviour, and origin; that is why there are more than 80 examples in the accompanying program. Some of those examples are simple; others are in reality complex and big enough applications by themselves. And there is not a single unmovable object in all of them. The world of movable objects – the world of user-driven applications.

Introduction

The book is accompanied by a program with a lot of examples. Even from the beginning, the examples are designed according to all the ideas of the user-driven applications, but the explanation of these ideas and the discussion of how they were born and why the applications must be designed in such a way appear much later, in the second half of the book. It cannot be done in another way: before talking about the ideas of user-driven applications I have to demonstrate the design of all the elements, without which such applications simply cannot exist.

This book is about two things:

  • The design of movable / resizable objects.
  • The development of user-driven applications. The two theories can be looked at as the independent things because:
  • The design of movable objects is not influenced in any way by the afterthoughts of where these objects are going to be used.
  • The design of user-driven applications is independent of the real algorithm for constructing movable objects.

At the same time there is a very strong relation between the two things, as the user-driven applications can be designed only on the basis of movable / resizable objects and all the extraordinary features of such applications are the results of their construction exclusively and entirely of movable objects. The movable / resizable objects can be used by themselves in the existing applications of the standard type, but only invention of an algorithm turning an arbitrary object into movable / resizable allowed to design an absolutely new type of programs – user-driven applications.

Often enough, when I tell people that I have thought out an algorithm of making movable any screen object, a lot of people are a bit (or strongly) surprised: “What are you talking about? Is there anything new in it? We have seen objects moving around the screen for years and years?” Certainly, they saw; as the demonstration of moving objects has the history of several decades. Only all those objects moved according to the scenario written by their developers. Users could do nothing about that moving except watching. The thing I was working on for years and which I am going to describe here is absolutely different: it is the moving of screen objects by the users of the programs. This article is about the development of screen objects, movements of which are not predicted by the designer of an application. It is not about a film developed on a predetermined scenario. It is about an absolutely new type of programs – user-driven applications. In these programs all objects, from the simplest to the most complicated, consisting of many independent or related parts, are designed to be moved and resized only by USERS. The objects are designed with these special features, and then the whole control of WHAT, WHEN, and HOW is going to appear on the screen is given to the users.

I would like to mention beforehand that the overall behaviour of user-driven applications is so different from whatever you experienced before that you can feel a shock or at least a great amusement at the first try. From my point of view, such a reaction from the people who are introduced to the user-driven applications for the first time supposed to be absolutely natural. A lot of people had the same feeling of a shock when, after years of work under DOS, they tried the Windows system for the first time. By the way, the only visual difference of the Windows system from the familiar DOS was the existence of several movable / resizable windows (in comparison with a single one and unmovable) and the possibility of moving icons across and along the screen. That was all! And even that was a shock. From the users’ point of view, the step from the currently used programs to the user-driven applications is much bigger than that old step from DOS to Windows. In user-driven applications EVERYTHING is movable and resizable, and this is done not according to some predefined scenario, but by users themselves.

Movability of elements is not some kind of an extra feature that is simply added to well known objects in order to improve their behaviour. Technically (from the programming point of view), it is adding a new feature, but it turned out to be not simply a small addition to the row of other features. The movability of objects which are well known for years changes the way of using these objects absolutely. The most remarkable result of this change is that the applications have to be redesigned, while movability of all their parts must be taken into consideration. Movable and unmovable elements cannot coexist on the screen in any way; they immediately begin to conflict and demand the transformation of any unmovable objects into movable. Movability of elements changes the whole system of relations between the objects of applications. Movability of objects is the main, the fundamental feature of the new design.

With the movability of all the parts from the tiny elements to the most complex objects, there is another way of application design, when a program continues to be absolutely correct from the point of fulfilling its main purpose (whatsoever this purpose is), but at the same time does not work according to the predefined scenario and does not even need such a scenario. To do such a thing, an application has to be developed not as a program in which whatever can be done with it has to be thought out by the developer beforehand and hard coded; instead an application is turned into an instrument of solving problems in particular area. An instrument has no fixed list of things that can be done with it, but only an idea of how it can be used; then an instrument is developed according to this idea. A user of an instrument has full control of it; only the user decides when, how, and for what purpose it must be used. Exactly the same thing happens with programs that are turned into instruments.

I call the programs, based on movable / resizable objects, user-driven applications. When you get a car, you get an instrument of transportation. Its manual contains some suggestions on maintenance, but there is no fixed list of destinations for this car. You are the driver, you decide about the place to go and the way to go. That is the meaning of the term user-driven application: you run the program and make all the decisions about its use; a designer only provides you with an instrument which allows you to drive.

Figure I.1 demonstrates the first view that you see on starting the WorldOfMoveableObjects application. This is, I think, also the first of my Demo applications in which I did not put on the screen the reminder that ALL the objects in this program (not only in this form, but in all the forms of this application!) are movable. The objects that you see in this figure are discussed in detail in one or another chapter of the book. I think that a quick look across this picture from one object to another may give you a better understanding of what you can find further on. Like a scheme at the entrance of a big museum. Only instead of the “Do not touch the exhibits”, you have “Any object can be moved by any inner point”, so I do not need to repeat it for each of them.

Polygon in the middle. Configuration of this polygon can be changed by moving any end point of any segment of the perimeter or the central point. By changing the configuration you can literally turn the figure inside out. All other points of the perimeter, except the apices, can be used for scaling the polygon. The polygon can be rotated (right mouse press) by any inner point. Class ChatoyantPolygon is discussed in the subsection of the chapter Polygons.
Group in the top right corner. The group consists of controls paired with their comments. Any control is moved by the frame; its comment moves synchronously. Comments can be moved independently and placed anywhere. Class CommentedControl is discussed in the chapter Control + text. A set of elements constitutes the group. The frame of the group adjusts to the positions of all the inner elements and is always shown around them. The title of the group can be moved left and right between the two sides of the group. Class ElasticGroup is discussed in the chapter Groups of elements and is widely used in all the complex applications demonstrated in the second part of the book. Class ElasticGroup has a special tuning form which is used in all other places, but not in this form.
Plot in the bottom left corner. The main plotting area is resized by borders and corners. The scales can be moved individually and positioned anywhere (with some restrictions, as there must be the conformity between the scales and the main plotting area). Comment belongs to the CommentToRect class; the identical copy of this class is discussed in the chapter Complex objects. Classes Scale and Plot are discussed in the chapter Applications for science and engineering. Both classes have special tuning forms which are used in all other places (forms), but not in this one.

image001-1.gif

Ring in the bottom right corner. Ring can be resized by any point of the outer or inner circle; resizing of rings is discussed in the chapter Curved borders. N-node covers. The cover of a ring uses a special technique which is discussed in the chapter Transparent nodes. This ring has the sliding partitions. Class PrimitiveRing is discussed in the chapter Data visualization.
Information at the bottom This text can be moved but not rotated. Class TextM is discussed in the chapter Texts.
Words across the form. Can be moved and rotated by any point. Class TextMR is discussed in the chapter Texts.
House in the top left corner. House can be resized by all four sides and all four corners of the rectangular part. The roof top can be moved not only up or down, but also to the sides (no requirement for the symmetry of the roof). Class SimpleHouse appears only in the Appendix B, but there are similar classes of houses in the chapters Polygons and An exercise in painting.

There are two big differences between using objects of the same classes in the Form_Main.cs and further on. You can move the objects in the Form_Main.cs, you can resize them, but you cannot tune them, and the results are not saved for later use. Design of programs without these two things is against the laws of the user-driven applications, but I decided not to introduce them here. Everything will come at a proper time. Let us start.

The next part is taken from chapter 11 “Movement restrictions”.

If we talk about the different types of restrictions, then the prevention of overlapping is definitely not the new type but is the part of the restrictions from other objects. The reason to divide them and to unite several further examples into another section is only one: next few examples use another technique. In the previous examples an object could be stopped, but the mouse continued to move. In all the examples of this section the mouse is stopped with the stopped object and stays exactly on that spot of an object, where it first caught it. If this would be organized for moving an object of an arbitrary shape inside the rectangular area, then it would be possible to calculate the rectangular clipping area for a mouse and use the standard clipping procedure. If an object is supposed to move inside the non-rectangular area, then the standard clipping is of no help. For such cases the Mover class has something else in its store.

Adhered Mouse

File: Form_AdheredMouse.cs

Menu position: Graphical objects – Movement restrictions – Adhered mouse

image001.gif

Fig.11.6 The ball can move only on the board

The Form_AdheredMouse.cs looks very simple with only few objects in view: a board in the form of a regular polygon, a ball on this board, and one control to change the number of vertices in the board (figure 11.6). The board belongs to the class RegularPolygonWithBall and can be moved and rotated. It has the form of a regular polygon, so it requires for its initialization a central point, radius of vertices, number of vertices, and the initial angle (angle to the first vertex).

    public class RegularPolygonWithBall : GraphicalObject
    {
        PointF ptC;
        float radius;
        int nVertices;
        double angle;
        SolidBrush brush;
        BallInsideConvexPoly ball;
        int radBall = 20;

The ball belongs to the BallInsideConvexPoly class. Though the name of the board informs that its form is a regular polygon and the name of the ball’s class hint about a convex polygon, this is not a mistake. The ball is designed to move inside a convex polygon without crossing its borders; the use of this ball inside the regular polygon is a private case.

The board is the classical case of a complex object: whenever it is moved, the ball moves with it, but the ball can also move individually. If during the movement the ball runs into the border, it stops and the cursor not goes any farther by itself but stays adhered to the same place on the ball, by which it originally has caught it.

Among the parameters of the RegularPolygonWithBall constructor you can find several expected values to describe a regular polygon, but you can find also two strange parameters, which were never used in constructors of graphical objects of any shape or complexity in all the previous examples. One of them is the form, in which the whole work is done; another is the mover, which supervises the moving process.

public RegularPolygonWithBall (Form frm, Mover mvr, PointF center, float rad,
                    int vertices, double angleDegree, Color clrPoly, Color clrObj)
{
    ptC = center;
    radius = Math .Max (Math .Abs (rad), 200);
    nVertices = Math .Min (Math .Max (3, Math .Abs (vertices)), 12);
    angle = Auxi_Convert.DegreeToRadian (Auxi_Common.LimitedDegree (angleDegree));
    brush = new SolidBrush (clrPoly);
    pen = new Pen (Color .DarkGray);
    ball = new BallInsideConvexPoly (frm, mvr, ptC, radBall, clrObj, BallAreal);
}

To be absolutely correct, the form is passed as a parameter to some objects which are based on or use the texts because the size of a text must be calculated, but if there is no text in an object, then its constructor does not need a form among the parameters. A mover is not needed among the parameters at all, because an object does not need to know whether it is movable or not. Mover supervises the moving / resizing of all the objects from its queue, but the objects cannot regulate a mover or demand anything from their supervisor. At least it was so up till this example, so there is definitely something special about it. As you can see from the code, both of these special parameters are not even saved in the RegularPolygonWithBall object, but are redirected into the constructor of the subordinate ball, so the BallInsideConvexPoly class is the real recipient of both of these parameters.

        public BallInsideConvexPoly (Form frm, Mover mvr, PointF pt, float rad,
                                     Color clr, PointF [] areal)
        {
            form = frm;
            supervisor = mvr;
            center = pt;
            radius = Math .Max (minRadius, Math .Abs (rad));
            ptsAllowed = areal;
            brush = new SolidBrush (clr);
        }

In addition to these two parameters, the ball gets the areal in which its center can move. For a board with a shape of a regular polygon this smaller area is easily calculated, when the radius of a ball and the geometry of a board are known, which includes the central point, radius and number of vertices, and the angle of the first vertex. But the points for the perimeter of the area can be calculated in some other way and the ball will continue to move inside these borders; the only restriction is that the array of points must represent a convex polygon.

Moving of all the objects is organized with the standard three mouse events. When any object is pressed by the mouse and caught by the mover, there are two special situations. When the rotation of the board has to start in response to a right mouse press, the reaction is standard and was discussed earlier: the compensation angle must be calculated. When the ball is pressed with a left button, nothing new is expected, because it is a standard movement of a primitive object. But this is the place where something interesting starts.

private void OnMouseDown (object sender, MouseEventArgs e)
{
    if (mover .Catch (e .Location, e .Button))
    {
        GraphicalObject grobj = mover .CaughtSource;
        if (e .Button == MouseButtons .Left)
        {
            if (grobj is BallInsideConvexPoly)
            {
                (grobj as BallInsideConvexPoly) .InitialMouseShift (e .Location);
            }
        }
        else if (e .Button == MouseButtons .Right)
        {
            if (grobj is RegularPolygonWithBall)
            {
                (grobj as RegularPolygonWithBall) .StartRotation (e .Location);
            }
        }
    }
}

A ball is an object of solid sizes. It can be pressed far away from its center, so the difference between the pressed point and the center of the ball must be remembered to organize the accurate moving further on.

        public void InitialMouseShift (Point pt)
        {
            dxMouseFromCenter = pt .X - center .X;
            dyMouseFromCenter = pt .Y - center .Y;
        }

The movement of any object is described by its MoveNode() method. Many previous examples demonstrated that if there exist any kind of restrictions, then they are used inside this method to determine the possibility of moving. Ball is a primitive object with the cover consisting of a single node, so there is no check for the number of the pressed node. But ball has a limitation of its movement in the shape of a convex polygon, inside which the center of a ball can move; the proposed movement of the ball is checked against this area.

public override bool MoveNode (int i, int dx, int dy, Point ptM, MouseButtons btn)
{
    bool bRet = false;
    if (btn == MouseButtons .Left)
    {
        PointF centerNew = new PointF (ptM .X - dxMouseFromCenter,
                                       ptM .Y - dyMouseFromCenter);
        if (Auxi_Geometry .InsideConvexPolygon (centerNew, ptsAllowed))
        {
            Center = centerNew;
            bRet = true;
        }
        else
        {
            supervisor .MouseTraced = false;
            Cursor .Position = form.PointToScreen (Point .Round (
                                      new PointF (center.X + dxMouseFromCenter,
                                                  center.Y + dyMouseFromCenter)));
            supervisor .MouseTraced = true;
            bRet = false;
        }
    }
    return (bRet);
}

When the proposed position of the ball’s center is going to be inside the allowed area, then the ball can be moved.

        if (Auxi_Geometry .InsideConvexPolygon (centerNew, ptsAllowed))
        {
            Center = centerNew;
            bRet = true;
        }

The real problem is in the case when the proposed movement is not allowed, because the mouse cursor has already moved! The cursor moved, but the movement of the ball is not allowed, so the cursor must be returned back. Easy? This movement of the cursor there and back again in an instant is absolutely invisible; nobody would see it, but there is a problem. The ball is caught by the mover, so each move of the cursor is transformed into the movement of the ball. If processed in the normal way, this back movement of the cursor is going to be transformed into the synchronous movement of the ball, so the cursor moves back, but the ball synchronously moves from it. The only way to avoid such thing is to cut temporarily the link between the cursor and the caught ball for this back movement of a cursor. This is done by the use of the Mover.MouseTraced property twice. First you cut the link, then you change back the position of the cursor, and then you reinstall the link between the mouse (mover) and the caught object.

        supervisor .MouseTraced = false;
        Cursor .Position = form.PointToScreen (Point .Round (
                                      new PointF (center.X + dxMouseFromCenter,
                                                  center.Y + dyMouseFromCenter)));
        supervisor .MouseTraced = true;
        bRet = false;

What is important that all the mover’s parameters are not affected by this temporarily cut of the link with the mover, so the caught object, the caught node, and everything else are unchanged. Because the move is not allowed, the MoveNode() method immediately returns false value; the caught object (ball) waits for the next movement.

Now you can see why an object, which is glued with the mouse throughout the period of movement, has to get those two additional parameters on initialization. The form is needed for the transformation of coordinates from one system to another; the mover (supervisor) is needed, because only mover can cut and reinstall the link with an object.

One more important detail. There are two ways to determine the new position of an object by the parameters of the MoveNode() method: either to rely on the pair of shifts along two axis (dx, dy) or on the mouse position (ptM). I mentioned several times that I always use the second choice throughout the rotations, but for normal forward movement, resizing, and reconfiguring I prefer the first option. This is correct anywhere except the cases when I have to use this Mover.MouseTraced property to go back and force. In all such cases the cursor has to be glued at the same point of an object throughout the whole movement. The shift from some object’s basic point to the cursor is calculated at the first moment and has to be unchanged throughout the process. It works much better, if in such a case the proposed position of this basic point is calculated from the changing cursor position with the help of that shift.

        PointF centerNew = new PointF (ptM .X - dxMouseFromCenter,
                                       ptM .Y - dyMouseFromCenter);

Ball in Labyrinth

File: Form_BallInLabyrinth.cs

Menu position: Graphical objects – Movement restrictions – Ball in labyrinth

image002.gif

Fig.11.7 Ball in labyrinth

The next example demonstrates the use of the same technique in some new environment. There is a small ball, which hopes to find its way through the labyrinth (figure 11.7). The labyrinth (class Labyrinth) consists of a set of walls (class Wall). The ball (class BallSV) can be moved along the corridors through the labyrinth. If the ball runs into the wall, it does not move farther on and the cursor does not move either. The cursor adheres to that point of the ball which it initially pressed and stays at this point of a ball throughout the whole move until the moment of release.

A ball of the BallSV class is a standard non-resizable circle. To move such an object, it is enough to give it a primitive cover, consisting of a single circular node. To define any simple circle, only a central point and radius are needed.

  public class BallSV : GraphicalObject
  {
      Form form;
      Mover supervisor;
      Point center;
      int radius;
      Labyrinth lab;

But this ball is going to move in the area, where its move is restricted by some structure. When the movement is blocked, the ball is supposed to stop and the mouse must be adhered to the same spot on the surface of the ball. Thus three additional parameters must be passed to the ball on initialization: form, mover, and labyrinth.

public BallSV (Form frm, Mover mvr, Point pt, int r, SolidBrush brsh, Labyrinth lb)

A human eye is a perfect instrument, which can see the discrepancy of one or two pixels. In this case, which requires an absolute accuracy, when the ball is caught by the mover (by the mouse!) it is impossible to simplify the task by assumption that the ball is caught at its central point. The initial shift between the center of a ball and the mouse position must be remembered and used throughout the whole movement. For this purpose, the InitialMouseShift() method is called from inside the OnMouseDown() method.

    private void OnMouseDown (object sender, MouseEventArgs e)
    {
        if (mover .Catch (e .Location, e .Button))
        {
            if (mover .CaughtSource is BallSV)
            {
                (mover .CaughtSource as BallSV) .InitialMouseShift (e .Location);
            }
        }
    }

The place where the possibility of ball’s movement is checked is certainly its MoveNode() method.

public override bool MoveNode (int i, int dx, int dy, Point ptM, MouseButtons btn)
{
    bool bRet = false;
    if (btn == MouseButtons .Left)
    {
        bRet = true;
        Point ptFrom, ptTo, ptCross;
        Point ptCenterNew = new Point (ptM .X - dxMouseFromCenter,
                                       ptM .Y - dyMouseFromCenter);
        for (int j = 0; j < lab .Walls .Count; j++)
        {
            lab .Segment (j, out ptFrom, out ptTo);
            if (Auxi_Geometry .Distance_PointSegment (ptCenterNew, ptFrom, ptTo)
                                                                          <= radius
                || Auxi_Geometry .Segment_Crossing (ptFrom, ptTo, center,
                                                         ptCenterNew, out ptCross))
            {
                supervisor .MouseTraced = false;
                Cursor .Position = form .PointToScreen (new Point (center .X +
                                dxMouseFromCenter, center .Y + dyMouseFromCenter));
                supervisor .MouseTraced = true;
                return (false);
            }
        }
        Center = ptCenterNew;
        bRet = true;
    }
    return (bRet);
}

This is also the case where the link between the mover and the caught ball must be cut temporarily, so the whole procedure is organized in the way similar to the previous example.

  1. The calculation of the proposed position of a ball is based not on the pair of mouse movements (dx, dy) but on exact mouse position ptM and the shift from the mouse to the center of a ball, which was estimated at the starting point of this movement.
    Point ptCenterNew = new Point (ptM .X - dxMouseFromCenter,
                                        ptM .Y - dyMouseFromCenter);
  2. There are two checks for the possibility of movement. Both checks are done against each wall of the labyrinth.
    lab .Segment (j, out ptFrom, out ptTo);
  3. The first check estimates the distance between the segment of the wall and the center of a ball; it cannot become less than the radius of a ball.
    if (Auxi_Geometry.Distance_PointSegment (ptCenterNew, ptFrom, ptTo) <= radius
  4. For the normal move of a ball it would be enough to have this first check only, but then I found that if the ball is moved against the wall at the speed of light or close to it, then it can go through (I hope you heard something about neutrino…). To return this ball back from becoming a particle into a normal screen object, I had to add another check. One method from the MoveGraphLibrary.dll solved the problem easily; the ball is not allowed to cross any segment on the way.
    || Auxi_Geometry .Segment_Crossing (ptFrom, ptTo, center,
        ptCenterNew, out ptCross))
  5. If any check failed, the mouse cursor has to be returned back. I want to remind that when the MoveNode() method is called, the mouse cursor has already moved! To return the cursor back, the link between the mover and the caught object (ball) must be temporarily cut, the cursor returned back, and then the same link reinstated. And do not forget to return false from the MoveNode() method in this case, because the move is not allowed.
        supervisor .MouseTraced = false;
        Cursor .Position = form .PointToScreen (new Point (center .X +
                               dxMouseFromCenter, center .Y + dyMouseFromCenter));
        supervisor .MouseTraced = true;
        return (false);

Strip in labyrinth

File: Form_StripInLabyrinth.cs

Menu position: Graphical objects – Movement restrictions – Strip in labyrinth

image003.gif

Fig.11.8 Strip in labyrinth

One more example of moving an object around a lot of obstacles. The same labyrinth is used in the Form_StripInLabyrinth.cs (figure 11.8), but the movable object is more interesting. It is a resizable strip with two rounded corners. With its sizes, the strip cannot be simply pressed and moved through the labyrinth; it has to be stopped and turned a bit to pass each turn of a corridor.

The strip (class StripSV) has a cover that was demonstrated at figure 7.2: curved parts of the border are used to change the length of a strip, straight parts of the border – to change the width. Strip can be moved and rotated by any point. The geometry of a strip, the possibility of its rotation, and the changing length require more parameters to describe the position of object than in case of a circle.

    public class StripSV : GraphicalObject
    {
        Form form;
        Mover supervisor;
        Labyrinth lab;
        PointF ptC0, ptC1;      // central points of the semicircles at the ends
        float radius;           // radius of those semicircles
        double angle;           // angle is calculated from ptc0 to ptc1
        SolidBrush brush;

The technique of returning the cursor back, if the proposed movement is not allowed, is similar to what was used in the previous example, so the form, mover, and labyrinth must be also mentioned among the fields of this class.

When any object is grabbed for moving, it usually requires the saving of some parameters, which are not going to change during the initiated movement, but are needed to calculate the object’s position throughout this movement. For rotation it is usually a compensation angle; for zooming it is often some scaling coefficient. In case of a strip we have both, but because of the expectation that on some restricted movements there can be a request to return the cursor back, there is a storing of an additional parameter.

private void OnMouseDown (object sender, MouseEventArgs e)
{
    if (mover .Catch (e .Location, e .Button))
    {
        GraphicalObject grobj = mover .CaughtSource;
        if (e .Button == MouseButtons .Left)
        {
            if (grobj is StripSV)
            {
               (grobj as StripSV).StartLengthChange (e.Location, mover.CaughtNode);
            }
        }
        else if (e .Button == MouseButtons .Right)
        {
            if (grobj is StripSV)
            {
                (grobj as StripSV) .StartRotation (e .Location);
            }
        }
    }
}

I have already explained the StartLengthChange() method for a strip while writing about similar strip before (chapter Curved borders. N-node covers). The code for this method is nearly the same, but here you can see an extra call to the InitialCatch() method.

    public void StartLengthChange (Point ptMouse, int iNode)
    {
        InitialCatch (ptMouse);
        PointF [] pts = CornerPoints ();
        if (iNode < 2)
        {
        }
        else if (iNode < 2 + nNodesOnHalfCircle)
        {
            fStartingDistanceToRect =
                        Auxi_Geometry .DistanceToLine (ptMouse, pts [1], pts [2]);
        }
        else
        {
            fStartingDistanceToRect =
                        Auxi_Geometry .DistanceToLine (ptMouse, pts [0], pts [3]);
        }
    }

When an object is pressed by the mouse with the intention to start some movement, it is just the right moment to calculate some parameters, which are fixed for the whole duration of this movement and are used to calculate the new position (or sizes) according to the mouse movement. These parameters are different for different movements and, as a rule, they are calculated in different methods. Up till now we saw three different types of parameters:

  • When an object is pressed by the left button for resizing, then a scaling coefficient is calculated.
  • When an object is pressed by the right button for rotation, then a compensation angle is calculated; in some cases the distance from the center of rotation is also needed.
  • When there is a chance of the future restricted movement, which requires the back move of a cursor, the shift between the pressed point and some object’s basic point is calculated.

In the case of the StripSV class I decided to combine all these calculations into a single InitialCatch() method and call this method on all the occasions. The basic point, from which the shifts are calculated, is also the center of rotation.

        private void InitialCatch (PointF pt)
        {
            center = Center;
            length = Auxi_Geometry .Distance (ptC0, ptC1);
            rMouseFromCenter = Auxi_Geometry .Distance (center, pt);
            double angleMouse = Auxi_Geometry .Line_Angle (center, pt);
            compensation = Auxi_Common .LimitedRadian (angleMouse - angle);
        }

If you make a quick search throughout the code of the Form_StripInLabyrinth.cs file, you will find out that this method is called not only at the moment of the first mouse press, but also in each part of the StripSV.MoveNode() method. This happens because different movements of a strip can be stopped by the walls; all such cases require to return the cursor back.

Any possible move of a strip in labyrinth must be checked against the restrictions. It can be a forward movement, or change of the width, or change of the length, or rotation – any of them can be stopped by the walls. The exact movement depends on the caught node or the pressed button, but all the branches of the strip’s MoveNode() method are organized in the similar way:

  • The cursor has its new location ptM.
  • The new size of the strip or its new location is calculated on the basis of this mouse position.
  • The proposed location of the strip is checked against the walls of the labyrinth by the Labyrinth.StripAllowedPosition() method.
  • If there are no problems with the proposed position of the strip, then this move or resizing is finalized; if not, then the ReturnCursor() method must return the cursor back without disturbing the strip itself.
public override bool MoveNode (int i, int dx, int dy, Point ptM, MouseButtons btn)
{
    if (btn == MouseButtons .Left)
    {
        … … 
        PointF [] pts = CornerPoints ();
        if (i == 0)
        {
            if (Auxi_Geometry .SameSideOfLine (ptC0, ptC1, ptM, pts [0]))
            {
                fDist = Auxi_Geometry .DistanceToLine (ptM, ptC0, ptC1);
                if (fDist >= minR)
                {
                    radiusNew = Convert .ToSingle (fDist);
                    if (!lab .StripAllowedPosition (ptC0, ptC1, radiusNew))
                    {
                        ReturnCursor ();
                        return (false);
                    }

The Labyrinth.StripAllowedPosition() method is used with nearly any movement (there is one exception!) and checks, if the proposed position of a strip is not going to be too close to the walls of a labyrinth. This check is based on a calculation of the distance between two segments. One is the segment of the wall; another is a segment between the centers of the strip’s semicircles. The distance from this second segment to the border of strip is equal to the radius of semicircles; if the distance between two segments is less than this radius, then such position of a strip is not allowed.

The use of the StripAllowedPosition() method in the MoveNode() method has one exception: it is not enough for the forward movement of the whole strip. For this case there is a similar StripAllowedMove() method, which includes an additional check, which prevents the really quick move of an bject through the wall.

The ReturnCursor() method temporarily cuts the link between the movement of the cursor and the movement of an object, returns the cursor back, and then reinstates the link. To return the cursor back, two parameters are used, which were saved by the InitialCatch() method: the distance between the mouse and the center of a strip and the compensation angle.

public void ReturnCursor ()
{
    center = Center;
    supervisor .MouseTraced = false;
    Cursor.Position = form.PointToScreen (Point.Round (Auxi_Geometry.PointToPoint (
                                Center, angle + compensation, rMouseFromCenter)));
    supervisor.MouseTraced = true;
}

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

SergeyAndreyev

United States United States
No Biography provided

Comments and Discussions

 
GeneralMy vote of 5 PinmemberMember 888886511-May-12 3:15 
Excellent...!!!!!!!!!!!!
Generalyou got my 5 PinmemberSouthmountain11-Nov-10 7:26 
GeneralProblem... PinmvpDave Kreskowiak10-Nov-10 11:02 
GeneralRe: Problem... PinmemberSergeyAndreyev10-Nov-10 20:26 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140922.1 | Last Updated 10 Nov 2010
Article Copyright 2010 by SergeyAndreyev
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid