Skip to content

Instantly share code, notes, and snippets.

@SuzanneSoy
Created August 25, 2022 21:22
Show Gist options
  • Save SuzanneSoy/3630eaf0cd4158a1d82d9d89fb85469a to your computer and use it in GitHub Desktop.
Save SuzanneSoy/3630eaf0cd4158a1d82d9d89fb85469a to your computer and use it in GitHub Desktop.
Reactive model→view function (full of bugs)
if True:
from PySide import QtGui
from PySide import QtCore
import threading
from collections import defaultdict
# resizing a window too fast seems to allow for the window manager to drop some requests.
# for experimentation, we resize a label within a fixed-size window.
w = QtGui.QLabel()
w.show()
w.setGeometry(QtCore.QRect(100,100,400,400))
propagators = {
'width': lambda src, dest:
dest.__self__.setGeometry(QtCore.QRect(dest.__self__.x(), dest.__self__.y(), src.value, dest.__self__.height())),
'height': lambda src, dest:
dest.__self__.setGeometry(QtCore.QRect(dest.__self__.x(), dest.__self__.y(), dest.__self__.width(), src.value))
}
def propagate(src, dest):
propagators[dest.__name__](src, dest)
def connect(src, dest):
src.register('set', lambda _: propagate(src, dest))
class Model:
def __init__(self):
self._lock = threading.Lock()
self._eventTypeToCallbacks = defaultdict(dict) # key: string i.e. event type, value: {callback}
self._eventTypeToCurrent = dict()
self._nextId = 0
def notify(self, eventType, info):
with self._lock:
self._eventTypeToCurrent[eventType] = info
# avoid race conditions by caching the list of callbacks, that way if
# one of the callbacks modifies the list of callbacks, it won't cause
# issues. Also, it prevents deadlocks if one of the callbacks tries to
# perform an operation which would try to acquire the lock.
cachedCallbacks = [c for c in self._eventTypeToCallbacks[eventType].values()]
for c in cachedCallbacks:
c(info)
def register(self, eventType, callback):
with self._lock:
callbackId = self._nextId
self._nextId = self._nextId + 1
self._eventTypeToCallbacks[eventType][callbackId] = callback
cachedHasCurrent = False
if eventType in self._eventTypeToCurrent:
cachedCurrent = self._eventTypeToCurrent[eventType]
cachedHasCurrent = True
if cachedHasCurrent:
callback(cachedCurrent)
return callbackId
def unregister(self, event, callbackId):
with self._lock:
del self._eventTypeToCallbacks[eventType][callbackId]
class Connection:
def __init__(self, src, dest):
self.src = src
self.dest = dest
class ModelFragment(Model):
def __init__(self, name, v):
super(ModelFragment, self).__init__()
self.name = name
if isinstance(v, ModelFragment):
self.value = v.value
else:
self.value = v
self.notify('set', self.value)
def setValue(self, v):
self.value = v
self.notify('set', self.value)
def __rshift__(self, other):
return Connection(self, other)
class ModelPropertySet(Model):
def _getter(self, p):
def real_getter(self):
return self._properties[p]
return real_getter
def _setter(self, p):
def real_setter(self, v):
if isinstance(v, Connection):
connect(v.src, v.dest)
else:
self._properties[p].setValue(v)
return real_setter
def __init__(self, theClass, properties):
super(ModelPropertySet, self).__init__()
self._properties = {}
for (p, v) in properties.items():
self._properties[p] = ModelFragment(p, v)
setattr(theClass, p, property(self._getter(p), self._setter(p)))
class ModelNode(ModelPropertySet):
def __init__(self):
super(ModelNode, self).__init__(ModelNode, {
'width': 200,
'height': 200
})
m = ModelNode()
l = QtGui.QLabel()
l.setStyleSheet("QLabel { background-color : rgba(0,0,255,100%); color : black; }")
l.setParent(w)
l.show()
l.setGeometry(QtCore.QRect(100,100,100,100))
m.width >>= l.width
m.height >>= l.height
m.width = 50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment