Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Have callback emitted when a property changes #112

Closed
ogoffart opened this issue Nov 23, 2020 · 13 comments · Fixed by #6058
Closed

Have callback emitted when a property changes #112

ogoffart opened this issue Nov 23, 2020 · 13 comments · Fixed by #6058
Assignees
Labels
a:language-slint Compiler for the .slint language (mO,bF) a:tool classes & property system runtime core classes (SharedVector,SharedString) and property system (mO,bS) priority:high Important issue that needs to be fixed before the next release rfc Request for comments: proposals for changes

Comments

@ogoffart
Copy link
Member

Do we want a signal when a property changes.

If we do, what would be the syntax.
Some suggestions:

Rectangle {
     changed color => { debug("the color was changed");  }
     color changed => { debug("the color was changed"); }
     color =>  { debug("the color was changed"); }
     color ~> { debug("the color was changed"); }
}

Can there be multiple event handler? (signal can only have one event handler currently)
When is the signal emitted? (properties are lazy right now, and they can be dirty without changing)

@ogoffart ogoffart added the rfc Request for comments: proposals for changes label Nov 23, 2020
@ogoffart
Copy link
Member Author

ogoffart commented Feb 2, 2021

We have concern that this will not help keeping the language pure.
This kind of signal would violate the lazyness and make implementation of design tools harder.
If this is just to be able to debug changes, we should have a debugger that allow to watch property.

@tronical
Copy link
Member

tronical commented Dec 8, 2021

We discussed this further a bit today and one way to address the laziness would be to treat each handler as if it were tracking a "gui" related property. So a "change handler" would result in the implementation using

  • a PropertyChangeHandler which queues the associated callback for invocation and schedules the rendering of a new frame (if window is visible, otherwise post an event to wake up the event loop)
  • When the event loop wakes up to render a new frame, call all queued change handlers, after verifying that the property value has actually changed
  • If no other gui property changed, then nothing needs to be re-drawn

@ogoffart ogoffart changed the title Have signal emitted when a property change. Have callback emitted when a property change. Mar 12, 2022
@ogoffart
Copy link
Member Author

ogoffart commented Mar 12, 2022

In the mean time, as a workaround/hack, it is possible to put callback using the fact that property are re-evaluated when their dependency changes

export Demo := Window {
    callback my_callback(bool) -> color;
    my_callback(x) => { debug("HELLO", x); white; }
    background: my_callback(touch.has-hover);
    touch := TouchArea {  height: 50%;  }
}

In that example, background is going to be re-evaluated when dirty by the rendering code. and it is going to be dirty if the has-hover property is changed. So the my_callback callback is called every time the has-hover property changes.

(This is just a workaround/hack until such mechanism are implemented)

@ogoffart ogoffart added this to the 1.2 milestone Jun 29, 2023
@ogoffart ogoffart added a:tool classes & property system runtime core classes (SharedVector,SharedString) and property system (mO,bS) a:language-slint Compiler for the .slint language (mO,bF) labels Jul 13, 2023
@aamer-shaikh
Copy link

Will this also work with global properties ?

@ogoffart
Copy link
Member Author

We decided to go with the following syntax:

Rectangle {
     changed background => { debug("the background was changed");  }
}

The concern is still that this feature is opening a can of worm because some user would tend to use change handler instead of using binding. This is bad because it makes the design tool and tooling more complex.

We decided that we will execute change handler in the next event loop iteration. When the property is marked as dirty, it is added to a queue and is evaluated in the next change handler. In particular, this will force the evaluation of the property at component creation no matter if the propery is queried by other mean (even if the component is hidden). This kind of disable the lazyness.

Another point is the loop detection. Should the compiler detect loops? This can get quite difficult as it involves multiple components:

component Inner {
    in property <int> c;
    out property <int> b: c;
}

component Foo {
    out property <int> a: inner.b;
    property <int> xxx;
    changed a => {
        func();
    }
    function func() {
        xxx = inner.b;  // Ok even if a depends on b
        xxx = z; // ok even if  z depends on a
        inner.c = 42; // NOT OK: Loop: because a depends on c, and c is modified (so changes a)
    }

    property <int> z: a;

    inner := Inner {}
}

Another example:

component Base {
  callback do_something;
}

component Foo inherits Base {
    in property <int> xyz;
    changed xyz => { root.do_something() }
    
    public function change() { 
        xyz = 88;
    }
}

component XXX {
    Foo {
        do_something => { 
            self.xyz = 42;  // Should be an error (loop)
            self.change(); // another error as change also change xyz            
        }  
    }
}

And finally:

component Foo {
    b0 := LineEdit {
        // Ok, no loop so far
        changed text => { b1.text = text;  }
    }
    
    b1 := LineEdit {
        // but this would be a loop
        // changed text => { b0.text = text; }
    }
}

This last example is something that may actually be wanted, to synchronize two propery, possibly with some code in the middle. It is kind of alright if, like in this case, the property converge, but if the binding was b0.text = text + 'a' we would get an infinite loop and that's bad.

So since it is really hard to detect loops in the compiler, we might actually not do it and resort to runtime detection: if the evaluation of a debug handler mark the property dirty again, we would stop. but the problem is that if it causes another change handler to mark another property dirty which then mark the same property again we don't know that easily so that's also not trivial to detect and in the end we would still have a runtime infinite loop :-(

@ogoffart
Copy link
Member Author

ogoffart commented Aug 18, 2023

One of the reason we want change handler is to keep some property in sync when it is not possible to have a proper two way binding.

component SpinBox {
    property <int> value; 
    // ...
    LineEdit {
       // this doesn't work because that's not the same type
       // text <=> root.value;
       
       // this breaks the binding as soon as text is edited or set.
       text: root.value;
       
       // works one way   
       edited => { value = text.to-float(); }

       // this binding would actually solve the problem  (but written at the root, not here)
       changed value => { text = value; }
       
      // but what about other possibilities such as 
      text <=> root.value { 
          => text.to-float();
          <= root.value;
      } 
    }
}

This is similar to #814

@tronical
Copy link
Member

In particular, this will force the evaluation of the property at component creation no matter if the propery is queried by other mean (even if the component is hidden).

Hmm, maybe there was a misunderstanding. IMO we should not call change handlers at component creation time, only at the next event loop iteration. Can you elaborate what you mean?

@ogoffart ogoffart modified the milestones: 1.2, 1.3 Aug 31, 2023
@ogoffart ogoffart removed this from the 1.3 milestone Sep 8, 2023
@FloVanGH
Copy link
Member

I have the next use case for changed handlers. The native macOS scroll bars are only displayed if the user start scrolling in the corresponding direction. After a short time the scrollbar will be hide again. At the moment I have no way to detect if viewport-x or viewport-y is changed.

@tronical tronical changed the title Have callback emitted when a property change. Have callback emitted when a property changes Nov 3, 2023
@ogoffart ogoffart added this to the 1.4 milestone Nov 7, 2023
@FloVanGH
Copy link
Member

It would be also great to have a possibility to check if an animation is finished like an callback.

@ogoffart
Copy link
Member Author

ogoffart commented Feb 1, 2024

Another workaround was suggested in #4324
Edit: and in #4717

@ogoffart ogoffart added the priority:high Important issue that needs to be fixed before the next release label Feb 6, 2024
@Tmpod
Copy link
Contributor

Tmpod commented Feb 8, 2024

Would be quite nice to have this natively supported by Slint.

One specific use case for this I ran into the other day would be implementing something akin to QtQuick's ToolTip (which could probably be a built-in thing as well). At the moment, it's very impractical to show/hide a popup on hover, seeing as that information comes solely from a has-hover property, and PopupWindows are only controllable through function calls.

@ogoffart
Copy link
Member Author

There is an experimental implementation of change callbacks.
It is already in 1.6, but gated under the SLINT_ENABLE_EXPERIMENTAL_FEATURES=1 environment variable which needs to be set as you compile your app.
It is documented there:


It will most likely still not be active in 1.7, but you can already give it a try with the env variable, and feedback welcome.

@gitmacer
Copy link

My take on loop handling:
==Stack trace concept==
-Keep trace of Events
-throw exception/block Event when calling itself again
-option for allowing max self calls
-something like C# attribution system?
or element/event property?

  • recursion applications

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
a:language-slint Compiler for the .slint language (mO,bF) a:tool classes & property system runtime core classes (SharedVector,SharedString) and property system (mO,bS) priority:high Important issue that needs to be fixed before the next release rfc Request for comments: proposals for changes
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants