# PureBasic Forum

 It is currently Sat Jun 06, 2020 5:51 am

 All times are UTC + 1 hour

 Page 1 of 1 [ 2 posts ]
 Print view Previous topic | Next topic
Author Message
 Post subject: Sizing handles againPosted: Mon Jul 01, 2019 9:32 am
 Enthusiast

Joined: Thu Dec 29, 2011 5:03 pm
Posts: 580
Hi,

My quest to perfect sizing handles continues. I'm getting in such a mess with it.

The cursor test isn't accurate, especially when the shape is rotated. What do I need to adjust the mouse or handle position by based on the rotation?

How can I tell what to do with the sizing handle when the shape is rotated? For example, right now the code thinks it's always going to need to adjust the height, even when the handle is on the right. I'd really rather avoid doing something like this:
Code:
If a => 0 And a < 24
*b\handle\which = #HANDLE_N
*b\handle\which = #HANDLE_NE
*b\handle\which = #HANDLE_E
*b\handle\which = #HANDLE_SE
*b\handle\which = #HANDLE_S
*b\handle\which = #HANDLE_SW
*b\handle\which = #HANDLE_W
*b\handle\which = #HANDLE_NW
ElseIf a => 24 And a < 48
*b\handle\which = #HANDLE_NE
*b\handle\which = #HANDLE_E
; ...and so on...
ElseIf a => 48 And a < 72
ElseIf a => 72 And a < 96
ElseIf a => 96 And a < 120
ElseIf a => 120 And a < 144
ElseIf a => 144 And a < 168
ElseIf a => 168 And a < 192
ElseIf a => 192 And a < 216
ElseIf a => 216 And a < 240
ElseIf a => 240 And a < 264
ElseIf a => 264 And a < 288
ElseIf a => 288 And a < 312
ElseIf a => 312 And a < 336
ElseIf a => 336 And a < 360
EndIf

When the height of the shape is changed when the shape is rotated, the position of the shape also changes. How can I calculate what to add/subtract from the position to keep it in the same space?

Code:
EnableExplicit

CompilerIf #PB_Compiler_OS = #PB_OS_MacOS
Structure Point : x.i : y.i : EndStructure
CompilerEndIf

Procedure rotatePoint(*point.Point, *rotationCenterPoint.Point, degrees.F, *_return.Point)
Define pointX.f = *point\x - *rotationCenterPoint\x
Define pointY.f = *point\y - *rotationCenterPoint\y
*_return\x = newPointX + *rotationCenterPoint\x
*_return\y = newPointY + *rotationCenterPoint\y
EndProcedure

Structure Box
x.i : y.i
w.i : h.i
handleCur.Point
handleOrig.Point
angle.f
EndStructure

Global currentPoint.Point, lastPoint.Point, resize

Declare UpdateBoxHandle()

Global *b.Box = AllocateStructure(Box)
With *b
\x = 200 : \y = 150
\w = 100 : \h = 125
\handleCur\x = \x + (\w / 2)
\handleCur\y = \y
CopyStructure(@\handleCur, @\handleOrig, Point)
\angle = 45
EndWith
UpdateBoxHandle()

Procedure UpdateBoxHandle()
Protected handle.Point, center.Point, newPos.Point
handle\x = *b\x + (*b\w / 2)
handle\y = *b\y - 4
center\x = *b\x + *b\w / 2
center\y = *b\y + *b\h / 2
rotatePoint(@handle, @center, *b\angle, *b\handleCur)
*b\handleCur\x - 4
*b\handleCur\y - 4
EndProcedure

Procedure DrawCanvas()
Protected txt$txt$ = ~"Left/right to rotate\n\n0 to reset\n\nEscape to end\n\n\nAngle: " + Str(*b\angle) + Chr(176)
If StartVectorDrawing(CanvasVectorOutput(0))
VectorSourceColor(RGBA(0, 0, 0, 255))
FillVectorOutput()
VectorSourceColor(RGBA(255, 255, 255, 255))
MovePathCursor(20, 20)
DrawVectorParagraph(txt$, 200, 200) RotateCoordinates(*b\x + *b\w / 2, *b\y + *b\h / 2, *b\angle) AddPathBox(*b\x, *b\y, *b\w, *b\h) VectorSourceColor(RGBA(0, 0, 255, 255)) StrokePath(1) StopVectorDrawing() EndIf If StartDrawing(CanvasOutput(0)) Box(*b\handleCur\x, *b\handleCur\y, 8, 8, RGB(255, 255, 255)) Box(*b\handleOrig\x, *b\handleOrig\y, 8, 8, RGB(255, 165, 0)) DrawingMode(#PB_2DDrawing_Transparent) StopDrawing() EndIf EndProcedure Procedure OnCanvasEvents() Protected offset.Point, newY, newH, mousePos.Point, center.Point Select EventType() Case #PB_EventType_MouseMove currentPoint\x = GetGadgetAttribute(0, #PB_Canvas_MouseX) currentPoint\y = GetGadgetAttribute(0, #PB_Canvas_MouseY) If resize & GetGadgetAttribute(0, #PB_Canvas_Buttons) & #PB_Canvas_LeftButton offset\y = currentPoint\y - lastPoint\y *b\y + offset\y *b\h - offset\y UpdateBoxHandle() DrawCanvas() Else center\x = *b\x + *b\w / 2 center\y = *b\y + *b\h / 2 rotatePoint(@currentPoint, @center, -*b\angle, @mousePos) If mousePos\x => *b\handleOrig\x And mousePos\x < *b\handleOrig\x + 8 And mousePos\y => *b\handleOrig\y And mousePos\y < *b\handleOrig\y + 8 resize = 1 SetGadgetAttribute(0, #PB_Canvas_Cursor, #PB_Cursor_UpDown) Else SetGadgetAttribute(0, #PB_Canvas_Cursor, #PB_Cursor_Default) EndIf EndIf lastPoint\x = GetGadgetAttribute(0, #PB_Canvas_MouseX) lastPoint\y = GetGadgetAttribute(0, #PB_Canvas_MouseY) Case #PB_EventType_LeftButtonUp resize = 0 SetGadgetAttribute(0, #PB_Canvas_Cursor, #PB_Cursor_Default) Case #PB_EventType_KeyDown Select GetGadgetAttribute(0, #PB_Canvas_Key) Case #PB_Shortcut_Left *b\angle - 5 If *b\angle < 0 *b\angle = 359 EndIf If *b\angle > 359 *b\angle = 0 EndIf UpdateBoxHandle() DrawCanvas() Case #PB_Shortcut_Right *b\angle + 5 If *b\angle < 0 *b\angle = 359 EndIf If *b\angle > 359 *b\angle = 0 EndIf UpdateBoxHandle() DrawCanvas() Case #PB_Shortcut_0 *b\angle = 0 UpdateBoxHandle() DrawCanvas() Case #PB_Shortcut_Escape : End DrawCanvas() EndSelect EndSelect EndProcedure If OpenWindow(0, 0, 0, 640, 480, "", #PB_Window_SystemMenu | #PB_Window_ScreenCentered) CanvasGadget(0, 0, 0, 640, 480, #PB_Canvas_Keyboard) BindGadgetEvent(0, @OnCanvasEvents(), #PB_All) DrawCanvas() SetActiveGadget(0) Repeat : Until WaitWindowEvent() = #PB_Event_CloseWindow EndIf I would really appreciate any help. Thank you. Top  Post subject: Re: Sizing handles againPosted: Mon Jul 01, 2019 6:18 pm  PureBasic Expert Joined: Wed Oct 29, 2003 4:35 pm Posts: 10587 Location: Beyond the pale... I enjoyed this one! Must admit that I would have probably done this with some good old fashioned belt and braces style trig and vector geometry (I have used a little), but I followed your lead somewhat. I haven't really tried to debug your code, though I do suspect that you were moving the centre of rotation during the resize which is a massive no-no when resizing just one dimension of the rectangle. Not sure why your hit-testing wasn't working, but like I said, I didn't want to debug and get myself in a tizzy! If you run the following you will see the original rectangle laid out with a dashed red line so you can compare and contrast. I've also displayed the centre of rotation. Use the left cursor key to rotate anti-clockwise etc. You will note that following a resize I move the original rectangle to match the new centre point. This seemed logical to me, but you can easily change that. If you do, and do not move the centre point following a resize, then it will be possible to subsequently rotate the rectangle about an exterior point which will cause the said rectangle to dance about the screen like fangbeast during the high point of a summer solstice! You would need to add some validation code to limit the original drag/resize etc. Hope it helps. Code: ;This code shows one way of allowing the user to resize one side of a possibly rotated rectangle. This is not a scaling of the rectangle ;in question, but a one-way stretch. ;We basically record info on the original (vertical) rectangle together with an appropriate angle. When resizing the rotated rectangle, ;we simply resize the vertical one in an appropriate manner and rotate when rendering to a canvas etc. careful to keep the centre of rotation ;intact during the resize. MUST NOT move the centre of rotation during the resize! ;At the conclusion of the resize we then move the centre (and thus the matching vertical rectangle) to match the newly rotated/resized object. ;This allows for seemless repeated resizing/rotating etc. Without this there will be a lot of jumping about and, in some cases, the rectangle ;will rotate about an external point! Can easily be changed mind. ;We draw the original rectangle (outlined red dash) for reference purposes. ;Left cursor key to rotate anti-clockwise. Right cursor key to rotate clockwise. CompilerIf #PB_Compiler_OS = #PB_OS_MacOS Structure POINT x.i y.i EndStructure CompilerEndIf ;The following structure wraps up our rectangle object. We store information only on the underlying vertical rectangle ;whose position depends on the rotation in effect etc. That is, these fields record the original un-rotated rectangle. Structure BOX ;(x, y) refer to the top-left point which can change during resizing. x.i y.i width.i height.i ;The following record the centre of the original rectangle and will change ONLY after each resize (not during!) cenX.i cenY.i ;The following field gives the rotation in degrees from the positive x-axis. A positive value represents a clockwise rotation. angle.i EndStructure ;We use the following structure to wrap up some of our drag fields. Structure RESIZEDRAG blnIsResizing.i originalY.i originalHeight.i originalCursorPos.i delta.i ;Tells us how much the cursor has moved (in the relevant direction) since the resize was instigated. EndStructure ;Create a box to play around with. Global gBox.BOX With gBox \x = 280 \y = 180 \width = 80 \height = 120 ;We have to set the coordinates of the centre manually at this stage. We don't do this dynamically since the centre coordinates change ;ONLY at the conclusion of a resize - not during! \cenX = \x + \width/2 \cenY = \y + \height/2 \angle = 0 EndWith Global gResize.RESIZEDRAG ;The following procedure rotates the given coordinate ;Angle must be specified in degrees. ;A positive angle rotates clockwise. Procedure.i RotatePoint(*pt.POINT, cenX, cenY, angle) Protected newX.d, newY.d, rAngle.d rAngle = Radian(angle) newX = cenX + Cos(rAngle) * (*pt\x - cenX) - Sin(rAngle) * (*pt\y - cenY) newY = cenY + Sin(rAngle) * (*pt\x - cenX) + Cos(rAngle) * (*pt\y - cenY) *pt\x = newX *pt\y = newY EndProcedure Procedure DrawRotatedRectangle() Protected cenX, cenY If StartVectorDrawing(CanvasVectorOutput(0)) ;Erase. VectorSourceColor($FFFFFFFF)
FillVectorOutput()
;Draw a dashed outline of the 'original' box for reference.
VectorSourceColor(RGBA(255, 0, 0, 255))
DashPath(1, 4)
;Visually mark the centre of rotation.
AddPathBox(gBox\cenX - 2, gBox\cenY - 2, 4, 4)
FillPath()
;Draw the rotated box.
;Set rotation.
RotateCoordinates(gBox\cenX, gBox\cenY, gBox\angle)
VectorSourceColor(RGBA(0, 0, 255, 255))
StrokePath(1)
;Draw a single sizing box.
VectorSourceColor(RGBA(0, 0, 0, 255))
AddPathBox(gBox\x + gBox\width/2 - 4, gBox\y - 4, 8, 8)
FillPath()
StopVectorDrawing()
EndIf
EndProcedure

Procedure OnCanvasEvents()
Protected pt.POINT, movedY
Select EventType()
Case #PB_EventType_LeftButtonDown ;Check the cursor position and, if over the resize handle, instigate a resize.
;Counter rotate the cursor position.
RotatePoint(pt, gBox\cenX, gBox\cenY, -gBox\angle)
;Are we atop the unrotated handle?
If pt\x >= gBox\x + gBox\width/2 -4 And pt\x <= gBox\x + gBox\width/2 + 4 And pt\y >= gBox\y - 4 And pt\y <= gBox\y + 4
;Instigate a resize action.
With gResize
\blnIsResizing = #True
\originalY = gBox\y
\originalHeight = gBox\height
\originalCursorPos = pt\y
\delta = 0
EndWith
EndIf

Case #PB_EventType_MouseMove
If gResize\blnIsResizing
;Calculate how far the user has moved the cursor since starting the resize.
;Counter rotate the cursor position.
RotatePoint(pt, gBox\cenX, gBox\cenY, -gBox\angle)
movedY = pt\y - gResize\originalCursorPos
gBox\y = gResize\originalY + movedY
gBox\height = gResize\originalHeight - movedY
;Redraw.
DrawRotatedRectangle()
Else  ;We set the cursor appropriately if the cursor is over the handle.
;For simplicity we just use a cross-hair cursor.
;Counter rotate the cursor position.
RotatePoint(pt, gBox\cenX, gBox\cenY, -gBox\angle)
;Are we atop the unrotated handle?
If pt\x >= gBox\x + gBox\width/2 -4 And pt\x <= gBox\x + gBox\width/2 + 4 And pt\y >= gBox\y - 4 And pt\y <= gBox\y + 4
Else
EndIf
EndIf

Case #PB_EventType_LeftButtonUp
If gResize\blnIsResizing
;Reset the rectangle structure ready for the next rotation etc.
;We move the vertical rectangle so that it's centre matches that of the newly resized (and possible rotated) rectangle.
;First rotate the centre of the newly sized rectangle about the original centre point. This will give us the centre point
;of the rotated and resized rectangle.
pt\x = gBox\x + gBox\width/2
pt\y = gBox\y + gBox\height/2
RotatePoint(pt, gBox\cenX, gBox\cenY, gBox\angle)
;Can now set the new centre point.
gBox\cenX = pt\x
gBox\cenY = pt\y
;Now we can reset the (x, y) of the top-left of the matching vertical rectangle.
gBox\x = gBox\cenX - gBox\width/2
gBox\y = gBox\ceny - gBox\height/2
;Clear the resizing flag.
gResize\blnIsResizing = #False
;Redraw the canvas to show the revamped original rectangle (in red dash lines).
DrawRotatedRectangle()
EndIf

Case #PB_EventType_KeyDown
Case #PB_Shortcut_Left
gBox\angle - 5
DrawRotatedRectangle()
Case #PB_Shortcut_Right
gBox\angle + 5
DrawRotatedRectangle()
EndSelect
EndSelect
EndProcedure

If OpenWindow(0, 0, 0, 640, 480, "", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
CanvasGadget(0, 0, 0, 640, 480, #PB_Canvas_Keyboard)
DrawRotatedRectangle()
Repeat : Until WaitWindowEvent() = #PB_Event_CloseWindow
EndIf

_________________
I may look like a mule, but I'm not a complete ass.

Top

 Display posts from previous: All posts1 day7 days2 weeks1 month3 months6 months1 year Sort by AuthorPost timeSubject AscendingDescending
 Page 1 of 1 [ 2 posts ]

 All times are UTC + 1 hour

#### Who is online

Users browsing this forum: Majestic-12 [Bot], VB6_to_PBx and 11 guests

 You cannot post new topics in this forumYou cannot reply to topics in this forumYou cannot edit your posts in this forumYou cannot delete your posts in this forum

Search for:
 Jump to:  Select a forum ------------------ PureBasic    Coding Questions    Game Programming    3D Programming    Assembly Programming    The PureBasic Editor    The PureBasic Form Designer    General Discussion    Feature Requests and Wishlists    Tricks 'n' Tips Bug Reports    Bugs - Windows    Bugs - Linux    Bugs - Mac OSX    Bugs - IDE    Bugs - Documentation OS Specific    AmigaOS    Linux    Windows    Mac OSX Miscellaneous    Announcement    Off Topic Showcase    Applications - Feedback and Discussion    PureFORM & JaPBe    TailBite