Sunday, August 1, 2010

BorderLayout example - with resizing

In a previous post, I examined how you can mimic Java's BorderLayout behavior in revTalk. At the end of that post, I promised to explain how you can add resizers to the West and East panels. Just as a reminder, here's what we had accomplished in the previous installment:



Before we get to the meat of the resizers, let's do some preparation work. To clearly show the user that he can drag around the resizers, we'll make a local copy of the vertical divide resize cursor that the RunRev team ships with the IDE. Open the message box and type:

copy image "vdividecursor.gif" of card id 1030 of stack "revMacCursors" to this card

We'll be using that as our cursor later, but it doesn't have to be visible for that to work - so you can hide it now. Next, we add a rectangular button, carefully placing it over the touching edges of the West and Center panels. Set the button's name to "WestResizer" and its width to 5, and make sure its height is the same as the West and Center panel. Here's what it should look like:



Before we add the script, we'll also add two custom properties to the West graphic:
-set the uMinWidth custom property to 75
-set the uMaxWidth custom property to 150

Now that we have the visuals worked out, let's script the resizer button:

local sIsResizing
local sMinX, sMaxX

on mouseEnter
lock cursor
set the cursor to the short id of image "vdividecursor.gif"
end mouseEnter

on mouseLeave
unlock cursor
end mouseLeave

on mouseDown
local tMouseOffset
put item 1 of the mouseLoc - item 1 of the location of me \
into tMouseOffset
put the uMinWidth of graphic "West" + tMouseOffset into sMinX
put the uMaxWidth of graphic "West" + tMouseOffset into sMaxX
put true into sIsResizing
pass mouseDown
end mouseDown

on mouseUp
put false into sIsResizing
pass mouseUp
end mouseUp

on mouseRelease
put false into sIsResizing
pass mouseRelease
end mouseRelease

on mouseMove x,y
if sIsResizing is true then
local tNewX
put min(max(x, sMinX), sMaxX) into tNewX
lock screen
set the left of me to tNewX - 3
set the widthFromLeft of graphic "West" to tNewX
dispatch "resizeCenterPanel" to this card
unlock screen
end if
pass mouseMove
end mouseMove

While it's not too long a script, some explanation is in order:
- the mouseEnter and mouseLeave messages are handled to update the cursor
- the mouseDown message is handled to setup a few script local variables:
-> the sIsResizing variable is used as a flag to track the status
-> the sMinX and sMaxX variables are calculated once, based on the offset between the mouse and the resizer button, and taking into account the minimum and maximum width we set earlier
-> calculating those once helps cut down on the math later on
- the mouseUp and mouseRelease messages are handled to turn off the sIsResizing flag
- the mouseMove message is handled to resize the West and Center panels
-> but only if the sIsResizing flag is true
-> it uses the sMinX and sMaxX variables to make sure we don't go too far either way
-> it updates the resizer button itself, the West panel, and the Center panel.

To make this work, we obviously also have to add a handler for our custom 'resizeCenterPanel' event. We'll add that to the card script in a bit, after we've finished the resizer for the East panel. So duplicate the WestResizer button, name the copy EastResizer and move it over to the right, placing it over the touching edges of the East and Center panels.

Before we add the script, we'll also add two custom properties to the East graphic:
-set the uMinWidth custom property to 100
-set the uMaxWidth custom property to 200

Next, update its script to:

local sIsResizing
local sMinX, sMaxX

on mouseEnter
set the lockCursor to true
set the cursor to the short id of image "vdividecursor.gif"
end mouseEnter

on mouseLeave
set the lockCursor to false
end mouseLeave

on mouseDown
local tMouseOffset
put item 1 of the mouseLoc - item 1 of the location of me \
into tMouseOffset
put the width of this stack - \
the uMinWidth of graphic "East" + \
tMouseOffset into sMaxX
put the width of this stack - \
the uMaxWidth of graphic "East" + \
tMouseOffset into sMinX
put true into sIsResizing
pass mouseDown
end mouseDown

on mouseUp
put false into sIsResizing
pass mouseUp
end mouseUp

on mouseRelease
put false into sIsResizing
pass mouseRelease
end mouseRelease

on mouseMove x,y
if sIsResizing is true then
local tNewX
put min(max(x, sMinX), sMaxX) into tNewX
lock screen
set the left of me to tNewX - 3
set the left of graphic "East" to tNewX
set the widthFromLeft of graphic "East" to \
the width of this stack - tNewX
dispatch "resizeCenterPanel" to this card
unlock screen
end if
pass mouseMove
end mouseMove

The difference with the WestResizer is that the EastResizer also has to take into account the width of the stack, but that's not too complicated. Finally we'll update the card script, adding a handler for the 'resizeCenterPanel' custom event, and we'll also take care of updating the resizer's location and height when the stack is resized. The new card script looks like this:

on resizeStack pNewWidth, pNewHeight
local tCenterHeight, tCenterWidth
--
lock screen
-- update the north and south panels
set the widthFromLeft of graphic "North" to pNewWidth
set the widthFromLeft of graphic "South" to pNewWidth
set the bottom of graphic "South" to pNewHeight
put the top of graphic "South" - \
the bottom of graphic "North" \
into tCenterHeight
-- update the west and east panels
set the heightFromTop of graphic "West" to tCenterHeight
set the heightFromTop of graphic "East" to tCenterHeight
set the right of graphic "East" to pNewWidth
put the left of graphic "East" - \
the right of graphic "West" \
into tCenterWidth
-- update the west and east resizers
set the heightFromTop of button "WestResizer" to tCenterHeight
set the heightFromTop of button "EastResizer" to tCenterHeight
set the left of button "EastResizer" to \
the left of graphic "East" - 3
-- update the center panel
set the widthFromLeft of graphic "Center" to tCenterWidth
set the heightFromTop of graphic "Center" to tCenterHeight
--
unlock screen
pass resizeStack
end resizeStack

--> custom event handlers

on resizeCenterPanel
local tCenterWidth, tCenterHeight
put the top of graphic "South" - \
the bottom of graphic "North" \
into tCenterHeight
put the left of graphic "East" - \
the right of graphic "West" \
into tCenterWidth
lock screen
set the left of graphic "Center" to the right of graphic "West"
set the top of graphic "Center" to the bottom of graphic "North"
set the widthFromLeft of graphic "Center" to tCenterWidth
set the heightFromTop of graphic "Center" to tCenterHeight
unlock screen
pass resizeCenterPanel
end resizeCenterPanel

--> helper property setters

setProp widthFromLeft pNewWidth
local tRectangle
--
lock screen
put the rectangle of the target into tRectangle
put item 1 of tRectangle + pNewWidth into item 3 of tRectangle
set the rectangle of the target to tRectangle
unlock screen
end widthFromLeft

setProp heightFromTop pNewHeight
local tRectangle
--
lock screen
put the rectangle of the target into tRectangle
put item 2 of tRectangle + pNewHeight into item 4 of tRectangle
set the rectangle of the target to tRectangle
unlock screen
end heightFromTop

And there we have it: a BorderLayout with resizers for the West and East panels.



Once you're happy with the way the resizers work, you can change the style of the WestResizer and EastResizer button to transparent. They'll continue to work just fine, but won't be visible. With a little more work, you can also add resizers for the North and South panels. But I'll leave that as an exercise to the reader...

No comments: