nucleic / enaml

Declarative User Interfaces for Python
http://enaml.readthedocs.io/en/latest/
Other
1.53k stars 130 forks source link

Using Property member in enaml doesn't take the set value #413

Closed frmdstryr closed 3 years ago

frmdstryr commented 4 years ago

Any ideas as to why this doesn't work and how to fix it? The x = 10 seems be be completely ignored.

from atom.api import Atom, Float, Typed, Property
from enaml.core.declarative import d_
from enaml.widgets.api import Window, Container, Label

class Point(Atom):
    x = Float(strict=False)
    y = Float(strict=False)

class Shape(Label):
    position = d_(Typed(Point, ()))

    def _get_x(self):
        return self.position.x

    def _set_x(self, v):
        self.position.x = v

    x = d_(Property(_get_x, _set_x))

enamldef Main(Window): window:
    Container:
        Shape: s1:
            x = 10
        Label:
            # Why does this not read read 10?
            # Makes no difference if I use `s1.x`
            text = 'X: %s' % s1.position.x
        Label:
            text = 'Slot: %s' % Shape.x.get_slot(s1)

Screenshot_20200416_081022

If I do the same in atom it works as expected, eg

from atom.api import *

class Point(Atom):
    x = Float(strict=False)

class Shape(Atom):
    position = Typed(Point, ())

class Shape(Atom):
    position = Typed(Point, ())
    def _get_x(self):
        return self.position.x
    def _set_x(self, v):
        self.position.x = v
    x = Property(_get_x, _set_x)

s = Shape()
s.x = 10
s.position.x
Out[8]: 10.0
s = Shape(x=10)
s.position.x
Out[10]: 10.0

I really want something like an AliasMember('position.x') that has no storage but delegates to another member. I tried wrapping the Property in a Delegate but that doesn't work either.

Any help is appreciated.

MatthieuDartiailh commented 4 years ago

I will try to have a look. I am not sure what is going here...

frmdstryr commented 4 years ago

It seems to be something with how atom handles defaults with a Property because enaml is setting the default handler it's just not being called.

So this "works" but doesn't seem like it should be necessary:

    def _get_x(self):
        m = self.position.get_member('x')
        if m.get_slot(self.position) is None:
            v = self.get_member('x').do_default_value(self)
            self._set_x(v)
        return self.position.x
MatthieuDartiailh commented 4 years ago

There is no trivial work around around that issue. In particular using a property will cause you a lot of headache when it comes to notifications. At this point, it would just be better to have a custom Member subclass. Could you elaborate on your real world use case ? I would like to better understand it, in order to decide if it makes sense to add such a member to atom.

frmdstryr commented 4 years ago

Technically it's not needed, but I would like to be able to define the same thing as enaml's alias as an Atom member. Eg alias x: self.position.x

The use case is for the other OSS project I work on DeclaraCAD. This would let me define positional/directional constraints between shapes using enaml by binding s1.x := s2.position.x, where x is an alias to self.position.x.

Currently reading shape.position.x is not a problem but I cant just use position.x := other_shape.position.x since enaml doesn't allow that unless position is an alias.

MatthieuDartiailh commented 4 years ago

I can see the point. However this slightly reminds me of chained static observers to arbitrary levels which I know @sccolbert has seen been abused in the Traits/TraitsUI context. This is why atom does not support more than one level of indirection on those. @sccolbert I would be interested in your opinion on this. I feel like this is definitively possible at the atom level and could be prototyped in pure Python. The handling of the notifications will be a bit tricky due to the lack of unified manual notification API (static and dynamic observers are handled separately) but it should be doable. @frmdstryr let me know if you need help coming up with a prototype. Depending on @sccolbert answer we may incorporate it in atom later on.

frmdstryr commented 3 years ago

Decided to just avoid doing this altogether.