How to bring control to front?

I am implementing simple tools windows based on the TCastleShape. You can drag them around and close them. But I can’t figure out how to bring them to the front when you click on them. No matter what I do they are in the order created (using Insertfront). I have tried setting KeepInFront on the active window (and unsetting it on the old active window) but it doesn’t affect order of controls. This seems like it should be simple, so maybe I am just overlooking the solution. I have also tried to remove the control from the parent and readding it, but this causes crash when done from from the press handler. It seems like it should just be mycontrol.movetofront or something like that.

Changing KeepInFront while the control is already added indeed does nothing.

It’s a trap, although documented —

     { Keep the control in front of other controls (with KeepInFront=@false)
      when inserting.

      TODO: Do not change this property while the control is already
      a child of something. }
    property KeepInFront: boolean read FKeepInFront write FKeepInFront
      default false;

I hesitate what to do with KeepInFront in general, maybe it should be deprecated.

Removing it + adding is indeed the proper solution. I see we even have our press handler “robust” to handle the case when control removes itself from the parent list… So, this should work.

Can you send a testcase that shows a crash? I’d like to fix it.

This is the extent of that window code:

 TGameForm = class( TCastleShape )

     constructor create( aowner : tcomponent ); override;
     destructor destroy; override;

     procedure PressClosebutton(const Sender: TCastleUserInterface;
                                const Event: TInputPressRelease; var Handled: Boolean);
     procedure PressHeader(const Sender: TCastleUserInterface;
                           const Event: TInputPressRelease; var Handled: Boolean);
     procedure ReleaseHeader(const Sender: TCastleUserInterface;
                             const Event: TInputPressRelease; var Handled: Boolean);
     procedure HeaderMotion(const Sender: TCastleUserInterface;
                            const Event: TInputMotion;
                            var Handled: Boolean);

     public

     HeaderBar : TButtonBar;
     Dragging  : boolean;

   end;

implementation

constructor TGameForm.create( aowner : tcomponent );
 var button : TIconButton;
 begin
   inherited create( aowner );
   VerticalAnchorParent := vpTop;
   VerticalAnchorSelf := vpTop;
   HorizontalAnchorParent := hpLeft;
   HorizontalAnchorSelf := hpLeft;
   HeaderBar := TButtonBar.create( self );
   HeaderBar.Height := 20;
   HeaderBar.VerticalAnchorParent := vptop;
   HeaderBar.VerticalAnchorSelf := vptop;;
   insertfront( HeaderBar );
   button := TIconButton.create( HeaderBar );
   button.setcaptiontooltip( 'x', 'close' );
   button.OnPress := @PressCloseButton;
   HeaderBar.InsertFront( button );
   HeaderBar.Color := Vector4(0,0,0,0.5);
   dragging := false;
   HeaderBar.OnPress := @PressHeader;
   HeaderBar.OnRelease := @ReleaseHeader;
   HeaderBar.OnMotion := @HeaderMotion;
 end;

destructor TGameForm.destroy;
 begin
   inherited;
 end;

procedure TGameForm.PressClosebutton(const Sender: TCastleUserInterface;
                                      const Event: TInputPressRelease; var Handled: Boolean);
 begin
   Handled := true;
   parent.RemoveControl(self);
 end;

procedure TGameForm.PressHeader(const Sender: TCastleUserInterface;
                                const Event: TInputPressRelease; var Handled: Boolean);
 begin
   Handled := true;
   Dragging := true;
   parent.RemoveControl(self);
   parent.InsertFront(self);
 end;

procedure TGameForm.ReleaseHeader(const Sender: TCastleUserInterface;
                                  const Event: TInputPressRelease; var Handled: Boolean);
 begin
   Handled := true;
   Dragging := false;
 end;

procedure TGameForm.HeaderMotion(const Sender: TCastleUserInterface;
                                 const Event: TInputMotion;
                                 var Handled: Boolean);
 var delta : TVector2;
 begin
   if Dragging then
    begin
      Handled := true;
      delta := ( Event.Position - Event.OldPosition ) * 0.6;

      Translation := Translation + delta;
    end;
 end;

The header is a TButtonBar from my Behavior Tree editor which is also a TShape. When you click the header it calls PressHeader and where the parent.removecontrol(self) works but then the parent.insertfront(self) crashes.
image

[update 1]
ohh. guessing parent is no longer valid. I think that is the problem.
[update 2]
Yes, that was the problem. I should have realized!!

Also, what is the correct way to use the mouse delta as the window delta (see HeaderMotion)? I have a 0.6 ‘kludge factor’ which works to keep the mouse and moving window synced when the program starts, but isn’t correct and becomes wrong if you change the application size.

1 Like

Sorry for a delay in responding – I just got back to this thread, and I see the main issue is solved. Yeah, Parent becomes nil after the control removes itself from this parent. This is by design, it’s correct, albeit I see how it was unexpected in this context.

Simply doing something like

var
  OldParent: TCastleUserInterface;
begin
  ...
  OldParent := Parent;
  OldParent.RemoveControl(Self);
  OldParent.InsertFront(Self);
end;

should fix the issue, I guess that’s what you did now.

Thinking about it – adding a MoveToFront method, like you suggested, that would deep down also really move in the list (so would not have any unexpected behavior due to remove + reinsert) seems like a good idea. Done! In Add TCastleUserInterface.MoveToFront · castle-engine/castle-engine@0b13a1c · GitHub .

So you can just call MoveToFront within that TGameForm.PressHeader now.

The Event.Position - Event.OldPosition you get is in final device pixels. How to treat it depends on how do these pixels map to the coordinates map to what you want to shift, which is Translation of TCastleShape in this case. Ah, so just convert them to the “unscaled” coordinates, by UIScale.

delta := ( Event.Position - Event.OldPosition ) / UIScale;

should be a replacement for

delta := ( Event.Position - Event.OldPosition ) * 0.6;

( I have a TODO to maybe express these things in unscaled coordinates, but it’s uneasy due to backward compatibility now. It will require inventing a better names than Position for it :slight_smile: Maybe Event.Translation ? And then we can deprecate Event.Position without breaking its meaning. )

1 Like