An Introduction to eXTension Builder Widgets

This subforum is for topics specifically about related to creating UI Widgets with eXTension Builder ( XTB / LCB ) and defining their properties for editing with the Property Inspector. Please stay on that topic or related in this forum.
Forum rules
This subforum is for topics specifically about related to creating UI Widgets with eXTension Builder ( XTB / LCB ) and defining their properties for editing with the Property Inspector. Please stay on that topic or related in this forum.
User avatar
richmond62
Posts: 4204
Joined: Sun Sep 12, 2021 11:03 am
Location: Bulgaria
Contact:

Re: An Introduction to eXTension Builder Widgets

Post by richmond62 »

Screen Shot 2024-10-08 at 4.29.16 pm.png
Screen Shot 2024-10-08 at 4.29.16 pm.png (6.35 KiB) Viewed 4392 times
-
Magic! 8-)
https://richmondmathewson.owlstown.net/
User avatar
OpenXTalkPaul
Posts: 2379
Joined: Sat Sep 11, 2021 4:19 pm
Contact:

Re: An Introduction to eXTension Builder Widgets

Post by OpenXTalkPaul »

So I inserted that last code to draw the bottom/right ''shadow" bevel edges in between the drawing of the background fill and the border stroke and text fill. The OnPaint handler now looks like this:

Code: Select all

public handler OnPaint()
   variable tColor as Color
   variable tRectPath as Path -- remember to declare variables before using them
   put rectangle path of my bounds into tRectPath -- 'my bounds' is the widget's view port / clipping rect
	-- log[the instructions of tRectPath]
   set the paint of this canvas to my background paint
   fill tRectPath on this canvas
   
	------- INSERTED CODE ------
	variable tPath as Path -- declare a canvas 'path' type variable to hold our drawing commands
	
	set the paint of this canvas to solid paint with color [0.0, 0.0, 0.0, 1.0]

	put the empty path into tPath -- init new blank path
	move to point [ 0, my height ] on tPath -- starting point left,bottom
	line to point [ my width, my height ] on tPath -- draw line to right,bottom
	line to point [ my width, 0 ] on tPath -- draw line upwards to right,top
	close path on tPath -- closing the path adds a line that connects it back to the starting point
	fill tPath on this canvas
	log[the instructions of tPath] -- just for development, check the canvas path string
	----------------------------
	
   set the paint of this canvas to my foreground paint
   set the stroke width of this canvas to 4
   stroke tRectPath on this canvas -- draw the line around our widget's rectangle
	if mLabel is not nothing then
		set the font of this canvas to my font
		-- fill text mLabel at point [ my width /2 , my height / 2 ] on this canvas
		fill text mLabel at center of my bounds on this canvas
	end if
end handler
The problem was this triangle shape would be the same color as the (inherited) background unless I change the current canvas paint color to a different color. We'll change it to a solid 'black' color for the 'shadow'.

Code: Select all

set the paint of this canvas to solid paint with color [0.0, 0.0, 0.0, 1.0]
There's some counter intuitive syntax for you. 'You need to use 'solid paint' if you want to use a color that has transparency! Again colors components in xBuilder are floating point numbers (NOT 0-255). The last number here is the Alpha / transparency component. Here we have it set to 1.0 which is a 100% solid color, but if it were fractional like 0.5, then 50% of whatever is behind this color will show through the color.

Now our widget looks like this by default (in dark mode):
Screen Shot 2024-10-08 at 9.45.51 AM.png
Screen Shot 2024-10-08 at 9.45.51 AM.png (18.38 KiB) Viewed 4391 times
Groovy!
In macOS Light-Mode however, with nothing yet set for the foregroundColor it is inherited from the OS, and with light-mode foreColor becomes black instead of white and so it looks like this:
Screen Shot 2024-10-08 at 10.05.04 AM.png
Screen Shot 2024-10-08 at 10.05.04 AM.png (19.96 KiB) Viewed 4389 times
Not so groovy. Half of the label text is now unreadable because it's the same color.
We can explicitly set the fore/back colors to something that will be appropriate for both dark and light modes, but in my opinion it should work in either with just its default property values.

But I digress. It's not really recognizable as a 'bevel' yet anyway.
We need another shape with a different color, layered in beween the 'shadow' and the label text. This will be the area our widget considers its background to be. We will now use the original rectangle fill as the upper bevel's edge.
We're also going to need to determine just how much of bevel do we want it to have. For now I'll just use a constant number of 8 for the bevel's thickness, but later we can change the value into a user settable property.
User avatar
richmond62
Posts: 4204
Joined: Sun Sep 12, 2021 11:03 am
Location: Bulgaria
Contact:

Re: An Introduction to eXTension Builder Widgets

Post by richmond62 »

How do simple folk like myself get access to all the wonderful things you doing if we are stuck with RC3/4?

Mind you, as that crashed on MacOS 15.1 beta 6 . . .
Attachments
OXTheavy_crash.rtf.zip
Crash report
(14.21 KiB) Downloaded 84 times
https://richmondmathewson.owlstown.net/
User avatar
OpenXTalkPaul
Posts: 2379
Joined: Sat Sep 11, 2021 4:19 pm
Contact:

Re: An Introduction to eXTension Builder Widgets

Post by OpenXTalkPaul »

richmond62 wrote: Tue Oct 08, 2024 2:17 pm How do simple folk like myself get access to all the wonderful things you doing if we are stuck with RC3/4?
This has little to do with this topic.
The stuff I'm writing here should work wether you are using LC Community, 'OXT Lite' or DPE RCx, and most likely it would also work with commercial versions of LiveCode Xavi Create (or whatever they're branding it as now), although I don't own that product in order to test compatiblity with that.

In fact last night I tested this on very resource limited 'netbook' type thing on Linux Mint with OXT Lite, and it compiled and works just fine.

Also, I don't install beta OSes, but thanks for reporting. If OXT Lite works with that OS, then I should be able to make DPE RC5/6 work with it too (once I get to that).
Logical CPU: 1
Error Code: 0x00000006 (no mapping for user data write)
Trap Number: 14
Hmmm?
Also not sure what 'mach_vm' is? Did Apple make the mach kernel into a virtual machine?
Are you running it in a virtual machine or via Intel instructioin translation to Apple Silicon in Rosetta 2?
User avatar
OpenXTalkPaul
Posts: 2379
Joined: Sat Sep 11, 2021 4:19 pm
Contact:

Re: An Introduction to eXTension Builder Widgets

Post by OpenXTalkPaul »

Having inserted another inset rectangle in-between layer and making the original bounds rectangle filled white, it looks like a bevel as long as the rectangle is actually a square, but it doesn't look like a bevel if the widget is more rectangular.
Screen Shot 2024-10-08 at 11.14.23 AM.png
Screen Shot 2024-10-08 at 11.14.23 AM.png (52.58 KiB) Viewed 4366 times
I will have to rethink this. I will need to make the bevel shape from a list of points that are a combination of select points from both fore and back rectangles.

OK so we will have the bevel shape follow the inset path back around to its starting point.
I store the inset rect in a variable of type 'Rectangle' to make it easier to reference the inset points of it.
The OnPaint Handler now looks like this:

Code: Select all

public handler OnPaint()
   variable tColor as Color
   variable tRectPath as Path -- remember to declare variables before using them
   variable tInsetRect as Rectangle
	put rectangle path of my bounds into tRectPath -- 'my bounds' is the widget's view port / clipping rect
	-- log[the instructions of tRectPath]
	set the paint of this canvas to solid paint with color [0.5, 0.5, 0.5, 1.0]
        fill tRectPath on this canvas

	set the paint of this canvas to my foreground paint
        set the stroke width of this canvas to 4

	put rectangle [8,8, my width - 8, my height - 8 ] into tInsetRect -- 'my bounds' is the widget's view port / clipping rect
	variable tPath as Path -- declare a canvas 'path' type variable to hold our drawing commands
	set the paint of this canvas to solid paint with color [0.0, 0.0, 0.0, 1.0]
	put the empty path into tPath -- init new blank path
	move to point [ 0, my height ] on tPath -- starting point left,bottom
	line to point [ my width, my height ] on tPath -- draw line to right,bottom
	line to point [ my width, 0 ] on tPath -- draw line upwards to right,top

	line to point [ the right of tInsetRect, the top of tInsetRect ] on tPath -- draw line to right,top of inset rect
	line to point [ the right of tInsetRect, the bottom of tInsetRect ] on tPath -- draw to to bottom right of inset rect
	line to point [ the left of tInsetRect, the bottom of tInsetRect ] on tPath -- draw line to bottom left of inset rect
	-- line to point [ 0, my height ] on tPath -- draw line back to starting point, but not needed as 'close path' will do this.
	close path on tPath -- closing the path adds a line that connects it back to the starting point

	fill tPath on this canvas
	log[the instructions of tPath] -- just for development, check the canvas path string

	set the paint of this canvas to my background paint
	put rectangle path of tInsetRect into tRectPath
	fill tRectPath on this canvas

	if mLabel is not nothing then
		set the paint of this canvas to my foreground paint
		set the font of this canvas to my font
		-- fill text mLabel at point [ my width /2 , my height / 2 ] on this canvas
		fill text mLabel at center of my bounds on this canvas
	end if
end handler
I also made the 'top bevel' a 50% gray color so that it will be visible in both dark/light modes.

Screen Shot 2024-10-08 at 12.11.04 PM.png
Screen Shot 2024-10-08 at 12.11.04 PM.png (45.75 KiB) Viewed 4360 times
Screen Shot 2024-10-08 at 12.10.50 PM.png
Screen Shot 2024-10-08 at 12.10.50 PM.png (45.52 KiB) Viewed 4360 times
Reminds me a bit of greyscale NeXTStep UI.
User avatar
richmond62
Posts: 4204
Joined: Sun Sep 12, 2021 11:03 am
Location: Bulgaria
Contact:

Re: An Introduction to eXTension Builder Widgets

Post by richmond62 »

I am running MacOS 15.1 beta 6 directly on a 2018 Mac Mini (INTEL) with 8 GB RAM.
https://richmondmathewson.owlstown.net/
User avatar
tperry2x
Posts: 2769
Joined: Tue Dec 21, 2021 9:10 pm
Location: Somewhere in deepest darkest Norfolk, England
Contact:

Re: An Introduction to eXTension Builder Widgets

Post by tperry2x »

Logical CPU: 1
Error Code: 0x00000006 (no mapping for user data write)
Trap Number: 14
OpenXTalkPaul wrote: Tue Oct 08, 2024 2:30 pm Hmmm?
Also not sure what 'mach_vm' is? Did Apple make the mach kernel into a virtual machine?
mach_vm in this context is virtual memory. That error means there's no data in memory available to access at that offset.
richmond62 wrote: Tue Oct 08, 2024 4:03 pm I am running MacOS 15.1 beta 6 directly on a 2018 Mac Mini (INTEL) with 8 GB RAM.
Emphasis mine, that's probably where your issue is. There's no telling if Apple are currently 'playing' with how virtual memory is allocated and referenced. 16GB is probably required on MacOS these days, otherwise things are loaded into and out of virtual memory swap constantly. (I would agree with "people here" who would advocate 16GB of RAM and higher.)
TerryL
Posts: 107
Joined: Sat Oct 16, 2021 5:05 pm
Contact:

Re: An Introduction to eXTension Builder Widgets

Post by TerryL »

Here's "Lesson 70 eXTension Builder Widget Part 2.txt" ( without the bevel info) to be added to OXT Lite's Lessons stack if Paul consents. Also included is "Lesson 71 Android Setup.txt" by Tom Perry, again if Tom consents. I thought both tutorials were instructional and useful for advanced users. Thanks to both Paul and Tom. I appreciate the time and effort both of you make to the OpenXTalk project. Terry
Attachments
Widget 2.zip
(9.03 KiB) Downloaded 84 times
User avatar
OpenXTalkPaul
Posts: 2379
Joined: Sat Sep 11, 2021 4:19 pm
Contact:

Re: An Introduction to eXTension Builder Widgets

Post by OpenXTalkPaul »

Because this all meant to be customizable by the widget end-user, we want to make everything we can be a property, rather than a constant value. So before I forget to, let's go back and make the inset amount ( '8' ) into a property.
We need to make a module-scoped variable to store its value in memory, and some metadata to allow Property Inspector to edit this value:

Code: Select all

private variable mBevelWidth as Number
property bevelWidth	get mBevelWidth	set setBevelWidth
metadata bevelWidth.label is "Bevel Width"
metadata bevelWidth.section is "Basic"
metadata bevelWidth.editor is "com.livecode.pi.number"
metadata bevelWidth.step is "1"
metadata bevelWidth.min is "0"
metadata bevelWidth.max is "255"
metadata bevelWidth.default is "0"
metadata bevelWidth.user_visible is "true"
public handler setBevelWidth( in pBevelWidth as optional Number)
	if pBevelWidth is nothing then
		put 0 into mBevelWidth -- make sure it has a numeric value even if it is set to 'empty'
	else
  		put pBevelWidth into mBevelWidth
	end if
  redraw all
end handler

With widgets you often have to do a lot of this, so to make this task slightly less tedious, I made a (work in progress) tool that generates the basic code framework for the property based on a given property name and a property-type editor to use (selected from a drop-down menu). For this custom property name I used 'BevelWidth' and a 'Number Range' (slider) editor.

After we add this to our source code, we can use mBevelWidth in the place of that number 8 that I originally used.
In the OnPaint handler I changed it to:

Code: Select all

put rectangle [mBevelWidth, mBevelWidth , my width - mBevelWidth, my height - mBevelWidth ] into tInsetRect
Now we can adjust the bevel width from the Property Inspector so we can have very shallow bevels or ridiculous sized bevels if we want.

Screen Shot 2024-10-08 at 1.19.31 PM.png
Screen Shot 2024-10-08 at 1.19.31 PM.png (86.28 KiB) Viewed 4343 times
Because I made the max value for the bevelWidth slider be rather large (255 px), we can even go to bevel widths that actually larger than inset rectangle, although that looks a bit of strange.

Screen Shot 2024-10-08 at 1.30.21 PM.png
Screen Shot 2024-10-08 at 1.30.21 PM.png (17.08 KiB) Viewed 4339 times
I could change the metadata for the maximum of the property number range to be something more reasonable like 50px, but I'm not going to :-P
User avatar
OpenXTalkPaul
Posts: 2379
Joined: Sat Sep 11, 2021 4:19 pm
Contact:

Re: An Introduction to eXTension Builder Widgets

Post by OpenXTalkPaul »

Thanks @TerryL for collecting this stuff into standalone documents, I do appreciate it. However, you may want to hold off until I finish working on this tutorial. Almost done with this one, I think.
___________________________________________________


If we click this button widget, it receives the MouseDown/Up messages we posted to the engine, but there is no visual indication that the 'button' is being pressed when the mouse is down. So for this I will mirror the behabior of 'classic' rectangle buttons that can do bevels. That is, we'll simply swap the colors of the top bevel and the bottom bevel, so that it will appear to be 'debossed' (beveled inwards) rather than embossed (beveled outwards).
User avatar
tperry2x
Posts: 2769
Joined: Tue Dec 21, 2021 9:10 pm
Location: Somewhere in deepest darkest Norfolk, England
Contact:

Re: An Introduction to eXTension Builder Widgets

Post by tperry2x »

TerryL wrote: Tue Oct 08, 2024 5:24 pm Here's "Lesson 70 eXTension Builder Widget Part 2.txt" ( without the bevel info)...
Thanks TerryL - Added.
OpenXTalkPaul wrote: Tue Oct 08, 2024 6:05 pm Thanks @TerryL for collecting this stuff into standalone documents, I do appreciate it. However, you may want to hold off until I finish working on this tutorial. Almost done with this one, I think.
I've added this as part 2, and can always add the next as part 3.
If you have:
auto-upd.png
auto-upd.png (2.55 KiB) Viewed 4332 times
...turned on, it'll download it for you.... if not, it won't - and you'd have to choose 'Check for updates' manually.
(Preferences > Automatic Updates).

It should even relaunch the IDE for you after the update is done.
(if you are on MacOS, you'll get an alert you'll need to let it have permissions for).
User avatar
OpenXTalkPaul
Posts: 2379
Joined: Sat Sep 11, 2021 4:19 pm
Contact:

Re: An Introduction to eXTension Builder Widgets

Post by OpenXTalkPaul »

If we click this 'button' (widget), it receives the mouseDown/Up messages we posted to the engine, but there is no visual indication that the 'button' is being pressed when the mouse is down. So for this I will mirror the behabior of 'classic' rectangle buttons that can do bevels. That is, we'll simply swap the colors of the top bevel and the bottom bevel, so that it will appear to be 'debossed' (beveled inwards) rather than embossed (beveled outwards).
--- continued ---
We will need to keep track of the state of the mouse button in order to render the graphics appropriately according to its state. We'll also want to make so that its 'highlight' property can be set via script so that und users can 'set the highlight of widget myButton to true' and since English language is weird and there's different dialects of English, users may be incline to use an alternate spelling like 'hilite', so we will make syntax synonyms that refer to the same property.

First we'll declare a module-scoped variable to store the highlight state.

Code: Select all

 private variable mHighlighted as optional Boolean 
For the Mouse Button you may recall I already created a module-scoped variable (mMouseButton) when I added the 'OnCreate' handler in anticipation of this need to track the mouse button state:

Code: Select all

private variable mMouseButton as optional Number
I changed the variable type there to be 'optional' and removed the line in OnCreate that assigned it an initial value of zero (0) because '0' is the number for the left-click button but if no button is being pressed this should be 'undefined' / null value (which in xBuilder that is the keyword 'nothing'). I also moved the variable declaration to be just above the first mouse-event related handler for readability and convenience, because the mouse events handlers will be where we check and store the mouse state. Then I modified the OnMouseDown/OnMouseUp to assign the appropriate values.

Code: Select all

private variable mMouseButton as optional Number
private variable mHighlighted as Boolean
public handler OnMouseDown()
     put the click button into mMouseButton
     put true into mHighlighted
     post "mouseDown" && mMouseButton formatted as string
     redraw all
end handler
public handler OnMouseUp()
     put false into mHighlighted
     post "mouseUp" && mMouseButton formatted as string
     put nothing into mMouseButton -- change the Mouse Button state variable back to 'undefined' again.
      redraw all
end handler
We need to trigger a redraw of the Widget whenever the 'button' is mouseDown'd or mouseUp'd on, so it will immediately reflect it's state. The last line of the mouse handkers is 'redraw all', which is the syntax that is the primary method to trigger a redraw. Typically you'll want to redraw the widget whenever one of its properties has been changed.

Now we can adjust the rendering in our OnPaint handler to reflect the current state by swapping upper/lower bevel colors:

Code: Select all

public handler OnPaint()
   	variable tRectPath as Path -- remember to declare variables before using them
   	variable tInsetRect as Rectangle

	-------------- Top Bevel ------------------ 
	put rectangle path of my bounds into tRectPath -- 'my bounds' is the widget's view port / clipping rect
	if mHighlighted then
		set the paint of this canvas to solid paint with color [0.0, 0.0, 0.0, 1.0]
	else
		set the paint of this canvas to solid paint with color [0.5, 0.5, 0.5, 1.0]
	end if
        fill tRectPath on this canvas
	
	-------------- Bottom Bevel ------------------ 
	set the paint of this canvas to my foreground paint
        set the stroke width of this canvas to 4

	put rectangle [8,8, my width - 8, my height - 8 ] into tInsetRect -- 'my bounds' is the widget's view port / clipping rect
	variable tPath as Path -- declare a canvas 'path' type variable to hold our drawing commands
	put the empty path into tPath -- init new blank path
	move to point [ 0, my height ] on tPath -- starting point left,bottom
	line to point [ my width, my height ] on tPath -- draw line to right,bottom
	line to point [ my width, 0 ] on tPath -- draw line upwards to right,top

	line to point [ the right of tInsetRect, the top of tInsetRect ] on tPath -- draw line to right,top of inset rect
	line to point [ the right of tInsetRect, the bottom of tInsetRect ] on tPath -- draw to to bottom right of inset rect
	line to point [ the left of tInsetRect, the bottom of tInsetRect ] on tPath -- draw line to bottom left of inset rect
	close path on tPath -- closing the path adds a line that connects it back to the starting point
	if mHighlighted then
		set the paint of this canvas to solid paint with color [0.5, 0.5, 0.5, 1.0]
	else
		set the paint of this canvas to solid paint with color [0.0, 0.0, 0.0, 1.0]
	end if
	fill tPath on this canvas

	-------------- Inner Rectangle ------------------ 
	set the paint of this canvas to my background paint
	put rectangle path of tInsetRect into tRectPath
	fill tRectPath on this canvas
	
	-------------- Label Text ------------------ 
	if mLabel is not nothing then
		set the paint of this canvas to my foreground paint
		set the font of this canvas to my font
		-- fill text mLabel at point [ my width /2 , my height / 2 ] on this canvas
		fill text mLabel at center of my bounds on this canvas
	end if
end handler
All that was needed there was inserting two if/then control structures in order to make the bevels colors conditional on if the current value in mHighlighted (Boolean), set by the mouse-event handlers, is true or false.

We should now have a fairly functional beveled-rectangle 'button' (widget), the only thing I can think of that really should be considered is implementing the other mouse button events such as 'mouseLeave'. A mouseLeave message is normally sent when a control was clicked but the mouse cursor is then moved outside of the controls rectangle without a corresponding 'OnMouseUp' event being triggered because the mouse button is still be 'down'. We don't want the widget to respond the same was as it would for a ' mouseLeave' as it does for an 'mouseUp' event.
User avatar
OpenXTalkPaul
Posts: 2379
Joined: Sat Sep 11, 2021 4:19 pm
Contact:

Re: An Introduction to eXTension Builder Widgets

Post by OpenXTalkPaul »

Before I write the 'mouseLeave' handler, I want to make it so that the button can be scripted to be in an 'pressed' or "not pressed" state, so I will impliment that as the standard property for that, the 'highlighted' property, AND also its synonym 'hilited'.
Since it's a simple true/false (boolean) property, it's just a matter of making a setter handler that will redraw the widget whenever this property is set.

Code: Select all

/**
Syntax:
set the highlight of <widget> to {true|false}
get the highlight of <widget>

Summary: `true` if the widget is highlighted; `false` otherwise

Description:
Use the `highlight` property to test or control whether the button-bevel appears
pressed inwards indicating highlighted or not.
*/
private variable mHighlighted as Boolean
property "highlight"	get mHighlighted	set setHighlighted
metadata highlight.label is "Hilited"
metadata highlight.section is "Basic"
metadata highlight.editor is "com.livecode.pi.boolean"
metadata highlight.default is "false"
metadata highlight.user_visible is "true"
--- synonym ----
property hilite	get mHighlighted	set setHighlighted
metadata hilite.user_visible is "false"

public handler setHighlighted( in pHighlighted as Boolean)
     put pHighlighted into mHighlighted
	  redraw all
end handler
I copied that directly from another widget that I used it in. It's a standard property so any time that you need it, it will look pretty much the same as it does here. Some of this metadata may be extra, not entirely needed, because I use boiler plate / template code that may includes all property options.

Notice that before the properties declarations there is 'lcdoc' markdown formatted comment section, this will be parsed out into an 'API.lcdoc file, which will be added to the syntax Dictionary when the widget is loaded by the IDE.

The 'setter' handler for the property should be 'public' for it to be used from the scripting engine. There's no 'getter' handler defined here because in the case of a Boolean it's just a matter of returning the contents of the variable and doesn't require any other action such as a 'redraw all'.

To create a synonym for the property it is simply a matter of declaring the alternate property name with the same 'getter' and 'setter' in the declaration as the 'main' spelling. I didn't want the 'synonym property' checkbox to also appear in the Property Inspector along side of the main spelling for the property, so I used some metadata to hide it, but the alternate spelling can still be used via scripting.

Code: Select all

metadata hilite.user_visible is "false"
All of you UK 'blokes' can now get to work adding synonym property definitions so that you can 'set the foreColour' instead of setting the foreColor, lol :D (BTW, probably the same could be done on the IDE level with those 'classic' property definitions .tsv files, could probably even be done on the Engine Level but requiring recompiling).
User avatar
tperry2x
Posts: 2769
Joined: Tue Dec 21, 2021 9:10 pm
Location: Somewhere in deepest darkest Norfolk, England
Contact:

Re: An Introduction to eXTension Builder Widgets

Post by tperry2x »

Part 3 added, just click "Re-download" to get the file again.
retry.png
retry.png (17.82 KiB) Viewed 4104 times
User avatar
OpenXTalkPaul
Posts: 2379
Joined: Sat Sep 11, 2021 4:19 pm
Contact:

Re: An Introduction to eXTension Builder Widgets

Post by OpenXTalkPaul »

OpenXTalkPaul wrote: Wed Oct 09, 2024 5:14 pm Before I write the 'mouseLeave' handler,
...
Getting back to those mouse events....

Code: Select all

public handler OnMouseLeave()
       if mMouseButton is not nothing then --- checks if the mouse button is still down
       --- if the mouse is still down then only un-highlight the button
             put false into mHighlighted
         --- we can have a corresponding  OnMouseEnter handler that re-highlights the button if the mouse button is still down
        end if
        post "mouseLeave"
        redraw all
end handler
The OnMouseLeave handler will be automatically triggered whenever the mouse x,y goes outside of the widget's bounds.
This handler above checks to see if there are any current contents of our 'mMouseDown' module-scoped variable, and if mMouseDown contains anything a all (it will be one of 0=Left Mouse Button, 3=Right Mouse Button, or 2=Middle Button if there is one) then the mouse is still down while leaving the widget, and we should un-highlight the 'button' to reflect that, which is done simply by changing the mHighlighted variable to false and then redrawing the widget.

Wether a mouse button is down or not, the widget should still emit the 'mouseLeave' message so that the end-user's scripts may use it, therefore we must 'post' outside of the if/then conditional. Lastly the widget is updated to reflect the change on screen using 'redraw all'.

The OnMouseEnter handler is almost idetical to mouseLeave but now mHighlighted is set back to 'true' if the mouse is still down on reentry, and change the post message is changed to send 'mouseEnter' message to the scripting engine.

Code: Select all

public handler OnMouseEnter()
       if mMouseButton is not nothing then --- checks if the mouse button is still down
       --- if the mouse is still down then only RE-highlight the button
             put true into mHighlighted
        end if
        post "mouseEnter"
        redraw all
end handler
Tested and code behaves as expected.

There's practically infintely more that could be done to make this widget more robust, but I think that's as far as I'll take it for this tutorial. Hopefully anyone following along now has a better understanding of how xBuilder Widgets (non-native-layer) are constructed.

Unless anyone posts any questions they want answered?

I was thinking I might go into the 'packaging' aspects (adding default scripts to the widget, adding Icons, resources, etc.), or maybe write a little about SVG 'path strings', like how to find them or make them yourself, and how they can be used with our canvas drawing widgets.

Here's the full source LCB file contents, all put together:

Code: Select all

/**
Description: A button widget that has properties commonly found in xTalk 'Button' objects.

Name: mouseDown

Type: message

Name: mouseUp

Type: message

Name: backgroundColor

Type: property

Name: foregroundColor

Type: property
*/
widget org.openxtalk.widget.oxtuikitbutton

metadata title is "Button-Widget-Demo" -- will be the displayed name for the control in the IDE.
metadata author is "Paul McClernan for OpenXTalk.org" -- writing credits
metadata version is "0.0.1" -- version num gets tracked on to the package name when an extension is packed as .lce
metadata preferredSize is "64,128" -- default Height,Width for new instance if the templateWidget has was not set
metadata _ide is "true" -- indicates module is a part of the IDE therefore is non-unloadable.
metadata userVisible is "true" --indicates if users see the module in listings or hides widgets from the Tools palette.
metadata svgicon is "" -- "M0,0v69.6c0,1.4,1.1,2.5,2.... z" -- SVG path string to use for the modules Icon in listings in the IDE.

-- dependancy declarations
use com.livecode.canvas
use com.livecode.string
use com.livecode.char
use com.livecode.array
use com.livecode.list
use com.livecode.widget
use com.livecode.engine
use com.livecode.library.widgetutils

---------  metadata for built-in properties ---------
private variable mForeColor	as Color
property foregroundColor		get getForeColor	set mForeColor
metadata foregroundColor.label is "Foreground Color"
private handler getForeColor() returns String
	return colorToString( mForeColor, false)
end handler

private variable mBackColor	as Color
property backgroundColor         get getBackColor set mBackColor
metadata backgroundColor.label is "Background Color"
private handler getBackColor() returns String
	return colorToString( mBackColor, false)
end handler

/**
Name: label

type: property

*/
private variable mLabel as optional String
property label              get mLabel              set setLabel
metadata label.default is "Label"
private handler setLabel(in pLabel as String)
	put pLabel into mLabel
	redraw all
end handler


/**
Syntax:
set the highlight of <widget> to {true|false}
get the highlight of <widget>

Summary: `true` if the widget is highlighted; `false` otherwise

Description:
Use the `highlight` property to test or control whether the button-bevel appears
pressed inwards indicating highlighted or not.
*/
private variable mHighlighted as Boolean
property "highlight"	get mHighlighted	set setHighlighted
metadata highlight.label is "Hilited"
metadata highlight.section is "Basic"
metadata highlight.editor is "com.livecode.pi.boolean"
metadata highlight.default is "false"
metadata highlight.user_visible is "true"
--- synonym ----
property hilite	get mHighlighted	set setHighlighted
metadata hilite.user_visible is "false"

public handler setHighlighted( in pHighlighted as Boolean)
     put pHighlighted into mHighlighted
	  redraw all
end handler

public handler OnCreate()
   ------- INTITIALIZE PROPERTY VARIABLES TO THIER DEFAULT VALUES HERE -------
	put the color of my foreground paint into mForeColor
	put the color of my background paint into mBackColor
	put 3 into mBevelWidth
	put "Label" into mLabel
	put false into mHighlighted
   redraw all
end handler

private variable mE as scriptObject
public handler OnOpen()
     put my script object into mE
     log [mE] -- the log command very useful for debugging, it outputs to the text field in the Extension Builder stack.
     redraw all
	  log "Open"
	  variable tRectPath as Path -- remember to declare variables before using them
	  put rectangle path of my bounds into tRectPath -- 'my bounds' is the widget's view port / clipping rect
end handler

public handler OnSave(out rProperties as Array)
   put colorToString(mBackColor, false)  into rProperties["backgroundColor"]
   put colorToString(mForeColor, false) into rProperties["foregroundColor"]
	put mHighlighted into rProperties["hilite"]
	put mLabel into rProperties["label"]
end handler

-- this handler is called when the widget is loaded
public handler OnLoad(in pProperties as Array)
	log "Load"
   -- log [pProperties] --> logging this doesn't seem to work! -- moreover, it silently fails making debugging difficult
   put stringToColor(pProperties["backgroundColor"]) into mBackColor
   put stringToColor(pProperties["foregroundColor"]) into mForeColor
	put pProperties["hilite"] into mHighlighted
	put pProperties["label"] into mLabel
   -- OnGeometryChanged()
   redraw all
end handler

private variable mBevelWidth as Number
property bevelWidth	get mBevelWidth	set setBevelWidth
metadata bevelWidth.label is "Bevel Width"
metadata bevelWidth.section is "Basic"
metadata bevelWidth.editor is "com.livecode.pi.number"
metadata bevelWidth.step is "1"
metadata bevelWidth.min is "0"
metadata bevelWidth.max is "255"
metadata bevelWidth.default is "4"
metadata bevelWidth.user_visible is "true"
public handler setBevelWidth( in pBevelWidth as optional Number)
	if pBevelWidth is nothing then
		put 0 into mBevelWidth
	else
  		put pBevelWidth into mBevelWidth
	end if
  redraw all
end handler --- setBevelWidth

public handler OnPaint()
   	variable tRectPath as Path -- remember to declare variables before using them
   	variable tInsetRect as Rectangle

	-------------- Top Bevel ------------------
	put rectangle path of my bounds into tRectPath -- 'my bounds' is the widget's view port / clipping rect
	if mHighlighted is true then
		set the paint of this canvas to solid paint with color [0.0, 0.0, 0.0, 1.0]
	else
		set the paint of this canvas to solid paint with color [0.5, 0.5, 0.5, 1.0]
	end if
   fill tRectPath on this canvas

	-------------- Bottom Bevel ------------------
	set the paint of this canvas to my foreground paint
   set the stroke width of this canvas to 2

	put rectangle [ mBevelWidth , mBevelWidth, my width - mBevelWidth, my height - mBevelWidth ] into tInsetRect
	variable tPath as Path -- declare a new canvas 'path' type variable to hold our drawing commands
	put the empty path into tPath -- init new blank path
	move to point [ 0, my height ] on tPath -- starting point left,bottom
	line to point [ my width, my height ] on tPath -- draw line to right,bottom
	line to point [ my width, 0 ] on tPath -- draw line upwards to right,top

	line to point [ the right of tInsetRect, the top of tInsetRect ] on tPath -- draw line to right,top of inset rect
	line to point [ the right of tInsetRect, the bottom of tInsetRect ] on tPath -- draw to to bottom right of inset rect
	line to point [ the left of tInsetRect, the bottom of tInsetRect ] on tPath -- draw line to bottom left of inset rect
	-- line to point [ 0, my height ] on tPath -- draw line back to starting point
	close path on tPath -- closing the path adds a line that connects it back to the starting point
	-- log[the instructions of tPath] -- just for development, check the canvas path string
	if mHighlighted is true then
		set the paint of this canvas to solid paint with color [0.5, 0.5, 0.5, 1.0]
	else
		set the paint of this canvas to solid paint with color [0.0, 0.0, 0.0, 1.0]
	end if
	fill tPath on this canvas

	-------------- Inner Rectangle ------------------
	set the paint of this canvas to my background paint
	put rectangle path of tInsetRect into tRectPath
	fill tRectPath on this canvas

	-------------- Label Text ------------------
	if mLabel is not nothing then
		set the paint of this canvas to my foreground paint
		set the font of this canvas to my font
		-- fill text mLabel at point [ my width /2 , my height / 2 ] on this canvas
		fill text mLabel at center of my bounds on this canvas
	end if
end handler

private variable mMouseButton as optional Number
public handler OnMouseDown()
     put the click button into mMouseButton
	  setHighlighted( true )
     post "mouseDown" && mMouseButton formatted as string
	  -- nice trick from HH:
	  -- post "set width of me to 230; set height of me to 115"
	  redraw all
end handler
public handler OnMouseUp()
     setHighlighted( false )
     post "mouseUp" && mMouseButton formatted as string
	  redraw all
     put nothing into mMouseButton -- change the Mouse Button state variable back to 'undefined' again.
end handler

public handler OnMouseLeave()
       if mMouseButton is not nothing then --- checks if the mouse button is still down
       --- if the mouse is still down then only UN-highlight the button
             put false into mHighlighted
         --- we can have a corresponding  OnMouseEnter handler that re-highlights the button if the mouse button is still down
        end if
        post "mouseLeave"
        redraw all
end handler
public handler OnMouseEnter()
       if mMouseButton is not nothing then --- checks if the mouse button is still down
       --- if the mouse is still down then only RE-highlight the button
             put true into mHighlighted
        end if
        post "mouseEnter"
        redraw all
end handler

end widget

User avatar
OpenXTalkPaul
Posts: 2379
Joined: Sat Sep 11, 2021 4:19 pm
Contact:

Re: An Introduction to eXTension Builder Widgets

Post by OpenXTalkPaul »

The properties that all widgets have visible in the Property Inspector by default are as follows:

Basic
• name
• kind
• tooltip
• visible
• disabled
Custom properties
• custom property sets
• custom properties
Colors
• ink
• blendLevel
Size and Position
• lockLoc
• width
• height
• location
• left
• top
• right
• bottom
• layer
Text
• textFont
• textSize
Advanced
• layerMode
• behavior
• traversalOn
• number

Any other property your widget may need will have to be added using properties meta data. Some of these automatic properties may be able to be hidden with the 'user visible' properties meta data as we did with the property-synonym example for 'hilite'.

The following is a list of of the properties a widget will have automatically, but that may or may not be visible in the propery inspector by default:

Code: Select all

•	abbrevId,
•	abbrevName
•	abbrevOwner
•	altId
•	backColor
•	backPattern
•	backPixel
•	blendLevel
•	borderColor
•	borderPattern
•	borderPixel
•	bottom
•	bottomColor
•	bottomLeft
•	bottomPattern
•	bottomPixel
•	bottomRight
•	brushColor
•	cantSelect
•	colors
•	customKeys
•	customProperties
•	customPropertySet
•	customPropertySets
•	disabled,
•	enabled
•	focusColor
•	focusPattern
•	focusPixel
•	foreColor
•	forePattern
•	forePixel
•	height
•	hiliteColor
•	hilitePattern
•	hilitePixel
•	ink
•	invisible
•	kind
•	layer
•	layerMode
•	left
•	location
•	lockLocation
•	longId
•	longName
•	longOwner
•	name
•	number
•	owner
•	parentScript
•	patterns
•	penColor
•	properties
•	rectangle
•	right
•	script
•	selected
•	shadowColor
•	shadowPattern
•	shadowPixel
•	shortId
•	shortName
•	shortOwner
•	textFont
•	textSize
•	textStyle
•	themeControlType
•	toolTip
•	top
•	topColor
•	topLeft
•	topPattern
•	topPixel
•	topRight
•	traversalOn
•	unicodeToolTip
•	visible
•	width
If a property that you want to use with your widget is in that list but not visiable by default, that means some metadata is needed for the property to be visible in the property inspector. 'Visible' does not necessarily mean 'Editable', properties may be set to be read-only (no 'setter' declared in it's property metadata) or write only (no 'getter' declared).

Some properties in this list may be be synonyms, for example textColor, foreColor and foregroundColor typically will all refer to the same property. It's best to stick with these property names and their typical usage for controls, because long-time xTalk scripters will have come to expect certain syntax to be usable for most UI controls.

Certain inherent properties, such as foregroundColor and backgroundColor have corresponding xBuilder syntax, but they may work differently than one might expect from an xTalk scripting perspective. foreColor/backColor in xBuilder can be referenced as 'the color of my foregound paint"/'the color of my background paint" respectively. Notice that for xBuilder there is a sort of 'main'-property of 'my paint' which has child-properties of foreground or background, which also have a sub-property (for lack of a better) of 'color' ('the color of'). Similarly 'my font' refers to a composite of both textFont ('the name of my font') and textSize ('the size of my font').

The syntax for these built-ins are defined in language modules, scriptObjects syntax coming from the 'Engine' module.
Most of the properties that have specific corresponding xBuilder syntax that you might want to use are related to Canvas drawing module.

Code: Select all

he paint of mCanvas -- set the paint of this canvas to solid paint with color [0,0,1,0.5] -- Blue with 50% tranparency
the antialias of mCanvas -- set the antialias of this canvas to false -- sometimes antialias looks sharper for fine details on low resolution screens
the opacity of mCanvas -- set the opacity of this canvas to 0.5
the blend mode of mCanvas -- set the blend mode of this canvas to "color dodge"
the stroke width of mCanvas -- set the stroke width of this canvas to 8
the fill rule of mCanvas -- set the fill rule of this canvas to "non-zero"
the cap style of mCanvas -- set the cap style of this canvas to "round"
the clipping bounds of mCanvas
the dashes of mCanvas
the dash phase of mCanvas
the font of mCanvas -- set the font of this canvas to font "Arial" at size 20
the bold of mFont -- set the bold of tFont to true
the image resize quality of mCanvas -- set the image resize quality of this canvas to "low"
the join style of mCanvas  -- set the join style of this canvas to "bevel"
the miter limit of mCanvas -- set the miter limit of this canvas to 1.5
the stippled of mCanvas -- set the stippled of this canvas to true
Colors:
the red/green/blue of mColor -- set the blue of tColor to 1 
the alpha of mColor -- put the alpha of tColor into tAlpha -- set the alpha of tColor to 0.5
Effects:
the blend mode of mEffect -- set the blend mode of tEffect to "color dodge"
the angle of mEffect
etc.
etc.
-- Widget properties are also used In the Widget module 'com.livecode.widget'
-- Widgets can also place 'child-widgets', using this feature has been called 'composed widgets'. 
-- There's a 'Widget' variable type for variable declarations to store references to other widgets.
the enabled of mWidget -- my enabled
the font of Widget -- my Font
Default properties for which there is no specific corresponding xBuilder syntax can still be referenced from xbuilder syntax but in a slightly different way. Any scriptObject's properties (not limited to widgets) can be referenced like these examples:

Code: Select all

set property "name" of my script object to "SuperWidget"

resolve script object "this stack" --  would return the stack that a widget is placed in.
-- resolved scriptObjects value is equivalent to 'the long name of'.
-- The return value is placed into the special global variable ''the result'.
set property "invisible" of the result to true 

get property "script" of my script object

--- Snippet required to ENABLE KEYBOARD-INPUT-FOCUS:
set property "traversalOn" of my script object to "true"
execute script "focus on me" in my script object
-- SYNTAX: execute script tScript [ in Object ] [ with Arguments ]
It is posible to add a custom property insprector property editor for a widget package, but that is beyond the scope of this tutorial and a matter for a seperate tutorial.
TerryL
Posts: 107
Joined: Sat Oct 16, 2021 5:05 pm
Contact:

Re: An Introduction to eXTension Builder Widgets

Post by TerryL »

I think Paul has finished his tutorial. Here's the complete .txt widget lesson in three parts. Thanks Paul.
Attachments
Widget Lessons.zip
(17.15 KiB) Downloaded 64 times
User avatar
tperry2x
Posts: 2769
Joined: Tue Dec 21, 2021 9:10 pm
Location: Somewhere in deepest darkest Norfolk, England
Contact:

Re: An Introduction to eXTension Builder Widgets

Post by tperry2x »

Thanks TerryL.
I've added this - please click 'redownload' to get the revised lessons.
Image

I actually split this into part 4, just because there's so much info there.
User avatar
OpenXTalkPaul
Posts: 2379
Joined: Sat Sep 11, 2021 4:19 pm
Contact:

Re: An Introduction to eXTension Builder Widgets

Post by OpenXTalkPaul »

Yeah, unless anyone has any questions or corrections, I think this is as far as I'm going to take this particular tutorial.
When I get some time I'll merge the 'bevel' coded here into the other 'Button-Widget' that was started in a different thread, about replacing all of the 'classic' controls with functionally equivalent widget versions.

If a few people would learn it enough and helped build them, that could happen quickly.
I'm going to make a repo for 'New Classics' and upload the ones I already have started (like 'button-Widget, and my slider control for example)
Post Reply

Who is online

Users browsing this forum: No registered users and 1 guest