Skip to content

Commit

Permalink
Merge pull request #132 from nucleic/layout-fixes
Browse files Browse the repository at this point in the history
Layout fixes
  • Loading branch information
sccolbert committed Feb 9, 2014
2 parents b57aa2a + 2dd4fa2 commit 5a9f529
Show file tree
Hide file tree
Showing 62 changed files with 570 additions and 343 deletions.
2 changes: 2 additions & 0 deletions enaml/applib/live_editor_view.enaml
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ enamldef ViewPanel(Container):
attr model: LiveEditorModel
padding = 0
MdiArea:
# MdiArea generates a huge size hint. Ignore it in favor
# of something more reasonable.
resist_width = 'ignore'
resist_height = 'ignore'
constraints = [width >= 640, height >= 480]
Expand Down
120 changes: 75 additions & 45 deletions enaml/layout/layout_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ class LayoutItem(Atom):
implement the necessary toolkit specific layout functionality.
"""
#: The list of cached size hint constraints. This is used for
#: storage by the layout manager.
_size_hint_cache = List()
#: The list of cached geometry constraints. This is used for storage
#: by the layout manager.
_geometry_cache = List()

#: The list of cached margin constraints. This is used for storage
#: by the layout manager.
Expand Down Expand Up @@ -75,32 +75,48 @@ def margin_constraints(self):
c_l = d.contents_left == (d.left + left)
return [c_t, c_r, c_b, c_l]

def size_hint_constraints(self):
""" Generate a list of size hint constraints for the item.
def geometry_constraints(self):
""" Generate a list of geometry constraints for the item.
Returns
-------
result : list
A list of size hint constraints for the item.
A list of geometry constraints for the item.
"""
cns = []
d = self.constrainable()
width, height = self.size_hint()
if width >= 0:

width_hint, height_hint = self.size_hint()
if width_hint >= 0:
if d.hug_width != 'ignore':
cns.append((d.width == width) | d.hug_width)
cns.append((d.width == width_hint) | d.hug_width)
if d.resist_width != 'ignore':
cns.append((d.width >= width) | d.resist_width)
cns.append((d.width >= width_hint) | d.resist_width)
if d.limit_width != 'ignore':
cns.append((d.width <= width) | d.limit_width)
if height >= 0:
cns.append((d.width <= width_hint) | d.limit_width)
if height_hint >= 0:
if d.hug_height != 'ignore':
cns.append((d.height == height) | d.hug_height)
cns.append((d.height == height_hint) | d.hug_height)
if d.resist_height != 'ignore':
cns.append((d.height >= height) | d.resist_height)
cns.append((d.height >= height_hint) | d.resist_height)
if d.limit_height != 'ignore':
cns.append((d.height <= height) | d.limit_height)
cns.append((d.height <= height_hint) | d.limit_height)

strength = 0.1 * kiwi.strength.strong

min_width, min_height = self.min_size()
if min_width >= 0:
cns.append((d.width >= min_width) | strength)
if min_height >= 0:
cns.append((d.height >= min_height) | strength)

max_width, max_height = self.max_size()
if max_width >= 0:
cns.append((d.width <= max_width) | strength)
if max_height >= 0:
cns.append((d.height <= max_height) | strength)

return cns

def layout_constraints(self):
Expand Down Expand Up @@ -172,6 +188,36 @@ def size_hint(self):
"""
raise NotImplementedError

def min_size(self):
""" Get the minimum size for the underlying widget.
This abstract method must be implemented by subclasses.
Returns
-------
result : tuple
A 2-tuple of numbers representing the (width, height)
min size of the widget. If any value is less than zero,
constraints will not be generated for that dimension.
"""
raise NotImplementedError

def max_size(self):
""" Get the maximum size for the underlying widget.
This abstract method must be implemented by subclasses.
Returns
-------
result : tuple
A 2-tuple of numbers representing the (width, height)
max size of the widget. If any value is less than zero,
constraints will not be generated for that dimension.
"""
raise NotImplementedError

def set_geometry(self, x, y, width, height):
""" Set the geometry of the underlying widget.
Expand Down Expand Up @@ -256,13 +302,10 @@ def set_items(self, items):
pairs = ((d.width, strength), (d.height, strength))
self._push_edit_vars(pairs)

# If there are no layout items, bail early.
if not items:
return

# Generate the constraints for the layout system. The size hint
# of the root item is ignored since the input to the solver is
# the suggested size of the root.
# and bounds of the root item are ignored since the input to the
# solver is the suggested size of the root item and the output
# of the solver is used to compute the bounds of the item.
cns = []
hc = root.hard_constraints()
mc = root.margin_constraints()
Expand All @@ -273,13 +316,13 @@ def set_items(self, items):
cns.extend(lc)
for child in items:
hc = child.hard_constraints()
sc = child.size_hint_constraints()
gc = child.geometry_constraints()
mc = child.margin_constraints()
lc = child.layout_constraints()
child._size_hint_cache = sc
child._geometry_cache = gc
child._margin_cache = mc
cns.extend(hc)
cns.extend(sc)
cns.extend(gc)
cns.extend(mc)
cns.extend(lc)

Expand Down Expand Up @@ -353,12 +396,6 @@ def min_size(self):
"""
d = self._root_item.constrainable()
shrink = ('ignore', 'weak')
if (d.resist_width in shrink and
d.resist_height in shrink and
d.hug_width in shrink and
d.hug_height in shrink):
return (0.0, 0.0)
width = d.width
height = d.height
solver = self._solver
Expand All @@ -380,26 +417,19 @@ def max_size(self):
The 2-tuple of (width, height) max size values.
"""
max_v = 16777215.0 # max allowed by Qt
d = self._root_item.constrainable()
expand = ('ignore', 'weak')
if (d.hug_width in expand and
d.hug_height in expand and
d.limit_width in expand and
d.limit_height in expand):
return (max_v, max_v)
width = d.width
height = d.height
solver = self._solver
solver.suggestValue(width, max_v)
solver.suggestValue(height, max_v)
solver.suggestValue(width, 16777215.0) # max allowed by Qt
solver.suggestValue(height, 16777215.0)
solver.updateVariables()
return (width.value(), height.value())

def update_size_hint(self, index):
""" Update the size hint for the given layout item.
def update_geometry(self, index):
""" Update the geometry for the given layout item.
The solver will be updated to reflect the item's new size hint.
The solver will be updated to reflect the item's new geometry.
This may change the computed min/max/best size of the system.
Parameters
Expand All @@ -410,9 +440,9 @@ def update_size_hint(self, index):
"""
item = self._layout_items[index]
old = item._size_hint_cache
new = item.size_hint_constraints()
item._size_hint_cache = new
old = item._geometry_cache
new = item.geometry_constraints()
item._geometry_cache = new
self._replace(old, new)

def update_margins(self, index):
Expand Down
12 changes: 11 additions & 1 deletion enaml/qt/docking/q_dock_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,8 @@ def maximumSize(self):
ms = self._max_size
if not ms.isValid():
widget = self._dock_widget
if widget is not None:
parent = self.parentWidget()
if widget is not None and parent.isFloating():
ms = widget.maximumSize()
title = self._title_bar
if title is not None and not title.isHidden():
Expand Down Expand Up @@ -518,6 +519,15 @@ def setPinned(self, pinned, quiet=False):
"""
self.titleBarWidget().setPinned(pinned, quiet)

def isFloating(self):
""" Get whether the dock item is free floating.
"""
container = self.parent()
if container is not None:
return container.isWindow()
return self.isWindow()

def titleEditable(self):
""" Get whether the title is user editable.
Expand Down
24 changes: 12 additions & 12 deletions enaml/qt/qt_abstract_button.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,11 @@ def init_widget(self):
super(QtAbstractButton, self).init_widget()
d = self.declaration
if d.text:
self.set_text(d.text, sh_guard=False)
self.set_text(d.text, guard=False)
if d.icon:
self.set_icon(d.icon, sh_guard=False)
self.set_icon(d.icon, guard=False)
if -1 not in d.icon_size:
self.set_icon_size(d.icon_size, sh_guard=False)
self.set_icon_size(d.icon_size, guard=False)
self.set_checkable(d.checkable)
self.set_checked(d.checked)
widget = self.widget
Expand Down Expand Up @@ -86,36 +86,36 @@ def on_toggled(self, checked):
#--------------------------------------------------------------------------
# ProxyAbstractButton API
#--------------------------------------------------------------------------
def set_text(self, text, sh_guard=True):
def set_text(self, text, guard=True):
""" Sets the widget's text with the provided value.
"""
if sh_guard:
with self.size_hint_guard():
if guard:
with self.geometry_guard():
self.widget.setText(text)
else:
self.widget.setText(text)

def set_icon(self, icon, sh_guard=True):
def set_icon(self, icon, guard=True):
""" Set the icon on the widget.
"""
if icon:
qicon = get_cached_qicon(icon)
else:
qicon = QIcon()
if sh_guard:
with self.size_hint_guard():
if guard:
with self.geometry_guard():
self.widget.setIcon(qicon)
else:
self.widget.setIcon(qicon)

def set_icon_size(self, size, sh_guard=True):
def set_icon_size(self, size, guard=True):
""" Sets the widget's icon size.
"""
if sh_guard:
with self.size_hint_guard():
if guard:
with self.geometry_guard():
self.widget.setIconSize(QSize(*size))
else:
self.widget.setIconSize(QSize(*size))
Expand Down
32 changes: 18 additions & 14 deletions enaml/qt/qt_constraints_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

# keep around for backwards compatibility
def size_hint_guard(obj):
return obj.size_hint_guard()
return obj.geometry_guard()


def QtContainer():
Expand Down Expand Up @@ -61,35 +61,39 @@ def request_relayout(self):
def restyle(self):
""" Restyle the widget with the current style data.
This reimplementation restyles from within a size hint guard.
This reimplementation restyles from within a geometry guard.
"""
with self.size_hint_guard():
with self.geometry_guard():
super(QtConstraintsWidget, self).restyle()

#--------------------------------------------------------------------------
# Layout API
#--------------------------------------------------------------------------
def size_hint_updated(self):
""" Notify the layout system that the size hint has changed.
def geometry_updated(self):
""" Notify the layout system that the geometry has changed.
This method forwards the update to the layout container.
"""
container = self.layout_container
if container is not None:
container.size_hint_updated(self)
container.geometry_updated(self)

@contextmanager
def size_hint_guard(self):
""" A context manager for guarding the size hint of the widget.
def geometry_guard(self):
""" A context manager for guarding the geometry of the widget.
This manager will call 'size_hint_updated' if the size hint of
the widget changes during context execution.
This manager will call 'geometry_updated' if the size hint,
minimum, or maximum size of the widget has changed.
"""
old_hint = self.widget.sizeHint()
widget = self.widget
old_hint = widget.sizeHint()
old_min = widget.minimumSize()
old_max = widget.maximumSize()
yield
new_hint = self.widget.sizeHint()
if old_hint != new_hint:
self.size_hint_updated()
if (old_hint != widget.sizeHint() or
old_min != widget.minimumSize() or
old_max != widget.maximumSize()):
self.geometry_updated()
Loading

0 comments on commit 5a9f529

Please sign in to comment.