Save yourself some time

I see a lot of times people write code like

#if debugbuild
   system.debuglog "some message"
#endif

It means you type multiple lines of code all the time and if you forget to wrap things in the #if debugbuild then you have logging messages in your builds that maybe should have been turned off

Personally I have a module, named Debug, that has a method in it – Log

Then where ever I want a debugging message I can write

debug.log "some message"

which is also shorter

Debug.Log is implemented as (I have other overloads as well)

Sub Log(msg as string)
  #if debugbuild
    System.Log(System.LogLevelDebug, msg)
  #endif
End Sub

This way I can write my debug.log messages and know that every last one will disappear in a built app.

And I type a little bit less as well

Extending end of line

In Xojo EndOfLine is two things
One is a global method and the other a Class.

Because of how the compiler works it realizes when you mean each one.

In a line of code like

var eol as EndOfLine

EndOfLine can, in that context, ONLY be a TYPE name. So the compiler knows you mean the EndOfLine class.

But when you do

dim eol as EndOfLine = EndOfLine

it knows the FIRST one is the TYPE, the class, and the second HAS to be something that returns a value – in this case the method named EndOfLine. (And this design is something you can also use in your own code)

What this means is that since EndOfLine is also a CLASS it can be extended. And that makes it possible to add things like a Length function.

Public Function Length(extends e as EndOfLine) as integer
  
  Dim s As String = e
  
  Return s.length
  
End Function

And there you go. Now you can write code like

var position as integer = otherString.IndexOf(EndOfLine)
var leftChars as string = otherString.left(position)
var eol as EndOfLine = EndOfLine
var rest as string = otherString.Middle(position + eol.length)

And NOT have to convert the EndOfLine into a string to get the length

The trouble with tribbles….

Or in this case linear gradients. If you want linear gradients to work well you really need to calculate the correct start and end points.

This is especially true if you dont want a gradient that simply runs from left to right or top to bottom (or their reverse directions)

While figuring out exactly what I needed to do I had several different suggestions from different people that I found dont work quite right.

If you simply compute the points using sin() and cos() of whatever angle you wont get the right answer. Depending on what radius you use you’ll either get clipping of the gradient inside the rectangle you want to fill or you’ll position the start & end points far enough outside that rectangle that if you have lots of gradient steps some wont show.

For example, if we set the start and end points so they are slightly angled across a square canvas with code like

Var linearBrush As New LinearGradientBrush
linearBrush.StartPoint = New Point(0, g.Height/2-20)
linearBrush.EndPoint = New Point(g.Width, g.Height/2+20)

linearBrush.GradientStops.Add(New Pair(0, Color.Red))
linearBrush.GradientStops.Add(New Pair(0.4, Color.Yellow))
linearBrush.GradientStops.Add(New Pair(0.7, Color.Magenta))
linearBrush.GradientStops.Add(New Pair(1.0, Color.Blue))

g.Brush = linearBrush

g.FillRect(0, 0, g.Width, g.Height)

we get a clipped gradient fill in the upper left corner

because the gradient “rectangle”, when rotated, doesnt overlap the entire rectangle we filled. But notice how the gradient starts at blue & ends at red very nicely. It shows all our steps quite completely.

When you start the gradient too far outside the rectangle you get some steps in the gradient not being as visible as they should be (this one IS much harder to see but notice how little blue there is)

I set the rotation to be 22.5 degree and then set the start & end points using sin & cos using a radius that is the hypotenuse of the right trangle formed from the rectangle center point to the lower right corner (any corner would do since this is a square – it get worse if you use a non-square rectangle) The red circle around the filled rectangle outlines where the start and end points might be place for any rotation. Note how far outside the rectangle they are and the more color steps there are the more get clipped out. The code is as follows

Const pi As Double = 3.1415926535897932384626433
Const degreesToRadians As Double = pi / 180
Const degrees As Double = 22.5


Var linearBrush As New LinearGradientBrush

Var hypot As Double = sqrt( (g.width - g.width/2)^2 + (g.height - g.height/2)^2)



Dim startx As Double = cos(degrees * degreesToRadians) * hypot
Dim starty As Double = sin(degrees * degreesToRadians) * hypot

linearBrush.StartPoint = New Point(g.width/2+startX, g.height/2+startY)
linearBrush.EndPoint = New Point(g.Width/2-startx, g.Height/2-startY)

linearBrush.GradientStops.Add(New Pair(0, Color.Red))
linearBrush.GradientStops.Add(New Pair(0.4, Color.Yellow))
linearBrush.GradientStops.Add(New Pair(0.7, Color.Magenta))
linearBrush.GradientStops.Add(New Pair(1.0, Color.Blue))

g.Brush = linearBrush

g.FillRect(0, 0, g.Width, g.Height)

You can see that in the two images about. Nove how much red & blue the first has compared to the second,. Yet the gradient is set up identically between them to trabsition between the same number of colors using the same number of steps.

The problem is that circle is much too large. Of course we could use a much smaller circle but if we set it so its inside the rectangle then we get the clipping problem back.

So whats the “right answer” ? So far it seems to me that the correct answer is to find a minimal bounding rectangle that is rotated however we want. And to make sure our gradient starts on the edges of that bounding box.

OK so what the kec does that mean ? 😛

Well – in action it looks like this

The white square is the rectangle we’re going to fill with the linear gradient. The light purple circle is the same circile that encloses the rectangle computed much like the one just above here. And the light blue line is where our gradient will run either left to right or right to left (we can invert those two trivially)

Where the gradient runs exactly left to right or top to bottom we can put the start & end points right on the white square. But as we start to rotate the gradient you’ll see some yellow and red lines show. Where the blue line crosses the yellow line is where we need to put the start & points of the gradient. And note those points are NOT on that circle drawn outside the rectangle. But those red & tyellow lines for the minimum enclosing rectangle that, if the gradient starts on its edges, then the entire rectangle in the white square will be filled. The result is this

I expect this mechanism can be extended to apply to any shape using a convex hull approach.

I’ll see about whether I can release this code or not.

Either way you have an explanation of how to compute the right start and end points for linear gradients and apply then to rectangular areas. And you’ll know why things might get clipped off or not fill quite right if you do something else.

Now I’m sure some of you will get to the end of this and go “Well Duh Norm”

Take this post purely as evidence my trig sucks 😛
I freely admit that. It does suck.

Enjoy !

Practices you may see in use but you’ll regret using

Some code looks enticing – but in the long run may cause you as many headaches as it solves.

Some is really innocuous looking like :

var position as integer = otherString.IndexOf(EndOfLine)
var leftChars as string = otherString.left(position)
var rest as string = otherString.Middle(position + 1)

There’s an assumption in this little bit of code and it may not be entirely obvious what it is.

Try it on a mac and its probably fine. And on Windows it makes a mess. The reason is the EndOfLine marker on Windows is of course two characters not 1 and so the assignment of rest is wrong and off by 1.

The fix is to simply use the Length of EndOfLine instead of 1. Note that autocomplete dislikes EndOfLine.Length and it wont compile forcing you to use a temporary variable for this. see this bug report and you appear to not be able to extend it since there’s a global method and class with the same name in the framework.

The following works but you end up declaring a variable to hold the EOL char(s).

var position as integer = otherString.IndexOf(EndOfLine)
var leftChars as string = otherString.left(position)
var eol as String = EndOfLine
var rest as string = otherString.Middle(position + eol.length)

Another that I’ve run into is TextInputStream.ReadLine. The issue here is that if you assume ReadLine reads “one line” then you can be in for a surprise since “one line” depends on what line endings are in use in the file.

If you’re on macOS reading lines from a file from Windows then the lines will include half of the Windows end of line marker (which is 2 bytes – &h0D + &h0A)

And TextinputStream has no mechanism built in to say “please translate line endings as you read” which would be really handy. So you either read all the text in (hoping its not GB of data) and use ReplaceLineEndings, or read “line by line” and clean them up. Or write a new Class that wraps a binary stream that does allow you to specify both an encoding and whether line endings should be converted to the platform specific ones. I chose the latter route. I’ll see about publishing my class on my Github

Any others you’ve run into that look simple and clear but are actually problematic ?

Merry Christmas …

I dont know all the proper holiday greetings for everyone celebrating something at this time of year. For that I will apologize.

But it doesnt change my wish that everyone have the best holiday season this year especially after the crazy year we have all endured.

Yes. To everyone. Those who have helped me along the way. And those very few who have tried to harm me.

Enjoy the season the best you can with those you can in the hopes we can all get together in the coming year.

Big Sur and Xojo

There’s a great debate about what versions of Xojo work on Big Sur.

IF you want to build DESKTOP apps you must use 2019r3.2 or later. Even an empty project fails to build properly.

They rely on a framework that Apple has removed and so the linking of them will fail.
Linking Executable ld: file not found: /System/Library/PrivateFrameworks/UIFoundation.framework/Versions/A/UIFoundation for architecture x86_64

Quite literally this framework does not exist – its got nothing to do with code signing etc

IF you are NOT using Xojo for desktop projects then you may be able to continue using old versions. I’ve successfully used 2017r3 and 2018r4 on Big Sur for Web projects.

I have to admit I have NOT tested console apps.

If you have it would be nice if you posted your results in the comments

Why everything in a global namespace sucks

Alternatively – why Xojo should put everything in a Xojo namespace and NOT make anything global

I have a few projects that are the results of many many years of coding.

Updating them to the very latest version of Xojo ends up with lots of conflicts since Xojo added a String module with their own global extends for EndsWith and many others that have existed in other peoples code for years.

And, since Xojo didnt add both an extends version AND a non-extends version I have to go adjust all my code because they decided to take over that name. EndsWith is just one example of this. All in all I have about 15000 items like this to go deal with.

And every time I update to a newer version of Xojo, since everything is always in a global namespace, ANY update of Xojo _may_result in many more conflicts like this.

And this taking over of names users may already have used is the problem with a framework that has everything only as extends and always global. Every update may take more of the names you have used for however long and force you to rename and update your code IF you want to keep using code that you may have no reason to want to update.

While I get the syntactic sugariness of the dotted methods names – its hose dotted names that cause this as they MUST be global.

If Xojo had named their module String AND made the methods in string protected then you could always unambiguously refer to yours AND theirs and never have confusion.

Thats not what they have chosen to do and so now we, the users, may end up having to adjust our already working code every time Xojo decides they want to take over another name for some extra functionality.

Inconsistent

In Xojo if you define a class and add a constant you can refer to that constant like

// suppose class1.kConstant is a string constant
dim v as string = Class1.kConstant

Basically you can refer to the Class type and access the constant.

Or you can do

// suppose class1.kConstant is a string constant
dim c as new Class1
dim v as string = c.kConstant

and refer to the constant NOT by referring to the class but to an instance of that class

Enums dont work that way at all. You can ONLY refer to the enum by referring to the Class type – not an instance. For example

// suppose class1.MyEnum is an enum with a value named EnumValue1
dim v as class1.MyEnum = Class1.MyEnum.EnumValue1

This works. What wont work is

// suppose class1.MyEnum is an enum with a value named EnumValue1
cim c as new Class1
dim v as class1.MyEnum = c.MyEnum.EnumValue1

You cannot refer to an enum through an instance – you MUST use the class type.

This seems inconsistent between these two very similar types of entities in Xojo.

Curiously this inability to refer to an enum through an instance is what causes this weird seeming bug

I filed a bug report but it was closed within an hour.

I made a feature request to make Constants and Enumerations behave same in this regard

Xojo and cross compiling for macOS

Way back when, when Apple still permitted 32 bit compiles, the landscape for what you had to do to build an app that would work on macOS was a lot simpler.

Now, not only do you have to build the right kind of executable you need to also do code signing, even if only ad-hoc signing, to run even a debug app on the very latest versions of macOS.

First off you cant use the native file systems of Windows or Linux to create a bundle. Those file system dont support what macOS needs to create a proper bundle. So you have to build into a zip file or some other container type that DOES.

As well, since you need to sign the build, you either sign it when you compile OR you try & somehow sign when you finally have the app ON a Mac or suitable VM. At best Xojo might make a remote debug run from Windows or Linux to macOS compile & link on Windows or Linux and then send the code to the remote stub. Then the remote stub might unpack this and apply an ad-hoc signature & start the application. You would still needs a Mac, or suitable VM, to sign code to be able to remote debug the app.

But this still leaves developers on Windows & linux unable to build & sign, or notarize, a Mac app that they could send to anyone else. I dont expect that signing a macOS build on Windows or Linux time will be possible since this would require Apples security framework be ported to another OS. I doubt this will ever occur. Trying to duplicate what Apple has implemented in their framework AND keeping up to date with it, without Apples source code, seems very difficult if not downright impossible.

And, to sign and submit an app to the App store or to notarize one you still need a Mac and do those tasks there.

It seems that if you want to develop FOR a Mac you are going to have to do that ON a Mac for the foreseeable future – if not forever.

Now, I want to be clear. This is NOT Xojo’s fault. And I dont expect they will be able to “fix” it. Apple has changed the rules a lot and made things more difficult. And, the side effects are what they are. Don’t blame Xojo for this one.

Where Xojo isnt fully 64 bit

In Xojo an array is limited to being no more than 2 billion entries (2,147,483,646 )

But arrays are documented as using integers which, in a 64 bit world, should be a LOT more entries than this.

When this post was written the documentation isnt clear about what the maximum size of an array IS. It just said :

sizeIntegerThe upper bound of the array. Size must be a number or a constant.

Thats been altered now and the case noted above marked “fix” . Documenting a shortcoming isnt the same as fixing the shortcoming though.

In a 64 bit application Integer usually means a 64 bit integer but in reality arrays dont use Integer, they use Int32. This limitation isnt considered a bug as

The Integer type is Xojo compiler/framework magic. Behind the scenes, LastIndex (and Ubound) are Int32 because we still support 32 bit OSs. Should the day come when we no longer support 32 bit operating systems, we could then change arrays to Int64 but even that would then be a LOT of work involving updating the compiler, the frameworks, etc.

In most Xojo code and use cases we never have to worry about “compiler magic”1 etc and integer is an Int64 in 64 bit apps and an Int32 in 32 bit programs.

As users we shouldn’t have to know, or care about “magic” – it should just work like it does everywhere else.

1 In a language like C++ this “magic” amounts to something like

// your C++ compiler MAY define specific tags to tell if you're compiling 32 or 64 bit
#if 64Bit // with whatever specific code your C++ compiler needs to tell this is a 64 bit build
   typedef int64_t integer ;
#elif 32Bit // with whatever specific code your C++ compiler needs to tell this is a 64 bit build
   typedef int32_t integer ;
#endif

They even have code in their headers for plugins that handles this (this is from the 2020r2 Plugin SDK include file REALplugin.h)

#if defined(__LP64__) || defined(_WIN64)
	#define TARGET_64BIT 1
#else
	#define TARGET_64BIT 0
#endif

#include <stdint.h>
#include <stddef.h>

typedef int64_t RBInt64;
typedef uint64_t RBUInt64;
typedef int8_t RBBoolean;
typedef uint32_t RBColor;

#if TARGET_64BIT
	typedef int64_t RBInteger;
	typedef uint64_t RBUInteger;
#else
	typedef int32_t RBInteger;
	typedef uint32_t RBUInteger;
#endif

UPDATE : Christian found another spot where Xojo isnt 64 bit

UPDATE : Xojo fixed the documentation and marked the original report “fixed”. But fixing the docs doesnt fix the actual issue reported.
I have an older feature request to make arrays 64 bit