Script-only stack

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

Moderators: FourthWorld, heatherlaine, Klaus, kevinmiller, robinmiller

Post Reply
dunbarx
VIP Livecode Opensource Backer
VIP Livecode Opensource Backer
Posts: 10337
Joined: Wed May 06, 2009 2:28 pm

Script-only stack

Post by dunbarx » Thu Aug 03, 2023 6:38 pm

Never needed these before, but I thought I would experiment. I made a new script-only stack, and had no problem placing two handlers ("xx" and "openControl") in the stack script.

Sending "xx" to the stack worked fine, but when I tried to send the "openControl" message, I got:
Error description: create: error in bad parent or background expression
Is it something particular about that message?

Craig

dunbarx
VIP Livecode Opensource Backer
VIP Livecode Opensource Backer
Posts: 10337
Joined: Wed May 06, 2009 2:28 pm

Re: Script-only stack

Post by dunbarx » Thu Aug 03, 2023 6:43 pm

OK, it is the "in me" that failed.

I did not make that up, but rather used one of the LC lessons to copy that line directly. It was in
https://lessons.livecode.com/m/98525/l/ ... ipt-widget

Anyway, fixed.

Craig

stam
Posts: 3089
Joined: Sun Jun 04, 2006 9:39 pm

Re: Script-only stack

Post by stam » Thu Aug 03, 2023 6:49 pm

I'm no expert having created all of 1 of these, but the 'in me' seems to be required by the script widget when creating sub-controls. But if you're just testing the stack in the IDE that will probably fail ;)
As, I suspect will the propertyMetadata handler...

Creating script widget is a lot more finicky than just making a group control (any error anywhere causes issues that aren't easy to debug, especially if you have no knowledge of what things are supposed to be... BUT, when you get it working it's great fun - and hugely helpful.

dunbarx
VIP Livecode Opensource Backer
VIP Livecode Opensource Backer
Posts: 10337
Joined: Wed May 06, 2009 2:28 pm

Re: Script-only stack

Post by dunbarx » Thu Aug 03, 2023 10:36 pm

Stam.

Aha. I am sure you are right, that the syntax is different for a widget as opposed to the IDE.

And like you, I am excited to be able to make widgets without having to learn LCB. But since I only develop for myself and friends, I will likely never actually need a widget where a group would do.

Craig

stam
Posts: 3089
Joined: Sun Jun 04, 2006 9:39 pm

Re: Script-only stack

Post by stam » Fri Aug 04, 2023 9:54 am

dunbarx wrote:
Thu Aug 03, 2023 10:36 pm
Stam.

Aha. I am sure you are right, that the syntax is different for a widget as opposed to the IDE.

And like you, I am excited to be able to make widgets without having to learn LCB. But since I only develop for myself and friends, I will likely never actually need a widget where a group would do.

Craig
Actually on further experimentation, 'in me' can be used with normal groups as well, so it's not specific to script widgets.

The required handlers for script widget (handling openControl, resizeControl, getters and setters for each property, updating appearance when properties change and the business logic handlers) can and arguably should be implemented in a group-based control... Off the top of my head, the biggest differences I found with how I would normally code a group are:
  • you have to create the components in script instead of with the IDE. But I experimented with a group and you can certainly do this with a group as well using the same syntax 'in me'.
  • instead of referencing a 'control of me' (eg. graphic "myControl" of me) it requires more disambiguation - you store the id of the control created in a script local variable and reference it with control id sMyControlID - interestingly this works just fine with normal groups as well.
  • Metadata required for the widget and the property inspector for the widget - clearly you can't use these with a normal group. The PI metadata is the bit I found most confusing as I struggled to find any documentation on how to apply this to stuff not in the lesson referenced above, but which really isn't hard.
  • There is no flexibility for error in the script widget - anything that goes wrong will make the whole thing fail and there's no help debugging it.

I recently posted a tri-state switch widget created as a script widget, based on a group control I had created before that. While that did require some re-working of the initial code used for the group-control, it was actually fairly straightforward using this re-worked widget code back into a group. In practice, you would create an empty group, assign the adapted code as the script (or a behaviour) of the new empty group and send openControl to it to initialise it.

Resizing groups can be a pain, but can be fixed to be the same as a widget by setting the clipsToRect of the group to true and actually you can't tell the difference from the script widget other than it doesn't have settings in the Property Inspector, doesn't have an icon in the Tools palette and can't be packaged as an extension for sharing.

With my tri-state widget as an example, here is how I modified the script widget's code to work for a normal group (the code for my script widget can be viewed/downloaded at https://github.com/stam66/tristate):

Code: Select all

local sHasCreatedVisuals, sMouseX, sClickedIndicator
local sBackroundID, sIndicatorID

########################### PROPERTIES ###########################
// rightColor
getProp rightColor
    local tColor
    put the rightColor of me into tColor
    if  tColor is empty then put "#C90076" into tColor
    return tColor
end rightColor

setProp rightColor pColor
    set the rightColor of me to pColor
    updateVisualControls
end rightColor

// leftColor
getProp leftColor
    local tColor
    put the leftColor of me into tColor
    if  tColor is empty then put "#2986CC" into tColor
    return tColor
end leftColor

setProp leftColor pColor
    set the leftColor of me to pColor
    updateVisualControls
end leftColor

// centerColor
getProp centerColor
    local tColor
    put the centerColor of me into tColor
    if  tColor is empty then put "#F4F6F7" into tColor
    return tColor
end centerColor

setProp centerColor pColor
    set the centerColor of me to pColor
    updateVisualControls
end centerColor

// indicatorColor
getProp indicatorColor
    local tColor
    put the indicatorColor of me into tColor
    if  tColor is empty then put "#E6E8E8" into tColor
    return tColor
end indicatorColor

setProp indicatorColor pColor
    set the indicatorColor of me to pColor
    updateVisualControls
end indicatorColor

// switchValue
getProp switchValue
    local tValue
    put the switchValue of me into tValue
    if tValue is empty then put 0 into tValue
    return tValue
end switchValue

setProp switchValue pValue
    set the switchValue of me to pValue
    updateIndicatorForValue pValue
end switchValue

########################### /PROPERTIES ##########################

on openControl
    createVisualControls
    layoutVisualControls
    updateVisualControls
end openControl

on resizeControl
    layoutVisualControls
end resizeControl

private command createVisualControls
    if sHasCreatedVisuals then exit createVisualControls
    lock screen
    set the width of me to 70
    set the height of me to 35
    set the clipsToRect of me to true
    
    if there is not a graphic "background" of me then create graphic "background" in me
    put the id of graphic "background" of me into sBackroundID
    set the style of  control id sBackroundID to "roundRect"
    set the opaque of  control id sBackroundID to true
    set the linesize of control id sBackroundID to 1
    
    if there is not a graphic "indicator" of me then create graphic "indicator" in me
    put the id of graphic "indicator" of me into sIndicatorID
    set the style of control id sIndicatorID to "oval"
    set the opaque of  control id sIndicatorID to true
    set the linesize of control id sIndicatorID to 1
    set the backgroundColor of control id sIndicatorID to "#E6E8E8"
    local tInnerGlow
    put "normal" into tInnerGlow["blendMode"]
    put 255,255,255 into tInnerGlow["color"]
    put "gaussian" into tInnerGlow["filter"]
    put 255 into tInnerGlow["opacity"]
    put 255 into tInnerGlow["range"]
    put 20 into tInnerGlow["size"]
    put "edge" into tInnerGlow["source"]
    put 0 into tInnerGlow["spread"]
    set the innerGlow of control id sIndicatorID to tInnerGlow
    
    set the switchValue of me to "center"
    put true into sHasCreatedVisuals
    layoutVisualControls
end createVisualControls

private command layoutVisualControls
    if not sHasCreatedVisuals then exit layoutVisualControls
    
    local tRect, tDiameter, tLength
    put the rect of me into tRect
    add 2 to item 1 of tRect
    add 2 to item 2 of tRect
    subtract 2 from item 3 of tRect
    subtract 2 from item 4 of tRect
    set the rect of  control id sBackroundID to tRect
    
    put item 4 of tRect - item 2  of tRect  into tDiameter
    set the width of control id sIndicatorID to tDiameter - 4
    set the height of control id sIndicatorID to tDiameter - 4
    set the roundRadius of control id sBackroundID to tDiameter
    if the mouse is up then updateIndicatorForValue the switchValue of me 
end layoutVisualControls

private command updateVisualControls
    if not sHasCreatedVisuals then exit updateVisualControls
    switch the switchValue of me
        case "Left"
            set the backgroundColor of control id sBackroundID to the leftColor of me
            break
        case "Center"
            set the backgroundColor of control id sBackroundID to the centerColor of me
            break
        case "Right"
            set the backgroundColor of control id sBackroundID to the rightColor of me
            break
    end switch
    set the backgroundColor of control id sIndicatorID to the indicatorColor of me
end updateVisualControls


private command updateIndicatorForValue pValue
    local tX
    switch pValue
        case "Left"
            set the backgroundColor of control id sBackroundID to the leftColor of me 
            put the left of control id sBackroundID + 3 + the width of control id sIndicatorID/2 into tX
            move control id sIndicatorID to tX, item 2 of the loc of control id sBackroundID in 200 milliseconds
            set the left of control id sIndicatorID to the left of control id sBackroundID + 2
            break
        case "Center"
            set the backgroundColor of control id sBackroundID to the centerColor of me 
            move control id sIndicatorID to the loc of control id sBackroundID in 200 milliseconds
            break
        case "Right"
            set the backgroundColor of control id sBackroundID to the rightColor of me 
            put the right of control id sBackroundID - 3 - the width of control id sIndicatorID/2 into tX
            move control id sIndicatorID to tX, item 2 of the loc of control id sBackroundID in 200 milliseconds
            set the right of control id sIndicatorID to the right of control id sBackroundID  -2
            break
    end switch
end updateIndicatorForValue

on mouseDown 
    if "groupbehavior" is in the long name of the target then exit to top
    
    if the mouseLoc is within the rect of control id sIndicatorID then 
        put true into sClickedIndicator
        put the mouseH into sMouseX
    end if
    pass mouseDown
end mouseDown

on mouseUp pKey
    local tSegmentClicked, tRect, tR1, tR2, tR3, t3
    if "groupbehavior" is in the long name of the target then exit to top
    if sClickedIndicator then   // drag indicator 1 step in drag direction
        local tValue
        put the switchValue of me into tValue
        if the mouseH - sMouseX > 0 and tValue is "Left" then // go right
            put "Center" into tValue
        else if the mouseH - sMouseX > 0 and tValue is "Center" then // go right
            put "Right" into tValue    
        else if the mouseH - sMouseX < 0 and tValue is "Right" then //go left
            put "Center" into tValue
        else if the mouseH - sMouseX < 0 and tValue is "Center" then //go left
            put "Left" into tValue
        end if
        set the switchValue of me to tValue
    else                        // establish 3 click zones to see if left, centre or right
        put the width of control id sBackroundID / 3 into t3
        put the rect of control id sBackroundID into tR1
        put item 1 of tR1 +  t3 into item 3 of tR1
        put tR1 into tR2
        add t3 to item 1 of tR2
        add t3 to item 3 of tR2
        put tR2 into tR3
        add t3 to item 1 of tR3
        add t3 to item 3 of tR3
        // apply value based on click zone
        if the mouseLoc is within tR1 then 
            set the switchValue of me to "Left"
        else if the mouseLoc is within tR2 then
            set the switchValue of me to "Center"
        else if the mouseLoc is within tR3 then
            set the switchValue of me to "Right"
        end if
    end if
    put false into sClickedIndicator
    put empty into sMouseX
end mouseUp

on mouseRelease
    local tValue
    if "groupbehavior" is in the long name of the target then exit to top
    put the switchValue of me into tValue
    if the mouseH - sMouseX > 0 and tValue is "Left" then // go right
        put "Center" into tValue
    else if the mouseH - sMouseX > 0 and tValue is "Center" then // go right
        put "Right" into tValue    
    else if the mouseH - sMouseX < 0 and tValue is "Right" then //go left
        put "Center" into tValue
    else if the mouseH - sMouseX < 0 and tValue is "Center" then //go left
        put "Left" into tValue
    end if
    set the switchValue of me to tValue
    put false into sClickedIndicator
    put empty into sMouseX
end mouseRelease
I put this code in the script of a substack called groupBehavior, and can create a new tri-state switch as a normal group with:

Code: Select all

on mouseUp pButtonNumber
    create group "tristate1"
    set the behavior of group "tristate1" to the long id of stack "groupBehavior"
    send "openControl" to group "tristate1"
end mouseUp
This creates a group that is indistinguishable from the widget but has no Property Inspector panel and doesn't appear in the Tools palette - but is largely the same code as the script widget (other than setting the clipsToRect to true and removing the metadata declarations). Given how finicky script widgets are, you are almost forced to use more disciplined code which is arguably a good thing generally ;)
The bit still missing documentation-wise is more guidance about script and property metadata... but I do like script widgets because they are encapsulated, shareable and easily re-used. But of course they don't do anything functionally that a group can't.

S.

PS: One downside to script widgets vs groups: I just experimented saving a stack with the above group code and the same as a script widget. On quitting LC and re-opening the stack, the group control appears instantly, while the script widget control takes 1-2 seconds to appear (not sure if that's just my bad code or an inherent issue with script widgets - or indeed if it's something that will improve as the techology matures).

dunbarx
VIP Livecode Opensource Backer
VIP Livecode Opensource Backer
Posts: 10337
Joined: Wed May 06, 2009 2:28 pm

Re: Script-only stack

Post by dunbarx » Fri Aug 04, 2023 1:59 pm

Stam.

Thanks for the info.

A widget is very cool in that it can live in the tools palette, something no group can do. One day when I grow up, maybe I will find that such a thing is worthwhile for me to construct. But I do not develop for distribution and sale, so I cannot see ever needing such a thing.

Craig

Post Reply