فهرست منبع

Added a custom layout for the prefs so that it adjusts to different screen sizes automatically. I may have gotten slightly carried away on this one........

David Robertson 5 سال پیش
والد
کامیت
500cc34639
2فایلهای تغییر یافته به همراه178 افزوده شده و 5 حذف شده
  1. 174 0
      flatcamGUI/ColumnarFlowLayout.py
  2. 4 5
      flatcamGUI/preferences/PreferencesSectionUI.py

+ 174 - 0
flatcamGUI/ColumnarFlowLayout.py

@@ -0,0 +1,174 @@
+import sys
+
+from PyQt5.QtCore import QPoint, QRect, QSize, Qt
+from PyQt5.QtWidgets import QLayout, QSizePolicy
+import math
+
+class ColumnarFlowLayout(QLayout):
+    def __init__(self, parent=None, margin=0, spacing=-1):
+        super().__init__(parent)
+
+        if parent is not None:
+            self.setContentsMargins(margin, margin, margin, margin)
+
+        self.setSpacing(spacing)
+        self.itemList = []
+
+    def __del__(self):
+        item = self.takeAt(0)
+        while item:
+            item = self.takeAt(0)
+
+    def addItem(self, item):
+        self.itemList.append(item)
+
+    def count(self):
+        return len(self.itemList)
+
+    def itemAt(self, index):
+        if 0 <= index < len(self.itemList):
+            return self.itemList[index]
+        return None
+
+    def takeAt(self, index):
+        if 0 <= index < len(self.itemList):
+            return self.itemList.pop(index)
+        return None
+
+    def expandingDirections(self):
+        return Qt.Orientations(Qt.Orientation(0))
+
+    def hasHeightForWidth(self):
+        return True
+
+    def heightForWidth(self, width):
+        height = self.doLayout(QRect(0, 0, width, 0), True)
+        return height
+
+    def setGeometry(self, rect):
+        super().setGeometry(rect)
+        self.doLayout(rect, False)
+
+    def sizeHint(self):
+        return self.minimumSize()
+
+    def minimumSize(self):
+        size = QSize()
+
+        for item in self.itemList:
+            size = size.expandedTo(item.minimumSize())
+
+        margin, _, _, _ = self.getContentsMargins()
+
+        size += QSize(2 * margin, 2 * margin)
+        return size
+
+    def doLayout(self, rect: QRect, testOnly: bool) -> int:
+        spacing = self.spacing()
+        x = rect.x()
+        y = rect.y()
+
+        # Determine width of widest item
+        widest = 0
+        for item in self.itemList:
+            widest = max(widest, item.sizeHint().width())
+
+        # Determine how many equal-width columns we can get, and how wide each one should be
+        column_count = math.floor(rect.width() / (widest + spacing))
+        column_count = min(column_count, len(self.itemList))
+        column_count = max(1, column_count)
+        column_width = math.floor((rect.width() - (column_count-1)*spacing - 1) / column_count)
+
+        # Get the heights for all of our items
+        item_heights = {}
+        for item in self.itemList:
+            height = item.heightForWidth(column_width) if item.hasHeightForWidth() else item.sizeHint().height()
+            item_heights[item] = height
+
+        # Prepare our column representation
+        column_contents = []
+        column_heights = []
+        for column_index in range(column_count):
+            column_contents.append([])
+            column_heights.append(0)
+
+        def add_to_column(column: int, item):
+            column_contents[column].append(item)
+            column_heights[column] += (item_heights[item] + spacing)
+
+        def shove_one(from_column: int) -> bool:
+            if len(column_contents[from_column]) >= 1:
+                item = column_contents[from_column].pop(0)
+                column_heights[from_column] -= (item_heights[item] + spacing)
+                add_to_column(from_column-1, item)
+                return True
+            return False
+
+        def shove_cascade_consider(from_column: int) -> bool:
+            changed = False
+
+            if len(column_contents[from_column]) > 1:
+                item = column_contents[from_column][0]
+                item_height = item_heights[item]
+                if column_heights[from_column-1] + item_height < max(column_heights):
+                    changed = shove_one(from_column) or changed
+
+            if from_column+1 < column_count:
+                changed = shove_cascade_consider(from_column+1) or changed
+
+            return changed
+
+        def shove_cascade() -> bool:
+            if column_count < 2:
+                return False
+            changed = True
+            while changed:
+                changed = shove_cascade_consider(1)
+            return changed
+
+        def pick_best_shoving_position() -> int:
+            best_pos = 1
+            best_height = sys.maxsize
+            for column_index in range(1, column_count):
+                if len(column_contents[column_index]) == 0:
+                    continue
+                item = column_contents[column_index][0]
+                height_after_shove = column_heights[column_index-1] + item_heights[item]
+                if height_after_shove < best_height:
+                    best_height = height_after_shove
+                    best_pos = column_index
+            return best_pos
+
+        # Calculate the best layout
+        column_index = 0
+        for item in self.itemList:
+            item_height = item_heights[item]
+            if column_heights[column_index] != 0 and (column_heights[column_index] + item_height) > max(column_heights):
+                column_index += 1
+                if column_index >= column_count:
+                    # Run out of room, need to shove more stuff in each column
+                    if column_count >= 2:
+                        changed = shove_cascade()
+                        if not changed:
+                            shoving_pos = pick_best_shoving_position()
+                            shove_one(shoving_pos)
+                            shove_cascade()
+                    column_index = column_count-1
+
+            add_to_column(column_index, item)
+
+        shove_cascade()
+
+        # Set geometry according to the layout we have calculated
+        if not testOnly:
+            for column_index, items in enumerate(column_contents):
+                x = column_index * (column_width + spacing)
+                y = 0
+                for item in items:
+                    height = item_heights[item]
+                    item.setGeometry(QRect(x, y, column_width, height))
+                    y += (height + spacing)
+
+        # Return the overall height
+        return max(column_heights)
+

+ 4 - 5
flatcamGUI/preferences/PreferencesSectionUI.py

@@ -1,7 +1,7 @@
 from typing import Dict
+from PyQt5 import QtWidgets, QtCore
 
-from PyQt5 import QtWidgets
-
+from flatcamGUI.ColumnarFlowLayout import ColumnarFlowLayout
 from flatcamGUI.preferences.OptionUI import OptionUI
 from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
 
@@ -10,8 +10,7 @@ class PreferencesSectionUI(QtWidgets.QWidget):
 
     def __init__(self, **kwargs):
         super().__init__(**kwargs)
-
-        self.layout = QtWidgets.QHBoxLayout()
+        self.layout = ColumnarFlowLayout() #QtWidgets.QHBoxLayout()
         self.setLayout(self.layout)
 
         self.groups = self.build_groups()
@@ -19,7 +18,6 @@ class PreferencesSectionUI(QtWidgets.QWidget):
             group.setMinimumWidth(250)
             self.layout.addWidget(group)
 
-        self.layout.addStretch()
 
     def build_groups(self) -> [OptionsGroupUI]:
         return []
@@ -34,6 +32,7 @@ class PreferencesSectionUI(QtWidgets.QWidget):
     def build_tab(self):
         scroll_area = QtWidgets.QScrollArea()
         scroll_area.setWidget(self)
+        scroll_area.setWidgetResizable(True)
         return scroll_area
 
     def get_tab_id(self) -> str: