Relative positioning with Constraints for existing window

To prepare for Tahoe and the future I’m playing around with the Constraints example from @Greg_O . For a new window everything works as it should. I slapped some controls on the window, added constraints for the buttons at the bottom and finished. But for an existing window some controls move where they shouldn’t.

Window in the IDE:

The result:

Code:

// 1. Put window in Auto Layout
Self.ConvertToAutolayout(True, True)

// 2. LOCK all existing controls where you placed them in the IDE
//    (this is what keeps your labels, image, red bar, etc. from moving)
'Self.ConvertConstraintsForAllControls(1000)

For Each c As DesktopUIControl In self.Controls
Var skip As Boolean
if c isA DesktopButton then
'skip
else
self.ConvertConstraintsForControl(c, 1000)
end if
Next

// 3. SOFTEN ONLY the two footer buttons so they are allowed to move
Self.ConvertConstraintsForControl(PBGotIt, 200)
Self.ConvertConstraintsForControl(PBPurchase, 200)

// 4. Add the layout we actually want
Dim c As SOSLayoutConstraint

// right button
c = PBGotIt.RightAnchor.ConstraintEqualToAnchor(Self.RightAnchor, -20)
c.Priority = 1000
c.Active = True

c = PBGotIt.BottomAnchor.ConstraintEqualToAnchor(Self.BottomAnchor, -20)
c.Priority = 1000
c.Active = True

// left button
c = PBPurchase.BottomAnchor.ConstraintEqualToAnchor(PBGotIt.BottomAnchor, 0)
c.Priority = 1000
c.Active = True

c = PBPurchase.RightAnchor.ConstraintEqualToAnchor(PBGotIt.LeftAnchor, -10)
c.Priority = 1000
c.Active = True

I’ve talked to ChatGPT and experimented for hours. But I can’t figure this out. The controls still have the name of statictext so this is like European drinking age old.

Also my project is still desktop API 1 where I don’t want to convert the whole project of 650 classes or so to desktop API 2. @Greg_O : how much work would it be to convert the Constraints code to desktop API 1?

Edit: forgot the example.

constraints.xojo_xml_project.zip (36.3 KB)

The new window is shown on opening. Click on the OK button to show the old problematic window.

Im surprised that it doesn’t already work, but it’s possible I didn’t put the work in.

That said, if you’re going to start using constraints, you should add them all. What I see here is that you need constraints:

  • Standard gap between one of the buttons and the label above it.
  • Between the button baselines
  • standard gap between each of the text items

I’ll look at the library later today to see if there are omissions to be fixed.

Edit: I should look and see if they added any features to NSConstraint in the last few years too.

3 Likes

If possible I don’t want to put constraints for every window, dialog and container control. There are way too many. Also me lazy developer.

I still don’t understand why the new window works out of the box and the old window is so foobared.

Any change of getting the code backported to API 1? For reasons your code might become important.

I’ve made a branch for API1 compatibility. if you could try it out and see if there are any missing items (like things that you think should autocomplete but don’t), that would be very helpful.

Please note… There is still no support for mixed API layouts. That is, API1 controls on API2 views and API2 controls on API1 layouts.

1 Like

I should clarify. This branch is for API 1 compatibility for desktop only.

a) Crashes hard:

Exception Name: NSInvalidArgumentException
Description: -[XOJWindow bottomAnchor]: unrecognized selector sent to instance 0x7fcea5013f70
User Info: (null)

0 CoreFoundation 0x00007ff806b62a16 __exceptionPreprocess + 242
1 libobjc.A.dylib 0x00007ff806639fd2 objc_exception_throw + 62
2 CoreFoundation 0x00007ff806c35861 +[NSObject(NSObject) instanceMethodSignatureForSelector:] + 0
3 CoreFoundation 0x00007ff806b02234 forwarding + 1306
4 CoreFoundation 0x00007ff806b01c88 _CF_forwarding_prep_0 + 120
5 contraints api 1.debug 0x00000001046fc932 SOSConstraintKit.$BottomAnchor%o<SOSConstraintKit.SOSLayoutYAxisAnchor>%o + 418
6 contraints api 1.debug 0x0000000104748aec TimeRemainingWindow.TimeRemainingWindow.Event_Open%%o<TimeRemainingWindow.TimeRemainingWindow> + 5068
7 XojoFramework 0x000000010e8bb2a4 FireWindowOpenEvents + 328
8 contraints api 1.debug 0x000000010447e658 Window.Constructor%%o + 56
9 contraints api 1.debug 0x0000000104755119 _MakeDefaultView + 217
10 contraints api 1.debug 0x00000001047553dd _LateStartup + 77
11 XojoFramework 0x000000010e729158 _Z29CocoaFinishApplicationStartupv + 197
12 XojoFramework 0x000000010e7279ae _Z27PluginGetCocoaViewCallbacksv + 1024
13 CoreFoundation 0x00007ff806b0e1a6 CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER + 137
14 CoreFoundation 0x00007ff806b7045c ___CFXRegistrationPost_block_invoke + 86
15 CoreFoundation 0x00007ff806b703b7 _CFXRegistrationPost + 513
16 CoreFoundation 0x00007ff806aedb64 _CFXNotificationPost + 815
17 Foundation 0x00007ff808721fb2 -[NSNotificationCenter postNotificationName:object:userInfo:] + 82
18 AppKit 0x00007ff80a8a8ecd -[NSApplication _postDidFinishNotification] + 311
19 AppKit 0x00007ff80a8a8c16 -[NSApplication _sendFinishLaunchingNotification] + 215
20 AppKit 0x00007ff80a8a3ca8 _DPSNextEvent + 1811
21 AppKit 0x00007ff80b3bce8e -[NSApplication(NSEventRouting) _nextEventMatchingEventMask:untilDate:inMode:dequeue:] + 1268
22 AppKit 0x00007ff80b3bc961 -[NSApplication(NSEventRouting) nextEventMatchingMask:untilDate:inMode:dequeue:] + 67
23 XojoFramework 0x000000010e729d45 _Z29CocoaFinishApplicationStartupv + 3250
24 XojoFramework 0x000000010e729d89 _Z29CocoaFinishApplicationStartupv + 3318
25 contraints api 1.debug 0x00000001044b7518 Application._CallFunctionWithExceptionHandling%%op + 184
26 XojoFramework 0x000000010e8a5b79 _Z33CallFunctionWithExceptionHandlingPFvvE + 254
27 XojoFramework 0x000000010e729cfb _Z29CocoaFinishApplicationStartupv + 3176
28 AppKit 0x00007ff80a89ad1b -[NSApplication run] + 472
29 XojoFramework 0x000000010e8a4477 RuntimeRun + 41
30 contraints api 1.debug 0x000000010464f363 REALbasic._RuntimeRun + 19
31 contraints api 1.debug 0x000000010475577a _Main + 826
32 contraints api 1.debug 0x0000000104754b43 main + 19
33 dyld 0x0000000204bf6781 start + 3457

b) Upside down-ish:

Same window as before:

constraints api1.xojo_xml_project.zip (36.9 KB)

Tahoe 26.1 some beta with Xojo something.

So… are you able to step through that code and see where the error actually occurs? The error seems to be that bottomAnchor doesn’t exist on the item that it was being either applied to or requested from.

Well, it was the same code on the same controls. This makes the crash:

c = PBGotIt.BottomAnchor.ConstraintEqualToAnchor(Self.BottomAnchor, -20)

I tried to step through the code. It goes deep into the bowels of your code, then the canvas are painted and finally there is a crash.

This is the last code before the canvas repaint:

Return SOSLayoutYAxisAnchor.Create(getBottomAnchor(view.Handle))

The problem is probably that it’s pointing to the NSWindow and not the NSView within the window. I must’ve missed one of those conversions. Will look at it after work tonight.

It’s more likely that I didn’t back-port something correctly because it works just fine in API 2.

Ok. I’ve pushed up some fixes for the things I missed. I think you’ll find that this gets you much closer.

2 Likes

It would be so awesome to have a graphical solution for Anchors or LayoutConstraints inside the UI editor. With standard control sizes between platforms drifting apart (and obviously inside single platforms too, see Beatrix’ thread about button sizes and corners), the best way to handle a GUI (also to have it adapt to localisations more easily) would be to have native constraint support for desktop.
I just learnt that XAML knows something like

RelativeLayout.HeightConstraint="{ConstraintExpression Type=RelativeToParent,Property=Height,Factor=0.9,Constant=0}"

which looks a lot like a NSLayoutConstraint, and GTK knows constraints in the form of

target.attribute = source.attribute × multiplier + constant

which again is quite similar.

So we’d “just” need UI support to handle them graphically inside the IDE, right? :wink:

A graphical solution would be awesome. For Tahoe with the new SDK positioning a button in the IDE produces a wrong result. Now I don’t know how you can make a solution for SDK 26 and pre-SDK 26 at all.

At the moment I’d settle for any solution. I’m even thinking about doing a brute-force solution or using the Einhugur Custom Button which doesn’t increase in size. Users are not going to notice the larger buttons.

Added my thumb.
https://tracker.xojo.com/xojoinc/xojo/-/issues/76453

1 Like

Sorry to be such a pest:

  • The crash is gone.
  • The controls aren’t upside down anymore.

New issues:

  • The other controls are gone.
  • The buttons overlap each other.
// 1. Put window in Auto Layout
Self.ConvertToAutolayout(True, True)

// 2. LOCK all existing controls where you placed them in the IDE
//    (this is what keeps your labels, image, red bar, etc. from moving)
'Self.ConvertConstraintsForAllControls(1000)

'For Each c As RectControl In self.Controls
'Var skip As Boolean
'if c isA DesktopButton then
''skip
'else
'self.ConvertConstraintsForControl(c, 1000)
'end if
'Next

// 3. SOFTEN ONLY the two footer buttons so they are allowed to move
Self.ConvertConstraintsForControl(PBGotIt, 200)
Self.ConvertConstraintsForControl(PBPurchase, 200)
self.ConvertConstraintsForControl(CIcon, 200)

// 4. Add the layout we actually want
Dim c As SOSLayoutConstraint

c = CICon.LeftAnchor.ConstraintEqualToAnchor(self.LeftAnchor, 20)
c.Priority = 1000
c.Active = True

c = CIcon.TopAnchor.ConstraintEqualToAnchor(self.TopAnchor, 20)
c.Priority = 1000
c.Active = True

// right button
c = PBGotIt.RightAnchor.ConstraintEqualToAnchor(Self.RightAnchor, -20)
c.Priority = 1000
c.Active = True

c = PBGotIt.BottomAnchor.ConstraintEqualToAnchor(Self.BottomAnchor, -20)
c.Priority = 1000
c.Active = True

// left button
c = PBPurchase.BottomAnchor.ConstraintEqualToAnchor(PBGotIt.BottomAnchor, 0)
c.Priority = 1000
c.Active = True

c = PBPurchase.RightAnchor.ConstraintEqualToAnchor(PBGotIt.LeftAnchor, -10)
c.Priority = 1000
c.Active = True

I added constraints for one canvas but there is no change.

constraints api1.xojo_xml_project.zip (37.5 KB)

You’re not. Finding all of these errors is important if people are going to use it.

2 Likes

Regarding this. After having worked on this in the Xojo IDE and used it for my own projects, my wish is for a system where constraints don’t affect the layout editor but can be visualized somehow. Perhaps a mini “run mode” where the IDE strips out all code and shows what the controls will do would be helpful, but not if you like doing manual constraint work like me.

While I understand Xojo’s want to have this right in the layout editor, the iOS experience is a disaster IMHO.

as far as bringing them to other platforms… well that’s even trickier. Yes, xaml has their own system, but it’s not going to match the behavior of Mac or iOS 100% (not that it should either). That pushes xojos cross-platformness even farther afield. When I was working on it, the only thing that seemed remotely tenable was for xojo to add a cross-platform engine themselves that was basically agnostic. That unfortunately means that Xojo would need to build it, do it right the first time, and have some people actually understand constraints completely (and continue to do so). And it would be critical to get it right, straight out of the box.

I’d like to point something out that you may not know. When working with constraints, you should be constraining from right to left and from bottom to top. So the first two constraints are fine, but the three with the negative offsets could be problematic. In those cases you should be switching the thing that’s being constrained with the constraining object. for instance with this code:

it should instead be

c = Self.BottomAnchor.ConstraintEqualToAnchor(PBGotIt.BottomAnchor, -20)

… and if you really want to look the best…

c = Self.BottomAnchor.ConstraintEqualtoSystemSpacingBelowAnchor(PBGotIt.BottomAnchor)

so if the system spacing has changed at all over the years, you’ll automatically get it.

More before I nod off for the night…

You need to set a width and height anchor for CIcon if you want it to appear:

CIcon.WidthAnchor.ConstraintEqualToConstant(CIcon.Width).Active = True
CIcon.HeightAnchor.ConstraintEqualToConstant(CIcon.Height).Active = True

I see you’ve used a control array for the labels. I never planned for that and I’ll just say YMMV in that case.

If you’re having trouble with a layout, you may want to call these two commands (they only work in debug mode and Apple would reject your app anyway if you tried to use it). They will show a window on the screen telling you about problems with your auto layout config. I find It most helpful when I’ve got a control that seems to move erratically because it shows you which ones are constrained incorrectly.

Self.VisualizeConstraints(SOSConstraintKit.Axis.Horizontal)
Self.VisualizeConstraints(SOSConstraintKit.Axis.Vertical)

I also added some more constraints to settle out the layout…

constraints api1_part2.xojo_xml_project.zip (37.5 KB)

Hi all!

In my apps I often have a method called “position_controls” that puts all conrols in it’s correct position and sets their size and visabilty depending on the window size, depending on each other, depending on the OS, etc. Sometimes depending on the text length if a label is localized.
This is really simple and straigth forward in Xojo code. For sure it can be complex if you have many controls. But you can work with virtual areas and variable and different margins. This works on all platforms and seems to me a lot easier than fighting against/with such a complex constraint system.

That sounds utterly painful.