Analysis
Overview
Currently frames can be resized using the Frame Size and Position halo button and hud. The user specifies new width and height, the frame content is resized accordingly and the frame location (top left corner) remains the same.
It would be convenient for the user to have another way of resizing the frame in addition to this one - by dragging the frame bounds with the mouse.
Task requirements
- There should be an invisible rectangular area around the border outline (the middle of the border) which could be used for resizing the frame.
- It should be divided into 8 subareas resposible for resizing in the 8 different directions - north, south, east, west and the intermediate north-east, north-west, south-west and south-east.
- When the user clicks on one of the subareas and starts dragging, the frame is resized in the appropriate way - if necessary its position is also changed. The opposite area is preserved. So if the user drags NW area the position and the size are changed so that SE area stays in place.
- The frame is resized while the user is dragging, not after he/she releases the mouse. Also if settings HUD is opened the data inside it should be updated before the mouse release. (This could be dropped for the next revision )
Task result
The result of this page should be source code.
Implementation idea
The 8 subareas could be 8 different scene elements (children of the frame scene element) with different responsible areas.
Related
How to demo
- Open Sophie application
- Create a new frame (populate content inside)
- Resize it.
Design
As described in the analysis, the resize area will be a thin rectangular area around the border outline and will be divided into 8 subareas.
A new inner class of FrameView will be created to represent them - ResizeAreaElement.
It will have one important property - the position of the element relative to the border rectangle, i.e. top left, top middle, top right, etc. (described in the Position enumeration).
Using the position we can calculate the responsible area of the element in the following way:
- construct the whole resize area - a rectangular area around the border
ImmRect outer = new ImmRect(midBorder.getX() - THRESHOLD, midBorder.getY() - THRESHOLD, midBorder.getWidth() + 2 * THRESHOLD, midBorder.getHeight() + 2 * THRESHOLD); ImmRect inner = new ImmRect(midBorder.getX() + THRESHOLD, midBorder.getY() + THRESHOLD, midBorder.getWidth() - 2 * THRESHOLD, midBorder.getHeight() - 2 * THRESHOLD); ImmArea totalArea = new ImmArea(outer).subtract(new ImmArea(inner));
- keeping an appropriate point from the outer rectangle (according to the position property), construct a new one 3 times smaller than it
ImmPoint posPoint = outer.getPoint(position().get()); ImmRect zoneGross = new ImmRect(position().get(), posPoint, new ImmSize (outer.getWidth() / 3, outer.getHeight() / 3));
- find the intersection of the two areas constructed above
The position determines not only the shape and location of the element, but also its behaviour - whether it can change the frame's width and height and horizontal or vertical position.
8 ResizeAreaElements will be added to FrameView - top left, top middle, top right, middle left, middle right, bottom left, bottom middle and bottom right.
They will also be added as subelements of its root scene element (in FrameView.sceneElement().sceneElement.compute().new DefaultCompositeSceneElement() {...}.setupElements()). It is important that they should be added after the border element, because otherwise it would obscure them.
When the user starts dragging, a MouseEvent will be fired and wrapped in an InputEventR3, which will be again wrapped in an EventR3 by SceneInteractionLogic.
A new controller will be created to handle it - FrameLogicR3 in org.sophie2.main.app.commons.frames package.
It will consist of only one operation - RESIZE_START with filter InputEventR3.MOUSE_PRESSED as eventID and FrameView.ResizeAreaElement.class as source class. In the handle method it will create a new MouseCapture that will actually process dragging in the following way:
- receive the coordinates of the resize vector (in scene coordinates)
- convert them to frame coordinates
- according to the position of the resize area calculate some coefficients: changeX, changeY, changeWidth and changeHeight
- calculate the size and location of the frame
- convert the frame location to page coordinates and set the new bounds
In fact, before creating the MouseCapture, we'll save the original bounds and always use them instead of intermediate ones, because otherwise the error accumulated will be too big.
In order to be able to convert from frame to page coordinates after resizing, a few changes to the rotation were necessary.
- In the model rotation will be possible only with center the top left vertex. The rotationPosition and rotationCenter properties will be removed from Frame.
The user may want rotation with a different center and the View will be responsible for the necessary transformations. It turns out that rotation with an arbitrary center is equivalent to rotation around contentLocation with the same angle + translation with the vector (newContentLocation - contentLocation), where newContentLocation is the point where contentLocation is sent by the original rotation. So all we need to do is calculate newContentLocation and set it to Frame.contentLocation and update Frame.rotationAngle. - rotationPosition and rotationCenter properties will be added to FrameRotationPropertiesHud.
- The rotation logic will be moved from the rotation halos to a controller.
An enumeration consisting of a single event id will be created in FrameRotateHaloMenu.
A new controller will be created in org.sophie2.main.app.halos.frame.rotate package - FrameRotateLogicR3 with a single operation - rotate.
It will be registered in MainAppHalosModule.
For testing purposes we need to add test mode to MouseCapture. When in this mode it will not add a listener, but will allow us to execute its methods manually.
- a new boolean field inTestMode (false by default) with getter and setter
- a new MouseCapture field lastInstance with getter
- In the constructor we should check the mode. If in test mode, no listener should be added and the lastInstance field should be set to this (the current instance of MouseCapture
- The methods should move(int, int) and released() should be made public
Tests: [3222]
Implementation
Done according to the design...
For now the top-left, top-right, bottom-left and bottom-right resize areas have a fixed width and height of 15.
The minimal frame size is (40, 40).
Changesets: [3000], [3020], [3056], [3064], [3119], [3136], [3187], [3222], [3259], [3266], [3310], [3351]
Merged to the trunk in [3558].
Comments
- In frame HUD there should be lock button that locks the width to height. This may not be part of this revision, but is important.
- Please make sure the behavior is clear when the frame is rotated for example.