Hi all,
Following recent efforts to create a tri-state control with groups (which worked well) I applied this to the new script widget facility in LC10 DP5.
It did take a couple of days to adapt my scripts to this but I'm happy with the end-result, not in the least because this took about 8-10 hours to complete whereas having to learn LCB and write this in LCB would have taken months... The .lce file is installed as usual with extension manager (Tools menu), will create a new icon in the Toolbar and the widget's properties are available in the IDE's property inspector.
The widget is similar to the existing switch control widget, but offers 3 states instead of 2 (I needed this to indicate 1 of 2 values or unassigned).
Many thanks to Heather and team for the really quite helpful lesson on script widgets: https://lessons.livecode.com/m/98525/l/ ... ipt-widget. It would have been good to have a bit more detail about property metadata (ideally the switch value would be an enum rather than textfield, but not sure how to do this.
Widget and source code and more detail available from my GitHub under MIT iicence: https://github.com/stam66/tristate
S.
Tri-state control widget
Moderators: FourthWorld, heatherlaine, Klaus, kevinmiller, robinmiller
-
- Livecode Staff Member
- Posts: 192
- Joined: Thu Apr 18, 2013 2:48 pm
Re: Tri-state control widget
Hi stam,
To use an enum editor in the PI for your switchValue property, you just need to return comma delimited `options` as metadata. You can change the label associated with each enum value using `|`, so you might do
Cheers!
Ali
To use an enum editor in the PI for your switchValue property, you just need to return comma delimited `options` as metadata. You can change the label associated with each enum value using `|`, so you might do
Code: Select all
/* Metadata */
getProp propertyMetadata[pProperty]
switch pProperty
case "switchValue"
return { \
"editor": "com.livecode.pi.enum", \
"options": "Left|-1,Centre|0,Right|1", \
"default": 0, \
"section": "Basic", \
"label": "Switch Value" \
}
break
Ali
Re: Tri-state control widget
Thanks Ali, very much appreciated (and on that note, is there a reference to look these things up somewhere?).livecodeali wrote: ↑Thu Aug 03, 2023 9:08 amHi stam,
To use an enum editor in the PI for your switchValue property, you just need to return comma delimited `options` as metadata. You can change the label associated with each enum value using `|
However, I can't quite get it to work... Using your example I am able to show the property as a dropdown menu but the value it assigns is the whole text, not the label (i.e. the popup shows Left|-1 which of course doesn't work; and when changing the state of the widget, it shows the actual value, not the label (i.e. the pop up just shows -1, 0 or 1):
This is my declaration in the propertyMetadata handler:
Code: Select all
case "switchValue"
return { \
"default": "Centre|0", \
"label": "Switch position", \
"editor": "com.livecode.pi.enum", \
"options": "Left|-1,Centre|0,Right|1", \
"section": "Basic" }
break
--------------------------------------
EDIT: Never mind, I refactored the code significantly and have changed the options for 'switchValue' to left/center/right insisted of -1/0/1 - using the enum now works just fine (seems the label option didn't work for me...)
I'll upload a revision to GitHub for anyone wanting this... Thanks once again for the advice with enum.
-
- Livecode Staff Member
- Posts: 192
- Joined: Thu Apr 18, 2013 2:48 pm
Re: Tri-state control widget
Oh strange - that should work. Possibly a bug- either that or I've forgotten the correct syntax for it!
Re: Tri-state control widget
I've updated the widget with some corrections by Bernd - I've also added a new feature that I needed for my own project - inline labels:
There are 2 1-char labels for the centre position and a larger label for left or right positioning. All texts are set in the 'content' section of the property inspector. The text size scales on widget height, within the constraint of a min and max text size that are set in the 'text' section of the property inspector; the textColor depends on luminosity of backgroundColor (ie dark text on light background and vice versa).
Vertical alignment isn't perfect and sometimes needs a slight 1-2 pixel nudget in height - I may well change the labels to buttons to be rid of this issue, but if anyone has any suggestion, would be grateful! The scriptOnlyStack and the built widget require LC 10 DP 5, and are available here: https://github.com/stam66/tristate
For those that don't have access to script widgets, you can use it as a normal group with the script:
I added this to a substack ("tristateBehavior") to use as a behaviour and can create a new 'group gadget' using a button with the script
This generates a group that is identical to the widget, but obviously doesn't have access to the property panel and you'd need to set the properties by code.
Labels are shown or hidden with the 'hasLabels' property of the widget.There are 2 1-char labels for the centre position and a larger label for left or right positioning. All texts are set in the 'content' section of the property inspector. The text size scales on widget height, within the constraint of a min and max text size that are set in the 'text' section of the property inspector; the textColor depends on luminosity of backgroundColor (ie dark text on light background and vice versa).
Vertical alignment isn't perfect and sometimes needs a slight 1-2 pixel nudget in height - I may well change the labels to buttons to be rid of this issue, but if anyone has any suggestion, would be grateful! The scriptOnlyStack and the built widget require LC 10 DP 5, and are available here: https://github.com/stam66/tristate
For those that don't have access to script widgets, you can use it as a normal group with the script:
Code: Select all
local sHasCreatedVisuals, sMouseX, sClickedIndicator
local sBackroundID, sIndicatorID, sTextLeftID, sTextRightID, sTextLongID
########################### 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 "Center" into tValue
return tValue
end switchValue
setProp switchValue pValue
set the switchValue of me to pValue
updateIndicatorForValue
end switchValue
// left text
getProp leftText
local tText
put the leftText of me into tText
if tText is empty then put "Left" into tText
return tText
end leftText
setProp leftText pText
set the leftText of me to pText
updateVisualControls
end leftText
// Left text small
getProp leftTextSmall
local tText
put the leftTextSmall of me into tText
if tText is empty then put "L" into tText
return tText
end leftTextSmall
setProp leftTextSmall pText
set the leftTextSmall of me to pText
updateVisualControls
end leftTextSmall
// right text
getProp rightText
local tText
put the rightText of me into tText
if tText is empty then put "Right" into tText
return tText
end rightText
setProp rightText pText
set the rightText of me to pText
updateVisualControls
end rightText
// Right text small
getProp rightTextSmall
local tText
put the rightTextSmall of me into tText
if tText is empty then put "R" into tText
return tText
end rightTextSmall
setProp rightTextSmall pText
set the rightTextSmall of me to pText
updateVisualControls
end rightTextSmall
// max label textSize
getProp maxTextSize
local tSize
put the maxTextSize of me into tSize
if tSize is empty then put 30 into tSize
return tSize
end maxTextSize
setProp maxTextSize pSize
set the maxTextSize of me to pSize
updateVisualControls
end maxTextSize
// min labeL textSize
getProp minTextSize
local tSize
put the minTextSize of me into tSize
if tSize is empty then put 9 into tSize
return tSize
end minTextSize
setProp minTextSize pSize
set the minTextSize of me to pSize
updateVisualControls
end minTextSize
// hasLabels
getProp hasLabels
local tBool
put the hasLabels of me into tBool
if tBool is empty then put true into tBool
return tBool
end hasLabels
setProp hasLabels pHasLabels
if pHasLabels is not a boolean then
return "Invalid parameter: not a boolean"
exit hasLabels
end if
set the hasLabels of me to pHasLabels
updateVisualControls
end hasLabels
########################### /PROPERTIES ##########################
on openControl
createVisualControls
layoutVisualControls
updateVisualControls
end openControl
on resizeControl
if not sHasCreatedVisuals then exit resizeControl
layoutVisualControls
formatTextFields
end resizeControl
private command createVisualControls
if sHasCreatedVisuals then exit createVisualControls
set the clipsToRect of me to true
lock screen
// background
if there is not a graphic "background" of me then
create graphic "background" in me
set the width of me to 120
set the height of me to 70
end if
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
// labels
set the margins of the templateField to "0,8,0,8"
set the opaque of the templateField to false
set the showBorder of the templateField to false
set the threeD of the templateField to false
set the locktext of the templateField to true
set the dontWrap of the templateField to true
set the traversalOn of the templateField to false
set the showFocusBorder of the templateField to false
set the textAlign of the templateField to "center"
if there is not a field "textLong" of me then create field "textLong" in me
put the id of field "textLong" of me into sTextLongID
if there is not a field "textLeft" of me then create field "textLeft" in me
put the id of field "textLeft" of me into sTextLeftID
if there is not a field "textRight" of me then create field "textRight" in me
put the id of field "textRight" of me into sTextRightID
// indicator
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
reset the templateGraphic
reset the templateField
layoutVisualControls
end createVisualControls
private command layoutVisualControls ## fix by Bernd to stop the mesmerising wandering of the indicator...
if not sHasCreatedVisuals then exit layoutVisualControls
local tRect, tDiameter
lock screen
// background rect
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
// indicator rect
put the height of control id sBackroundID - 4 into tDiameter
set the width of control id sIndicatorID to tDiameter
set the height of control id sIndicatorID to tDiameter
## resize the indicator in current location
switch the switchValue of me
case "Left"
set the left of control id sIndicatorID to the left of control id sBackroundID + 2
set the top of control id sIndicatorID to the top of control id sBackroundID + 2
break
case "Center"
set the loc of control id sIndicatorID to the loc of control id sBackroundID
break
case "Right"
set the right of control id sIndicatorID to the right of control id sBackroundID - 2
set the top of control id sIndicatorID to the top of control id sBackroundID + 2
break
end switch
set the roundRadius of control id sBackroundID to tDiameter
if the mouse is up then updateIndicatorForValue
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
// display and set text labels
formatTextFields
end updateVisualControls
private command formatTextFields
if not sHasCreatedVisuals then exit formatTextFields
if the hasLabels of me is false then
hide control id sTextLongID
hide control id sTextLeftID
hide control id sTextRightID
exit formatTextFields
else
local tOffset, tSize
put (the width of control id sBackroundID - the width of control id sIndicatorID) / 4 into tOffset
------- text field rects ------
// resize/reposition long label
set the width of control id sTextLongID to the width of control id sBackroundID - the width of control id sIndicatorID - 20
set the height of control id sTextLongID to the height of control id sBackroundID
set the loc of control id sTextLongID to the loc of control id sBackroundID
// resize/reposition left text label
set the width of control id sTextLeftID to the width of control id sIndicatorID / 2
set the height of control id sTextLeftID to the height of control id sBackroundID
set the loc of control id sTextLeftID to the the left of control id sBackroundID + tOffset, item 2 of the loc of control id sBackroundID
// resize/reposition right text label
set the width of control id sTextRightID to the width of control id sIndicatorID / 2
set the height of control id sTextRightID to the height of control id sBackroundID
set the loc of control id sTextRightID to the right of control id sBackroundID - tOffset, item 2 of the loc of control id sBackroundID
------- format field text ------
# get text size
if length(the leftText of me) > length(the rightText of me) then
set the text of control id sTextLongID to the leftText of me
else
set the text of control id sTextLongID to the rightText of me
end if
put the maxTextSize of me into tSize
repeat
set the textSize of control id sTextLongID to tSize
if the formattedHeight of control id sTextLongID > the height of control id sTextLongID then
subtract 1 from tSize
else
exit repeat
end if
if tSize <= the minTextSize of me then exit repeat
end repeat
# apply textSize, topMargin, appropriate textColor for background
lock screen
formatFieldText tSize, sTextLeftID
formatFieldText tSize, sTextRightID
formatFieldText tSize, sTextLongID
------- field text ------
// show/hide fields and set text values
switch the switchValue of me
case "left"
hide control id sTextLeftID
hide control id sTextRightID
set the left of control id sTextLongID to the right of control id sIndicatorID + 5
set the text of control id sTextLongID to the leftText of me
show control id sTextLongID
break
case "center"
hide control id sTextLongID
set the text of control id sTextLeftID to the leftTextSmall of me
set the text of control id sTextRightID to the rightTextSmall of me
show control id sTextLeftID
show control id sTextRightID
break
case "right"
hide control id sTextLeftID
hide control id sTextRightID
set the right of control id sTextLongID to the left of control id sIndicatorID - 5
set the text of control id sTextLongID to the rightText of me
show control id sTextLongID
end switch
end if
end formatTextFields
private command formatFieldText pSize, pControlID
set the textColor of control id pControlID to textColorForBackground(the backgroundColor of control id sBackroundID)
set the textSize of control id pControlID to pSize
set the topMargin of control id pControlID to \
(round((the height of control id pControlID - the formattedHeight of control id pControlID)/2 \
+ the formattedHeight of control id pControlID / 5))
end formatFieldText
private command updateIndicatorForValue
local tX
if not sHasCreatedVisuals then exit updateIndicatorForValue
switch the switchValue of me
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
formatTextFields
end updateIndicatorForValue
on mouseDown
if "groupbehavior" is in the long name of the target then exit to top -- stop behavior stack from firing mouseDowns, remove in widget
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 -- stop behavior stack from firing mouseUps, remove in widget
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
# return dark textColor for light backgrounds & vice versa
private function textColorForBackground pColor
local hsp, r, g, b
if char 1 of pColor is "#" and length(pColor) = 7 then // hex
put baseConvert(char 2 to 3 of pColor, 16, 10) into r
put baseConvert(char 4 to 5 of pColor, 16, 10) into g
put baseConvert(char 6 to 7 of pColor, 16, 10) into b
else if length(pColor) = 6 then
put baseConvert(char 1 to 2 of pColor, 16, 10) into r
put baseConvert(char 3 to 4 of pColor, 16, 10) into g
put baseConvert(char 5 to 5 of pColor, 16, 10) into b
else if pColor contains "," then// rbg
put item 1 of pColor into r
put item 2 of pColor into g
put item 3 of pColor into b
end if
put sqrt(0.299 * (r * r) + 0.587 * (g * g) + 0.114 * (b * b)) into hsp
if hsp > 127.5 then
return "black"
else
return "white"
end if
end textColorForBackground
I added this to a substack ("tristateBehavior") to use as a behaviour and can create a new 'group gadget' using a button with the script
Code: Select all
on mouseUp pButtonNumber
local tName
put "tristate 1" into tName
repeat
if there is a group tName then
add 1 to the last word of tName
else
exit repeat
end if
end repeat
create group tName
set the behavior of group tName to the long id of stack "tristateBehavior"
send "openControl" to group tName
end mouseUp