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........

This commit is contained in:
David Robertson
2020-05-09 23:29:59 +01:00
parent 13296aea1d
commit 500cc34639
2 changed files with 178 additions and 5 deletions

View File

@@ -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)

View File

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