diff engine/extensions/pychan/widgets/layout.py @ 249:1cc51d145af9

Further split up the containers.py; bugfix.
author phoku@33b003aa-7bff-0310-803a-e67f0ece8222
date Thu, 26 Mar 2009 16:36:21 +0000
parents
children 51cc05d862f2
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/engine/extensions/pychan/widgets/layout.py	Thu Mar 26 16:36:21 2009 +0000
@@ -0,0 +1,162 @@
+# -*- coding: utf-8 -*-
+
+from common import *
+
+class LayoutBase(object):
+	"""
+	This class is at the core of the layout engine. The two MixIn classes L{VBoxLayoutMixin}
+	and L{HBoxLayoutMixin} specialise on this by reimplementing the C{resizeToContent} and
+	the C{expandContent} methods.
+
+	Dynamic Layouting
+	-----------------
+
+	The layout is calculated in the L{Widget.show} method. Thus if you modify the layout,
+	by adding or removing child widgets for example, you have to call L{Widget.adaptLayout}
+	so that the changes ripple through the widget hierachy.
+
+	Internals
+	---------
+
+	At the core the layout engine works in two passes:
+
+	Before a root widget loaded by the XML code is shown, its resizeToContent method
+	is called recursively (walking the widget containment relation in post order).
+	This shrinks all HBoxes and VBoxes to their minimum heigt and width.
+	After that the expandContent method is called recursively in the same order,
+	which will re-align the widgets if there is space left AND if a Spacer is contained.
+
+	Inside bare Container instances (without a Layout MixIn) absolute positioning
+	can be used.
+	"""
+	def __init__(self,align = (AlignLeft,AlignTop), **kwargs):
+		self.align = align
+		self.spacer = None
+		super(LayoutBase,self).__init__(**kwargs)
+
+	def addSpacer(self,spacer):
+		if self.spacer:
+			raise RuntimeException("Already a Spacer in %s!" % str(self))
+		self.spacer = spacer
+		spacer.index = len(self.children)
+
+	def xdelta(self,widget):return 0
+	def ydelta(self,widget):return 0
+
+	def _adjustHeight(self):
+		if self.align[1] == AlignTop:return #dy = 0
+		if self.align[1] == AlignBottom:
+			y = self.height - self.childarea[1] - self.border_size - self.margins[1]
+		else:
+			y = (self.height - self.childarea[1] - self.border_size - self.margins[1])/2
+		for widget in self.children:
+			widget.y = y
+			y += self.ydelta(widget)
+
+	def _adjustHeightWithSpacer(self):
+		pass
+
+	def _adjustWidth(self):
+		if self.align[0] == AlignLeft:return #dx = 0
+		if self.align[0] == AlignRight:
+			x = self.width - self.childarea[0] - self.border_size - self.margins[0]
+		else:
+			x = (self.width - self.childarea[0] - self.border_size - self.margins[0])/2
+		for widget in self.children:
+			widget.x = x
+			x += self.xdelta(widget)
+
+	def _expandWidthSpacer(self):
+		x = self.border_size + self.margins[0]
+		xdelta = map(self.xdelta,self.children)
+
+		for widget in self.children[:self.spacer.index]:
+			widget.x = x
+			x += xdelta.pop(0)
+
+		x = self.width - sum(xdelta) - self.border_size - self.margins[0]
+		for widget in self.children[self.spacer.index:]:
+			widget.x = x
+			x += xdelta.pop(0)
+
+	def _expandHeightSpacer(self):
+		y = self.border_size + self.margins[1]
+		ydelta = map(self.ydelta,self.children)
+
+		for widget in self.children[:self.spacer.index]:
+			widget.y = y
+			y += ydelta.pop(0)
+
+		y = self.height - sum(ydelta) - self.border_size - self.margins[1]
+		for widget in self.children[self.spacer.index:]:
+			widget.y = y
+			y += ydelta.pop(0)
+
+
+class VBoxLayoutMixin(LayoutBase):
+	"""
+	A mixin class for a vertical layout. Do not use directly.
+	"""
+	def __init__(self,**kwargs):
+		super(VBoxLayoutMixin,self).__init__(**kwargs)
+
+	def resizeToContent(self, recurse = True):
+		max_w = self.getMaxChildrenWidth()
+		x = self.margins[0] + self.border_size
+		y = self.margins[1] + self.border_size
+		for widget in self.children:
+			widget.x = x
+			widget.y = y
+			widget.width = max_w
+			y += widget.height + self.padding
+
+		#Add the padding for the spacer.
+		if self.spacer:
+			y += self.padding
+
+		self.height = y + self.margins[1] - self.padding
+		self.width = max_w + 2*x
+		self.childarea = max_w, y - self.padding - self.margins[1]
+
+		self._adjustHeight()
+		self._adjustWidth()
+
+	def expandContent(self):
+		if self.spacer:
+			self._expandHeightSpacer()
+
+	def ydelta(self,widget):return widget.height + self.padding
+
+class HBoxLayoutMixin(LayoutBase):
+	"""
+	A mixin class for a horizontal layout. Do not use directly.
+	"""
+	def __init__(self,**kwargs):
+		super(HBoxLayoutMixin,self).__init__(**kwargs)
+
+	def resizeToContent(self, recurse = True):
+		max_h = self.getMaxChildrenHeight()
+		x = self.margins[0] + self.border_size
+		y = self.margins[1] + self.border_size
+		for widget in self.children:
+			widget.x = x
+			widget.y = y
+			widget.height = max_h
+			x += widget.width + self.padding
+
+		#Add the padding for the spacer.
+		if self.spacer:
+			x += self.padding
+
+		self.width = x + self.margins[0] - self.padding
+		self.height = max_h + 2*y
+		self.childarea = x - self.margins[0] - self.padding, max_h
+
+		self._adjustHeight()
+		self._adjustWidth()
+
+	def expandContent(self):
+		if self.spacer:
+			self._expandWidthSpacer()
+
+	def xdelta(self,widget):return widget.width + self.padding