feat: Optimize properties panel layout to prevent horizontal scrolling

- Add smart path truncation for long file paths (preserves filename and parent folder)
- Set maximum width constraints on all UI components (QPushButton, QComboBox, QSpinBox, QDoubleSpinBox, QLineEdit)
- Add tooltips showing full paths for truncated file path buttons
- Disable horizontal scrollbar and optimize right panel width (320-380px)
- Improve styling for all property widgets with consistent theme
- Add better placeholder text for input fields

Key improvements:
- File paths like "C:/Very/Long/Path/.../filename.nef" → "...Long/Path/filename.nef"
- All widgets limited to 250px max width to prevent panel expansion
- Enhanced hover and focus states for better UX
- Properties panel now fits within fixed width without horizontal scroll

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
HuangMason320 2025-07-31 01:23:40 +08:00
parent 77bd8324ab
commit a1b6af0bde

View File

@ -361,10 +361,10 @@ class IntegratedPipelineDashboard(QMainWindow):
# Middle: Pipeline Editor (50% width) - without its own status bar # Middle: Pipeline Editor (50% width) - without its own status bar
middle_panel = self.create_pipeline_editor_panel() middle_panel = self.create_pipeline_editor_panel()
# Right side: Configuration panels (25% width) # Right side: Configuration panels (25% width) - optimized for no horizontal scroll
right_panel = self.create_configuration_panel() right_panel = self.create_configuration_panel()
right_panel.setMinimumWidth(300) right_panel.setMinimumWidth(320)
right_panel.setMaximumWidth(400) right_panel.setMaximumWidth(380)
# Add widgets to splitter # Add widgets to splitter
main_splitter.addWidget(left_panel) main_splitter.addWidget(left_panel)
@ -795,8 +795,15 @@ class IntegratedPipelineDashboard(QMainWindow):
def create_node_properties_panel(self) -> QWidget: def create_node_properties_panel(self) -> QWidget:
"""Create node properties editing panel.""" """Create node properties editing panel."""
widget = QScrollArea() widget = QScrollArea()
# Configure scroll area to prevent horizontal scrolling
widget.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
widget.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
widget.setWidgetResizable(True)
content = QWidget() content = QWidget()
layout = QVBoxLayout(content) layout = QVBoxLayout(content)
layout.setContentsMargins(10, 10, 10, 10) # Add some padding
# Header # Header
header = QLabel("Node Properties") header = QLabel("Node Properties")
@ -1271,6 +1278,41 @@ class IntegratedPipelineDashboard(QMainWindow):
return widget return widget
def truncate_path_smart(self, path: str, max_length: int = 35) -> str:
"""
Smart path truncation that preserves important parts.
Shows: ...drive/important_folder/filename.ext
"""
if not path or len(path) <= max_length:
return path
import os
# Split path into components
drive, path_without_drive = os.path.splitdrive(path)
path_parts = path_without_drive.replace('\\', '/').split('/')
if len(path_parts) <= 2:
# Very short path, just truncate from start
return '...' + path[-(max_length-3):]
filename = path_parts[-1] if path_parts[-1] else path_parts[-2]
# Always keep filename and one parent directory if possible
if len(filename) > max_length - 10:
# Filename itself is too long
return '...' + filename[-(max_length-3):]
# Try to keep parent folder + filename
parent_dir = path_parts[-2] if len(path_parts) >= 2 else ''
short_end = f"/{parent_dir}/{filename}" if parent_dir else f"/{filename}"
if len(short_end) <= max_length - 3:
return '...' + short_end
else:
# Just keep filename
return '.../' + filename
def create_property_widget_enhanced(self, node, prop_name: str, prop_value): def create_property_widget_enhanced(self, node, prop_name: str, prop_value):
"""Create enhanced property widget with better type detection.""" """Create enhanced property widget with better type detection."""
# Create widget based on property name and value # Create widget based on property name and value
@ -1284,9 +1326,35 @@ class IntegratedPipelineDashboard(QMainWindow):
# Check for file path properties first (from prop_options or name pattern) # Check for file path properties first (from prop_options or name pattern)
if (prop_options and isinstance(prop_options, dict) and prop_options.get('type') == 'file_path') or \ if (prop_options and isinstance(prop_options, dict) and prop_options.get('type') == 'file_path') or \
prop_name in ['model_path', 'source_path', 'destination']: prop_name in ['model_path', 'source_path', 'destination']:
# File path property with filters from prop_options or defaults # File path property with smart truncation and width limits
widget = QPushButton(str(prop_value) if prop_value else 'Select File...') display_text = self.truncate_path_smart(str(prop_value)) if prop_value else 'Select File...'
widget.setStyleSheet("text-align: left; padding: 5px;") widget = QPushButton(display_text)
# Set fixed width and styling to prevent expansion
widget.setMaximumWidth(250) # Limit button width
widget.setMinimumWidth(200)
widget.setStyleSheet("""
QPushButton {
text-align: left;
padding: 5px 8px;
background-color: #45475a;
color: #cdd6f4;
border: 1px solid #585b70;
border-radius: 4px;
font-size: 10px;
}
QPushButton:hover {
background-color: #585b70;
border-color: #74c7ec;
}
QPushButton:pressed {
background-color: #313244;
}
""")
# Store full path for tooltip and internal use
full_path = str(prop_value) if prop_value else ''
widget.setToolTip(f"Full path: {full_path}\n\nClick to browse for {prop_name.replace('_', ' ')}")
def browse_file(): def browse_file():
# Use filter from prop_options if available, otherwise use defaults # Use filter from prop_options if available, otherwise use defaults
@ -1295,7 +1363,9 @@ class IntegratedPipelineDashboard(QMainWindow):
else: else:
# Fallback to original filters # Fallback to original filters
filters = { filters = {
'model_path': 'Model files (*.onnx *.tflite *.pb)', 'model_path': 'NEF Model files (*.nef)',
'scpu_fw_path': 'SCPU Firmware files (*.bin)',
'ncpu_fw_path': 'NCPU Firmware files (*.bin)',
'source_path': 'Media files (*.mp4 *.avi *.mov *.mkv *.wav *.mp3)', 'source_path': 'Media files (*.mp4 *.avi *.mov *.mkv *.wav *.mp3)',
'destination': 'Output files (*.json *.xml *.csv *.txt)' 'destination': 'Output files (*.json *.xml *.csv *.txt)'
} }
@ -1303,7 +1373,12 @@ class IntegratedPipelineDashboard(QMainWindow):
file_path, _ = QFileDialog.getOpenFileName(self, f'Select {prop_name}', '', file_filter) file_path, _ = QFileDialog.getOpenFileName(self, f'Select {prop_name}', '', file_filter)
if file_path: if file_path:
widget.setText(file_path) # Update button text with truncated path
truncated_text = self.truncate_path_smart(file_path)
widget.setText(truncated_text)
# Update tooltip with full path
widget.setToolTip(f"Full path: {file_path}\n\nClick to browse for {prop_name.replace('_', ' ')}")
# Set property with full path
if hasattr(node, 'set_property'): if hasattr(node, 'set_property'):
node.set_property(prop_name, file_path) node.set_property(prop_name, file_path)
@ -1312,9 +1387,43 @@ class IntegratedPipelineDashboard(QMainWindow):
# Check for dropdown properties (list options from prop_options or predefined) # Check for dropdown properties (list options from prop_options or predefined)
elif (prop_options and isinstance(prop_options, list)) or \ elif (prop_options and isinstance(prop_options, list)) or \
prop_name in ['source_type', 'dongle_series', 'output_format', 'format', 'output_type', 'resolution']: prop_name in ['source_type', 'dongle_series', 'output_format', 'format', 'output_type', 'resolution']:
# Dropdown property # Dropdown property with width limits
widget = QComboBox() widget = QComboBox()
# Set maximum width to prevent expansion
widget.setMaximumWidth(250)
widget.setMinimumWidth(150)
widget.setStyleSheet("""
QComboBox {
padding: 4px 8px;
background-color: #45475a;
color: #cdd6f4;
border: 1px solid #585b70;
border-radius: 4px;
font-size: 11px;
}
QComboBox:hover {
border-color: #74c7ec;
}
QComboBox::drop-down {
border: none;
width: 20px;
}
QComboBox::down-arrow {
image: none;
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-top: 4px solid #cdd6f4;
margin-right: 4px;
}
QComboBox QAbstractItemView {
background-color: #313244;
color: #cdd6f4;
selection-background-color: #89b4fa;
border: 1px solid #585b70;
}
""")
# Use options from prop_options if available, otherwise use defaults # Use options from prop_options if available, otherwise use defaults
if prop_options and isinstance(prop_options, list): if prop_options and isinstance(prop_options, list):
items = prop_options items = prop_options
@ -1401,10 +1510,48 @@ class IntegratedPipelineDashboard(QMainWindow):
widget.stateChanged.connect(on_change) widget.stateChanged.connect(on_change)
elif isinstance(prop_value, int): elif isinstance(prop_value, int):
# Integer property # Integer property with width limits
widget = QSpinBox() widget = QSpinBox()
widget.setValue(prop_value) widget.setValue(prop_value)
# Set width limits to prevent expansion
widget.setMaximumWidth(120)
widget.setMinimumWidth(80)
widget.setStyleSheet("""
QSpinBox {
padding: 4px 6px;
background-color: #45475a;
color: #cdd6f4;
border: 1px solid #585b70;
border-radius: 4px;
font-size: 11px;
}
QSpinBox:hover {
border-color: #74c7ec;
}
QSpinBox:focus {
border-color: #89b4fa;
}
QSpinBox::up-button, QSpinBox::down-button {
width: 16px;
background-color: #585b70;
border: none;
}
QSpinBox::up-button:hover, QSpinBox::down-button:hover {
background-color: #6c7086;
}
QSpinBox::up-arrow {
border-left: 3px solid transparent;
border-right: 3px solid transparent;
border-bottom: 3px solid #cdd6f4;
}
QSpinBox::down-arrow {
border-left: 3px solid transparent;
border-right: 3px solid transparent;
border-top: 3px solid #cdd6f4;
}
""")
# Set range from prop_options if available, otherwise use defaults # Set range from prop_options if available, otherwise use defaults
if prop_options and isinstance(prop_options, dict) and 'min' in prop_options and 'max' in prop_options: if prop_options and isinstance(prop_options, dict) and 'min' in prop_options and 'max' in prop_options:
widget.setRange(prop_options['min'], prop_options['max']) widget.setRange(prop_options['min'], prop_options['max'])
@ -1429,11 +1576,49 @@ class IntegratedPipelineDashboard(QMainWindow):
widget.valueChanged.connect(on_change) widget.valueChanged.connect(on_change)
elif isinstance(prop_value, float): elif isinstance(prop_value, float):
# Float property # Float property with width limits
widget = QDoubleSpinBox() widget = QDoubleSpinBox()
widget.setValue(prop_value) widget.setValue(prop_value)
widget.setDecimals(2) widget.setDecimals(2)
# Set width limits to prevent expansion
widget.setMaximumWidth(120)
widget.setMinimumWidth(80)
widget.setStyleSheet("""
QDoubleSpinBox {
padding: 4px 6px;
background-color: #45475a;
color: #cdd6f4;
border: 1px solid #585b70;
border-radius: 4px;
font-size: 11px;
}
QDoubleSpinBox:hover {
border-color: #74c7ec;
}
QDoubleSpinBox:focus {
border-color: #89b4fa;
}
QDoubleSpinBox::up-button, QDoubleSpinBox::down-button {
width: 16px;
background-color: #585b70;
border: none;
}
QDoubleSpinBox::up-button:hover, QDoubleSpinBox::down-button:hover {
background-color: #6c7086;
}
QDoubleSpinBox::up-arrow {
border-left: 3px solid transparent;
border-right: 3px solid transparent;
border-bottom: 3px solid #cdd6f4;
}
QDoubleSpinBox::down-arrow {
border-left: 3px solid transparent;
border-right: 3px solid transparent;
border-top: 3px solid #cdd6f4;
}
""")
# Set range and step from prop_options if available, otherwise use defaults # Set range and step from prop_options if available, otherwise use defaults
if prop_options and isinstance(prop_options, dict): if prop_options and isinstance(prop_options, dict):
if 'min' in prop_options and 'max' in prop_options: if 'min' in prop_options and 'max' in prop_options:
@ -1462,15 +1647,40 @@ class IntegratedPipelineDashboard(QMainWindow):
widget.valueChanged.connect(on_change) widget.valueChanged.connect(on_change)
else: else:
# String property (default) # String property (default) with width limits
widget = QLineEdit() widget = QLineEdit()
widget.setText(str(prop_value)) widget.setText(str(prop_value))
# Set width limits to prevent expansion
widget.setMaximumWidth(250)
widget.setMinimumWidth(150)
widget.setStyleSheet("""
QLineEdit {
padding: 4px 8px;
background-color: #45475a;
color: #cdd6f4;
border: 1px solid #585b70;
border-radius: 4px;
font-size: 11px;
}
QLineEdit:hover {
border-color: #74c7ec;
}
QLineEdit:focus {
border-color: #89b4fa;
}
QLineEdit::placeholder {
color: #6c7086;
}
""")
# Set placeholders for specific properties # Set placeholders for specific properties
placeholders = { placeholders = {
'model_path': 'Path to model file (.nef, .onnx, etc.)', 'model_path': 'Path to model file',
'destination': 'Output file path', 'destination': 'Output file path',
'resolution': 'e.g., 1920x1080' 'resolution': 'e.g., 1920x1080',
'port_id': 'e.g., 6,7,8 or auto',
'operations': 'e.g., resize,normalize'
} }
if prop_name in placeholders: if prop_name in placeholders: