views:

408

answers:

7

Hi,

I am building a graphic board like project where i am facing a design issue.

Main Class is Board which is a canvas responsible for handling mouse events when drawing shapes. It also has context variables such as currentShape or snapFlag to activate grid magnetism.

To handle the moving / resizing / rotating of the shapes, they inherit from a third party open source tool called ObjectHandles (flex).

I have a baseShape extending ObjectHandles main class to override some of its internal functions, like the onMove function.

When creating a shape (mouse down, move, mouse up) this is handle by the Board and it knows about his own snap flag.

var mouseUpPoint:Point = boardCanvas.globalToLocal(new Point(event.stageX, event.stageY)); var snapMouseUpPoint = snapPoint(mouseUpPoint.x, mouseUpPoint.y);

In my overidden onMove method i would like the shape to be aware of the Board snap flag and when its changing. How do i do this ?

Do i pass the Board as a parameter in my basicShape constructor so that i can check snap ?

Do i pass the flag as a parameter and somehow make all shapes listen for change ?

What is the cleanest solution ?

Thanks a lot.

+1  A: 

I would approach this from a slightly different angle. I assume that the Board object traps mouse events first, so that it can decide which shape has been clicked on. I would have the board trap mouse movements as well, passing the correct (snapped or unsnapped) coordinates "down" to the selected Shape object, rather than letting the shape object figure it out.

This leaves the grid snap handling to the Board, and keeps your Shape object onMove method free of clutter.

e.James
Actualy it does not, it receives it later. Each shape using objectHandles is selfaware of its selection. Objecthandles has a selectionManager which can select / unselect / add / remove.My board can however know when an object is moving. But its already too late.
coulix
Events goes from child to parent, board is the parent.
coulix
Well, to be honest, I don't think that is the right way to do it. Of course it is your project so you know a great deal more about the costs and benefits of such design choices. I stand by my answer, but I'm looking forward to reading the other responses. Great question!
e.James
A: 

Just trying to think together with you.. I see no big deal in Shapes having IBoard interface. Though, I don't like the idea that they have to check the flag on the board...

How would you pass the flag as parameter? In OnMove() method? didn't understood this quite well...could you expand?

Though.. If you try to think a bit about SRP - single responsibility principle...what is the responsibility of Shape classes? Yea, this is what eJames wrote already.

It feels to me that their main responsibility is probably NOT handling mouse events...here need to know more about your application, but my general feeling is why not someone else get this mouse down and then figure out what the shape should do with it and for instance call Draw() on the Shape with new coordinates?

Let's say you want to apply something like Composite pattern (Shapes inside shapes...) and you want them to be able to handle those mouse events themselves...but then Then it would be logical if they perceived this mouse event in their local coordinates, but then I think you should provide all the information through this event (local coordinates, mouse status...) so that they don't have to ask for "global" variables on the board...

badbadboy
A: 

Passing the flag as a parameter for the shape constructor. But it wont be good since flag is going to change and i have to make each shape to update their flag copy on change.

Its true that shape responsibility is not to know how to handle mouse events. But thats what ObjectHandles do: react to events, update height width rotation parameter of the shape.

Maybe i should transfer some of the library code in my board class to handle shape selection and movement / resizing / rotation.

coulix
You might not have to transfer too much of it. I would say that Board should do a fast, efficient check to see which shape rectangles the mouse click lands in, and then simply ask those shapes: "Is this mouse position inside your bounds?"
e.James
This way, the Board handles generic Shapes, and the Shapes themselves handle all of the gory details that make them unique from other shapes.
e.James
+1  A: 

Not knowing your app:

Is it ever possible for a Shape to have it's own 'snap' behavior? That is, could a Shape be excluded from snapping while others aren't? If so, make snapFlag a member of Shape. When snapFlag is set on the Board, iterate through your Shapes and set or don't set according to your rules.

If snapping behavior applies to all Shapes on the Board, consider an event-driven model (if it's available - I'm a Flex noob). When a Shape moves, have it raise an OnMove event. The Board can then respond and decide to 'snap' the Shape into place if it's appropriate.

If snap behavior applies to all Shapes and events aren't available, I'd just say the hell with loose coupling in this case - make the Shapes Board-aware. It sounds like you're saving a bunch of code by using the ObjectHandle. That benefit may out-weigh the cost of coupling your UI elements.

Corbin March
Hum i can try to answer the onmove event by updating the shape position to the snap mode. I will give it a try.
coulix
A: 

OnMouseMove ObjectHandles

     protected function onMouseMove(event:MouseEvent) : void
 {
  if( ! visible ) { return; }

  if( ! event.buttonDown )
  {
   setMouseCursor( event.stageX, event.stageY );
   return;
  }

  if(parent == null )
  {
   return;
  }


  var dest:Point = parent.globalToLocal( new Point(event.stageX, event.stageY) );
  var desiredPos:Point = new Point();
  var desiredSize:Point = new Point();
  var desiredRotation:Number = 0; 

... plenty more
then 

      if( wasMoved ) {    dispatchMoving() ; }
      if( wasResized ) {  dispatchResizing() ; }
      if( wasRotated ) {  dispatchRotating(); }

So i can not listen for move event and tell the board to snap it since the shape is already moving freely. I should add snap here:

var dest:Point = parent.globalToLocal( new Point(event.stageX, event.stageY) );

All shapes follow the snap rule there can not be one snapping and the other free.

coulix
I like that idea. If you already have a 'parent' ivar, why not make the parent of the top-level shapes be the board? When you call .globalToLocal on a Board object, it could convert that point to the snapped point if necessary.
e.James
Hum yes i should try to overide globalToLocal for the parent which is the board. Will post the answer when it works
coulix
A: 

Solved it this way:

Since i overridde onMouseMove in my baseShape class and i am using PureMVC framework, i just made baseShape aware of my boardMediator.

override protected function onMouseMove(event:MouseEvent) : void { [...]

// added on override
var board:BoardMediator = ApplicationFacade.getInstance().retrieveMediator(BoardMediator.NAME) as BoardMediator;

Then

desiredPos = board.snapPoint(desiredPos.x, desiredPos.y);

Maybe not super pretty but it works, o Overridding the globalToLocal method in my board view did work too but some more calculations were done inside onMouseMove resulting in an out of alignment snap move.

coulix
A: 

Use ObjectHandles Version 2, and then create a constraint to do what you want.

Marc Hughes