Using a group's handles to resize controls within group

Anything beyond the basics in using the LiveCode language. Share your handlers, functions and magic here.

Moderators: FourthWorld, heatherlaine, Klaus, kevinmiller, robinmiller

tjm167us
Posts: 50
Joined: Sat Dec 03, 2011 8:27 pm
Location: San Ramon, CA

Using a group's handles to resize controls within group

Post by tjm167us » Sun Oct 20, 2013 11:07 pm

Hello,
I have made a searchable text field control that I want to be able to drag out of a palette and place on a stack (like a normal textField, but with additional functionality). Before I can do this, I need to allow the control to be resized by the user (so once they drag it out, they can make it fit in their stack). The drag handles that appear when in edit mode are for the group, but I get some really strange behavior when I try to set the text field width and height in a resizeControl handler within the group script. Has anyone successfully done this?

You can setup the following simplified test to demonstrate the problem I am having:

1. Create a button "theButton" and a field "theField"
2. Group them and name the group "theGroup"
3. While in edit mode, grab the right-edge handle and increase the width of the group. Notice the default behavior is to increase the width of the group in the direction you are dragging.
4. Grab the bottom-edge handle and increase the height of the group. The default behavior is to increase the height of the group in the direction you are dragging.

5. Within the group script, place the following code:

Code: Select all

on resizeControl
   set the width of field "theField" to (the width of me)
   pass resizeControl
end resizeControl
6. Drag the right edge handle to increase the width of the group just like step 3. This time, I expected the SAME behavior, but with the width of the field also increasing in the direction of the dragging. Instead, the group and the field increase in size, but the left edge isn't fixed like before.

7. Drag the bottom edge handle to increase the height of the group. Instead of increasing the height of just the group, the width of the group (and the text field) increases. This is really odd to me, because the width of the group shouldn't be increasing when the bottom-edge handle is dragged.

The only thing I can surmise is that the size of the group rect is updated by the engine when you change the dimensions of the controls within the group. But since I am then setting the dimensions of the controls within the group based off of the groups dimensions, things are getting confused.

Well, I'm at least confused!
Thanks,
Tom

bn
VIP Livecode Opensource Backer
VIP Livecode Opensource Backer
Posts: 4003
Joined: Sun Jan 07, 2007 9:12 pm
Location: Bochum, Germany

Re: Using a group's handles to resize controls within group

Post by bn » Mon Oct 21, 2013 12:24 am

Hi Tom,

your script fights the group on two accounts
setting the width of an object increases the width to both sides, the loc stays the same. That is the reason to get the topLeft or any other reference point first and set topLeft of the field to that after changes in width.

You set the width of the field to the width of the group, I assume the group has the default margins of 4. So the group increases its width to accomodate the margins.

this 'mostly' works for me (it is not totally smooth and you would have to catch if the user tries to set the height of the field to a negative value if the height of the group gets too small)

Code: Select all

on resizeControl
   put the topLeft of field "theField" into tTopLeft
   lock screen
   lock messages
   set the width of field "theField" to (the width of me - the leftMargin of me - the rightMargin of me)
   set the height of field "theField" to the the bottom of me - the bottomMargin of me - item 2 of tTopLeft
   set the topLeft of field "theField" to tTopLeft
   unlock messages
   unlock screen
   pass resizeControl
end resizeControl
not quite sure the lock messages is necessary.
Maybe you are better off to make your own handle and you don't have to fight the group.

Kind regards
Bernd

bn
VIP Livecode Opensource Backer
VIP Livecode Opensource Backer
Posts: 4003
Joined: Sun Jan 07, 2007 9:12 pm
Location: Bochum, Germany

Re: Using a group's handles to resize controls within group

Post by bn » Mon Oct 21, 2013 12:37 am

Hi Tom,

I changed the script to decouple resizing control and setting the width and height of the field by using send in time, works a lot smoother.

Code: Select all

on resizeControl
   put the width of me into tWidth
   put the bottom of me into tBottom
   send "doSetSizes tWidth, tBottom" to me in 0 milliseconds
end resizeControl

on doSetSizes pWidth, pBottom
   put the topLeft of field "theField" into tTopLeft
   lock screen
   lock messages
   set the width of field "theField" to (pWidth - the leftMargin of me - the rightMargin of me)
   set the height of field "theField" to pBottom - the bottomMargin of me - item 2 of tTopLeft
   set the topLeft of field "theField" to tTopLeft
   unlock messages
   unlock screen
end doSetSizes
you would still have to watch out for the height of the field if the user makes the group too small and I omitted the pass resizeControl.

Kind regards
Bernd

tjm167us
Posts: 50
Joined: Sat Dec 03, 2011 8:27 pm
Location: San Ramon, CA

Re: Using a group's handles to resize controls within group

Post by tjm167us » Mon Oct 21, 2013 1:05 am

Bernd,
Thank you! I should have asked before I spent so much time confused :)
As always, I appreciate your prompt, accurate, and courteous responses.
Cheers,
Tom

tjm167us
Posts: 50
Joined: Sat Dec 03, 2011 8:27 pm
Location: San Ramon, CA

Re: Using a group's handles to resize controls within group

Post by tjm167us » Tue Oct 22, 2013 12:14 am

Hello,
I've made some progress since Bernd helped, but I am now getting the group "fighting" my code when I resize the group (using the top-edge handle this time).

This is my code in "theGroup" group script:

Code: Select all

on resizeControl
   put the bottomRight of field "theField" into theAnchor    
   set the height of field "theField" to .85*((the height of me) - the bottomMargin of me - the topMargin of me)
   set the bottomRight of field "theField" to theAnchor
end resizeControl
You can tell that the group is "fighting" what I'm doing by the flicker when you drag the top-edge handle up or down, or drag and release. Once you release, the group resizes to the top of the highest control.

Please note: If you change the multiplier from .85 to 1.0, the problem goes away...

Thanks,
Tom

tjm167us
Posts: 50
Joined: Sat Dec 03, 2011 8:27 pm
Location: San Ramon, CA

Re: Using a group's handles to resize controls within group

Post by tjm167us » Tue Oct 22, 2013 1:38 pm

I still hope someone is able to answer my previous question, but it would be very helpful to me if someone can answer these questions:

1. How can I "disable" the default group resizing behavior?
2. Where does the default behavior script reside relative to the message path (i.e. is this behavior done in a frontscript, backscript, in the engine?)

Thanks,
Tom

bn
VIP Livecode Opensource Backer
VIP Livecode Opensource Backer
Posts: 4003
Joined: Sun Jan 07, 2007 9:12 pm
Location: Bochum, Germany

Re: Using a group's handles to resize controls within group

Post by bn » Tue Oct 22, 2013 4:58 pm

Hi Tom,

I am currently on a train.

Could you in the meantime maybe explain (and post a zipped sample stack) of what controls your group should contain and what the limits of movement are.

I did not test to grab the upper handles, just tried to change to the right bottom and bottom/right.

Did you notice that the user could move the whole group while in editing mode?

Maybe it is time to rethink the whole approach. That is why I ask for a little more information.

If you could to with a custom handle everything would be easy.

I don't know the answer to your 2 questions but either way I would not even think of interfering / modifying those.

Kind regards

tjm167us
Posts: 50
Joined: Sat Dec 03, 2011 8:27 pm
Location: San Ramon, CA

Re: Using a group's handles to resize controls within group

Post by tjm167us » Tue Oct 22, 2013 5:16 pm

Bernd,
Thank you - I will post a sample stack with some additional information when I get home (at work for the next 4 hours).
Cheers,
Tom

bn
VIP Livecode Opensource Backer
VIP Livecode Opensource Backer
Posts: 4003
Joined: Sun Jan 07, 2007 9:12 pm
Location: Bochum, Germany

Re: Using a group's handles to resize controls within group

Post by bn » Tue Oct 22, 2013 8:34 pm

Tom,

here is a stack that has 3 field and their size and position are controlled by a handle.

You might want to have a look at that and tell me if it is somewhat close to what you want.

Kind regards

Bernd
Attachments
ResizeGroupTom_0.0.4.livecode.zip
(2.5 KiB) Downloaded 215 times

tjm167us
Posts: 50
Joined: Sat Dec 03, 2011 8:27 pm
Location: San Ramon, CA

Re: Using a group's handles to resize controls within group

Post by tjm167us » Wed Oct 23, 2013 12:02 am

Bernd, that's nerdy awesome! That is exactly the type of behavior I want, however, I want the user to be able to resize using any of the 8 built-in drag handles. More in-depth detail and an update on my progress is below:

My overall goal is to create a searchable text-field control that can be dragged out from a palette and placed onto a stack to use. I want this control to as closely resemble a built-in livecode control so I feel it is necessary to use the built-in handles to control the size of the objects within the group.

The searchable text field is fully functional (and well encapsulated), so I can copy it and paste it to create another instance. However, to go the final step, I need to allow the developer to do the resizing stuff (without having to edit the group). This is where your help started!

I made some good progress on the resizing stuff today, and I think it will work. Here is my general approach:

1. Create a handler within the searchable text field group to handle the resizeControl message.
2. Within this handler, determine which handle (of which there are eight) the user is dragging to resize the control
3. Select the proper reference point given the handle that is being used. For example, to properly resize the text control when the user drags the right-edge drag handle, the reference needs to be either topLeft or bottomLeft corner of the text field. If the user drags the left-edge drag handle, the reference needs to be either the topRight or the bottomRight corner of the text field.
4. Resize and reposition each one of the controls

This is much easier to "say" I'm going to do then actually "do", because of the lack of visibility into how and when LiveCode decides to resize the group.

So, once this proved harder to do than anticipated, I have spent a lot of my time figuring out when LiveCode does choose to resize the group, and have written my code to make sure it doesn't interfere with LiveCode's built-in behavior. To address the "fighting" that was occurring on the top-edge handle, I have done the following:

Code: Select all

         
on resizeControl
            put the bottomRight of field "theField" into theAnchor    
         --set the width of field "theField" to (pWidth - the leftMargin of me - the rightMargin of me)         
            set the lockScreen to true
           
            
            put the bottomRight of field "theField" into theAnchor
         --Calculate the new height of the field "theField"
            put the height of me - the topMargin of me - the bottomMargin of me - 25 into theNewHeight
            if theNewHeight > 75 then      //Don't allow the textfield to get less than 75 in height

            --Setting the position of the button first allows LiveCode to properly resize the group
            --and then given the new size, I set the new height of my field "theField"

                  set the top of button "theButton" to the top of me + the topMargin of me
                  set the height of field "theField" to theNewHeight
                  set the bottomRight of field "theField" to theAnchor
            else
            --Need some help here
                  put the bottomRight of me into it
                  set the height of me to 120
                  set the bottomRight of me to it
                  set the top of button "theButton" to the top of me + the topMargin of me
                  --set the lockLocation of me to true
                 end if
            set the lockScreen to false
            pass resizeControl
end resizeControl
Please note, this is actually not in the resizeControl handler, but rather in the "DoSetSizes" handler. Additionally, this is the code to work for just the top edge handle.

There is still some funny behavior present once you reach the limit (see the "Need some help here" comment above), but otherwise, you get a smooth increase and decrease in the size of the field "theField" when you drag the top-edge handle What I really want to do is lock the position of the group if the user tries to make it a smaller size than allowed, however, this doesn't take effect until after the user stops trying to resize the group. Any ideas of how I can tastefully restrict the size of the group while using the built-in resizing handles?

Cheers,
Tom

bn
VIP Livecode Opensource Backer
VIP Livecode Opensource Backer
Posts: 4003
Joined: Sun Jan 07, 2007 9:12 pm
Location: Bochum, Germany

Re: Using a group's handles to resize controls within group

Post by bn » Wed Oct 23, 2013 10:10 pm

Hi Tom,

here is a modification of your code that works for me using the built in handles at top middle and topLeft, not in the one at the topRight. But you said you can find out which handle is clicked at (I wonder how).

Since you don't give any details on the rest of you code I will just post this partial solution, which on top is partly hard coded to what I imagine you want. It would certainly help if you would post just a stack with the objects (no need for them to have their specific code). Just the level of groups/position and max and min dimensions of the objects, restraints regarding width etc.
Coding resizing handlers is a lot of hand crafting. Too many specific things going on.

Code: Select all

on resizeControl
   
   put the bottomRight of field "theField" into theAnchor  
   --set the width of field "theField" to (pWidth - the leftMargin of me - the rightMargin of me)   
   set the lockScreen to true
   lock messages
   
   
   put the bottomRight of field "theField" into theAnchor
   --Calculate the new height of the field "theField"
   put the height of me - the topMargin of me - the bottomMargin of me - 25 into theNewHeight
   put the bottomRight of me into tGroupBotRight
   if theNewHeight >= 75 then   //Don't allow the textfield to get less than 75 in height
      
      --Setting the position of the button first allows LiveCode to properly resize the group
      --and then given the new size, I set the new height of my field "theField"
      
      set the top of button "theButton" to the top of me + the topMargin of me
      set the height of field "theField" to theNewHeight
      set the bottomRight of field "theField" to theAnchor
   else
      --Need some help here
      set the height of field "theField" to 87
      set the bottomRight of field "theField" to theAnchor
      set the bottom of button "theButton" to the top of field "theField" - 2
      
      set the height of me to 120
   end if
   if the width of me <> 167 then set the width of me to 167 -- change width to your width
   set the bottomRight of me to tGroupBotRight
   set the lockScreen to false
   pass resizeControl
end resizeControl
Kind regards
Bernd

bn
VIP Livecode Opensource Backer
VIP Livecode Opensource Backer
Posts: 4003
Joined: Sun Jan 07, 2007 9:12 pm
Location: Bochum, Germany

Re: Using a group's handles to resize controls within group

Post by bn » Wed Oct 23, 2013 11:55 pm

Hi Tom,

I think I got it

have a look at the stack. It prevents changes of width, you can change height by top and bottom handles, side handles are non-functional due to width constraints. You can change the width of the field in a constant and the min height of the field in another constant. Also you can change the distance between button and field in a constant. All constants are defined at the top of the group script.

The only flaw I could detect is if you drag from the handle while the group is fixed and reach the opposite handle area the whole group can jump. But I think that is not really an issue.

don't ask me why the code works, a lot of experimentation... :) There might be some redundancies in the code, but I am too tired now to check, the code does not like fiddling around... boy these circular conditions, I don't like them.
But I definitely see why you want to use the native resize handles since it is some sort of toolbox as far as I gather.

I hope this is what you wanted to do

Kind regards
Bernd


Kind regards
Bernd
Attachments
resizeGroupAndObject_0_2.livecode.zip
(1.46 KiB) Downloaded 212 times

bn
VIP Livecode Opensource Backer
VIP Livecode Opensource Backer
Posts: 4003
Joined: Sun Jan 07, 2007 9:12 pm
Location: Bochum, Germany

Re: Using a group's handles to resize controls within group

Post by bn » Thu Oct 24, 2013 12:06 am

Hi Tom,

I forgot to mention that you might notice that I use e.g.

Code: Select all

field "theField" of me
this ties the reference to the current owner, i.e. the group where the script resides. This is in case you want to place two of the same widgets on the same card. If you don't specify "of me" then it would reference the control with the lowest layer, which could be in another one of widgets.

Kind regards
Bernd

tjm167us
Posts: 50
Joined: Sat Dec 03, 2011 8:27 pm
Location: San Ramon, CA

Re: Using a group's handles to resize controls within group

Post by tjm167us » Thu Oct 24, 2013 2:13 pm

Bernd,
I'm glad to see I have not been the only one obsessively experimenting with this stuff! It's been a lot of fun having someone else working on the problem, so thank you!

I haven't posted the stack because I felt it was sufficient to demonstrate the concepts with just a field and a button. Once that is done, one can apply the same idea to any number of controls. Additionally, I don't expect you to give me a turn key solution (what would be the fun in that?!).

Using the code below is a really good idea, and one that I will surely follow.

Code: Select all

field "theField" of me
I am very close to getting all 8 handles resizing properly, and when I do, I will send the code your way (possibly off-line?). I love posting here to help people answer their questions, but I am reticent to post code people will just copy-paste and not take any effort to understand.

The combination of my code and your code will lead to what I think will be an optimum solution. For example, your size limiting code is exactly what I was hoping to be able to do while I have handled the exception you alluded to when you said:
The only flaw I could detect is if you drag from the handle while the group is fixed and reach the opposite handle area the whole group can jump. But I think that is not really an issue.
The algorithm for detecting which handle is being used to resize was pretty simplistic, but it works! The basic idea behind it is:
1. Within the resizeControl handler, get the mousePosition (used mouseH and mouseV).
2. Run a shortest distance algorithm between the mousePosition and the handle coordinates

In code, it looks like this:

Code: Select all

on resizeControl
   put the mouseH into theMouseH
   put the mouseV into theMouseV
   put the right of me into theRight
   put the left of me into theLeft
   put the top of me into theTop
   put the bottom of me into theBottom
   put (theRight+theLeft)/2 into theLRMiddle
   put (theTop + theBottom)/2 into theTBMiddle
   
   put theLeft&comma&theLRMiddle&comma&theRight into theColumnList
   put theTop&comma&theTBMiddle&comma&theBottom into theRowList
  
   put closestToIndex(theRowList, theMouseV)&comma&closestToIndex(theColumnList, theMouseH) into theHandle
   
   --Tell me which reference point I should use
   switch theHandle
      case "1,1"
         put "upper left"&return into theMsg
         break
      case"1,2"
         put "upper middle"&return into theMsg
         break
      case "1,3"
         put "upper right"&return into theMsg
         break
      case "2,1"
         put "middle left"&return into theMsg
         break
      case "2,3"
         put "middle right"&return into theMsg
         break
      case "3,1"
         put "lower left"&return into theMsg
         break
      case "3,2"
         put "lower middle"&return into theMsg
         break
      case "3,3"
         put "lower right"&return into theMsg
         break
   end switch
   put theMsg into msg
   
end resizeControl

function closestToIndex theList, theVal
   put 1 into theLoopCounter
   repeat for each item tItem in theList
      put tItem into theArray[theLoopCounter]
      add 1 to theLoopCounter
   end repeat
   put theVal into theArray["Val"]
   put the keys of theArray into theKeys
   sort theKeys ascending numeric by theArray[each]
   put lineOffset("Val",theKeys) into theOffset
   switch theOffset
      case 1
         put 1 into theIndex
         break
      case (the number of lines in theKeys)
         put the number of lines in theKeys-1 into theIndex
         break
      default
         put theArray[theOffset-1] into firstNum
         put theArray[theOffset] into secondNum
         --if there is an equal distance between theVal and the two numbers next to it, round down
         if abs(theVal-firstNum) <= abs(theVal-secondNum) then
            put theOffset-1 into theIndex
         else
            put theOffset into theIndex
         end if
   end switch
   put theArray[theIndex] into theVal
   put itemOffset(theVal,theList) into theIndex
   return theIndex
end closestToIndex
The ClosestToIndex function is a little more robust than it really needs to be to differentiate between the 8 handles, but I wanted it to be general purpose so I can use it again later.

Naturally, the code to do the resizing would then be in each one of the case statements. Having said that, there are things that can be done to make this more efficient, but I figured efficiency will come later!

Do you have an email I can send my stack to once it's completed?
Cheers,
Tom

bn
VIP Livecode Opensource Backer
VIP Livecode Opensource Backer
Posts: 4003
Joined: Sun Jan 07, 2007 9:12 pm
Location: Bochum, Germany

Re: Using a group's handles to resize controls within group

Post by bn » Thu Oct 24, 2013 9:00 pm

Hi Tom,

as member of the working party on obsessive/compulsory resizeHandler clicking I would like to share this code to detect which resize handle is clicked with you.

It determines the specific handle at the first run of a resizeControl script, and only on the first run. It puts its results into script local messages. This makes it different from your script which runs everytime on resizeControl.
I figured I really only need this once per resize session. Though I don't get if the handle "overruns" the area of the group, but then it is too late anyways.
I use the clickLoc which is preserved during the whole resize session. I use a test for "mouse is down" to determine the first run of a resize session. This also alows to e.g. store the margins of the group only once on first run and put them into script locals. The last run of a resize session has the mouse up, that is how I prepare for the next session of resizeControl.

Code: Select all

local sFirstRound = true -- used to determine the first resizeControl message
local sHorzLoc, sVert, sMiddleVert, sMiddleHorz, sLeft, sOnTop -- filled in handler determineHandle
local sLM, sTM, sRM, sBM -- these are the margins of the group, filled in handler determineHandle, left-, top-, right-, bottom-Margin
local sHandle


on resizeControl
   -- lets find out if this is the first round of resize since click
   -- use it to determine the clicked handle only once when resizing begins
   if the mouse is down then
      if sFirstRound = true then -- this is first resizeControl message
         put false into sFirstRound
         
         -- determine which of the 8 handles has been clicked -> in sHandle, determine the margins of the group and put them in script local variables
         determineHandle 
         
         put sHandle && the milliseconds -- into field "theField" of me
      end if
   else -- mouse is up
      -- resizControl is last time around since the mouse is up
      -- prepare script local var sFirstRound for next time a resize will occur
      put true into sFirstRound
      -- possibly more code here
   end if
   -- end preparing
   
   pass resizeControl
end resizeControl

on DetermineHandle
   put item 1 of the clickLoc - item 1 of the loc of me into sHorz
   if abs(sHorz) < 5 then
      put true into sMiddleHorz
   else
      put false into sMiddleHorz
      
   end if
   put item 2 of the clickLoc - item 2 of the loc of me into sVert
   if abs(sVert) < 5 then
      put true into sMiddleVert
   else
      put false into sMiddleVert
   end if
   put (sHorz < 0) into sLeft 
   put (sVert < 0) into sOnTop
   
   switch sOnTop
      case true
         if sMiddleHorz then put "upper middle" into sHandle
         if sLeft and not (sMiddleHorz)  then
            put "upper left" into sHandle
         end if
         if not (sLeft) and not sMiddleHorz then
            put "upper right" into sHandle
         end if
         break
      case false
         if sMiddleHorz then put "lower middle" into sHandle
         if sLeft and not (sMiddleHorz)  then
            put "lower left" into sHandle
         end if
         if not (sLeft) and not sMiddleHorz then
            put "lower right" into sHandle
         end if
         break
   end switch
   
   switch sLeft
      case true
         if sMiddleVert then put "middle left" into sHandle
         break
      case false
         if sMiddleVert then put "middle right" into sHandle
         break
   end switch
   
   put the leftmargin of me into tLM
   put the topMargin of me into sTM
   put the rightMargin of me into tRM
   put the bottomMargin of me into tBM
   
   -- possibly more code here
end DetermineHandle
If you are using groups as widgets that you copy wholesale onto a card you might want to look at the "newGroup" message which is sent to the newly placed group, a handy message to do initializations and such.
Just as the preopenControl, openControl and closeControl messages. They are sent to a group when the card opens or closes, also very handy to e.g. start/stop send in time handlers. Please note that in the last stack I posted I forgot some "of me"

BTW if you have a script in a control of a group that addresses another control of that same group you would specify

Code: Select all

dispatch someMessage to button "myButton" of the owner of me
"owner of me" again resolves the ambiguities of controls of the same name in multiple instances of a grouped widget. If you think of doing those kind of widgets it will save you a lot of head scratching and recoding later.

for my email make a button:

Code: Select all

on mouseUp
   put "bmlnZ2VtYW5uQHVuaS13aC5kZQ==" into tData
   put base64decode(tData)
end mouseUp
Kind regards
Bernd

Post Reply

Return to “Talking LiveCode”