Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

* Added `Component` base class with standardized `widget` attribute and `update()` method.
* Added `BoundComponent` class for components bound to object attributes with automatic value synchronization.
* Added new components: `BooleanToggle`, `ColorPicker`, `NumberEdit`, `Container`, `Tabform`.

### Changed

* Complete restructuring of compas_viewer UI architecture to implement component-based system.
* Replaced dialogs with integrated components: `CameraSettingsDialog` → `CameraSetting`, `ObjectSettingDialog` → `ObjectSetting`.
* Enhanced existing components: Updated `Slider`, `TextEdit`, `Button` to use new component-based system.
* Moved UI elements to dedicated `components/` folder.
* Refactored `MenuBar`, `ToolBar`, `SideDock`, `MainWindow`, `StatusBar`, `ViewPort` to use new component system.
* Updated `UI` class to use new component architecture.
* All UI components now inherit from `Base` class for consistent structure.
* Improved data binding with automatic attribute synchronization.

### Removed

* Removed deprecated components: `ColorComboBox`, `ComboBox`, `DoubleEdit`, `LineEdit`, `LabelWidget`.
* Removed `CameraSettingsDialog` and `ObjectSettingDialog` (replaced with integrated components).


## [1.6.1] 2025-06-30

Expand Down
32 changes: 32 additions & 0 deletions UI_Component_Refactoring_Summary.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# UI Component System Refactoring

## Overview
Complete restructuring of the compas_viewer UI architecture to implement a modern component-based system with improved modularity and maintainability.

## Key Changes

### New Component Architecture
- Added `Component` base class with standardized `widget` attribute and `update()` method
- Added `BoundComponent` class for components bound to object attributes with automatic value synchronization
- All UI components now inherit from `Base` class for consistent structure

### Component Refactoring
- **Replaced dialogs with integrated components**: `CameraSettingsDialog` → `CameraSetting`, `ObjectSettingDialog` → `ObjectSetting`
- **Enhanced existing components**: Updated `Slider`, `TextEdit`, `Button` to use new inheritance model
- **Added new components**: `BooleanToggle`, `ColorPicker`, `NumberEdit`, `Container`, `Tabform`
- **Removed deprecated components**: `ColorComboBox`, `ComboBox`, `DoubleEdit`, `LineEdit`, `LabelWidget`

### UI Structure Improvements
- Moved components to dedicated `components/` folder
- Added `MainWindow`, `StatusBar`, `ViewPort` components
- Refactored `MenuBar`, `ToolBar`, `SideDock` to use new component system
- Updated `UI` class to use new component architecture

### Technical Improvements
- Standardized component initialization with `obj`, `attr`, `action` parameters
- Improved data binding with automatic attribute synchronization
- Enhanced container system with scrollable and splitter options
- Updated event handling and signal connections

## Impact
This refactoring provides a cleaner, more maintainable codebase with better separation of concerns and improved extensibility for future UI development.
7 changes: 7 additions & 0 deletions docs/api/compas_viewer.components.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,10 @@ Classes
Slider
Treeform
ViewModeAction
mainwindow.MainWindow
menubar.MenuBar
sidebar.SideBarRight
sidedock.SideDock
statusbar.StatusBar
toolbar.ToolBar
viewport.ViewPort
16 changes: 0 additions & 16 deletions docs/api/compas_viewer.ui.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,3 @@ Classes
:nosignatures:

UI


Other Classes
=============

.. autosummary::
:toctree: generated/
:nosignatures:

mainwindow.MainWindow
menubar.MenuBar
sidebar.SideBarRight
sidedock.SideDock
statusbar.StatusBar
toolbar.ToolBar
viewport.ViewPort
18 changes: 11 additions & 7 deletions scripts/sidedock.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,31 @@

boxobj = viewer.scene.add(box)

import time


def toggle_box():
boxobj.show = not boxobj.show
viewer.renderer.update()


def slider_changed(slider: Slider, value: int):
def slider_changed1(slider: Slider, value: float):
global viewer
global boxobj

vmin = slider.min_val
vmax = slider.max_val
boxobj.transformation = Translation.from_vector([5 * value, 0, 0])
boxobj.update()
viewer.renderer.update()

v = (value - vmin) / (vmax - vmin)
def slider_changed2(slider: Slider, value: float):
global boxobj

boxobj.transformation = Translation.from_vector([10 * v, 0, 0])
boxobj.update()
viewer.renderer.update()


viewer.ui.sidedock.show = True
viewer.ui.sidedock.add(Button(text="Toggle Box", action=toggle_box))
viewer.ui.sidedock.add(Slider(title="test", min_val=0, max_val=2, step=0.2, action=slider_changed))
viewer.ui.sidedock.add(Slider(title="Move Box", min_val=0, max_val=2, step=0.2, action=slider_changed1))
viewer.ui.sidedock.add(Slider(title="Box Opacity", obj=boxobj, attr="opacity", min_val=0, max_val=1, step=0.1, action=slider_changed2))

viewer.show()
15 changes: 15 additions & 0 deletions src/compas_viewer/base.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
class Base:
"""
Base class for all components in the viewer, provides a global access to the viewer and scene.

Attributes
----------
viewer : Viewer
The viewer instance.
scene : Scene
The scene instance.
"""

@property
def viewer(self):
from compas_viewer.viewer import Viewer

return Viewer()

@property
def scene(self):
return self.viewer.scene
31 changes: 11 additions & 20 deletions src/compas_viewer/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@
from compas.datastructures import Datastructure
from compas.geometry import Geometry
from compas.scene import Scene
from compas_viewer.components.camerasetting import CameraSettingsDialog
from compas_viewer.components.objectsetting import ObjectSettingDialog

if TYPE_CHECKING:
from compas_viewer import Viewer
Expand Down Expand Up @@ -116,8 +114,17 @@ def change_view(viewer: "Viewer", mode: Literal["Perspective", "Top", "Front", "


def camera_settings(viewer: "Viewer"):
items = viewer.config.camera.dialog_settings
CameraSettingsDialog(items=items).exec()
# Try to focus on the camera settings tab in the sidebar
if hasattr(viewer.ui, "sidebar") and hasattr(viewer.ui.sidebar, "tabform"):
tabform = viewer.ui.sidebar.tabform
if "Camera" in tabform.tabs:
tabform.set_current_tab("Camera")
if hasattr(viewer.ui.sidebar, "camera_setting"):
viewer.ui.sidebar.camera_setting.update()
else:
print("Camera settings tab not found in sidebar")
else:
print("Camera settings are available in the sidebar")


camera_settings_cmd = Command(title="Camera Settings", callback=camera_settings)
Expand Down Expand Up @@ -490,19 +497,3 @@ def load_data():


load_data_cmd = Command(title="Load Data", callback=lambda: print("load data"))


# =============================================================================
# =============================================================================
# =============================================================================
# Info
# =============================================================================
# =============================================================================
# =============================================================================


def obj_settings(viewer: "Viewer"):
ObjectSettingDialog().exec()


obj_settings_cmd = Command(title="Object Settings", callback=obj_settings)
16 changes: 8 additions & 8 deletions src/compas_viewer/components/__init__.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
from .button import Button
from .combobox import ComboBox
from .combobox import ViewModeAction
from .camerasetting import CameraSettingsDialog
from .objectsetting import ObjectSettingDialog
from .camerasetting import CameraSetting
from .slider import Slider
from .textedit import TextEdit
from .treeform import Treeform
from .sceneform import Sceneform
from .objectsetting import ObjectSetting
from .tabform import Tabform
from .component import Component

__all__ = [
"Button",
"ComboBox",
"CameraSettingsDialog",
"ObjectSettingDialog",
"CameraSetting",
"Renderer",
"Slider",
"TextEdit",
"Treeform",
"Sceneform",
"ViewModeAction",
"ObjectSetting",
"Tabform",
"Component",
]
92 changes: 92 additions & 0 deletions src/compas_viewer/components/booleantoggle.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
from typing import Callable
from typing import Union

from PySide6.QtWidgets import QCheckBox
from PySide6.QtWidgets import QHBoxLayout
from PySide6.QtWidgets import QLabel
from PySide6.QtWidgets import QWidget

from .boundcomponent import BoundComponent
from .component import Component


class BooleanToggle(BoundComponent):
"""
This component creates a labeled checkbox that can be bound to an object's boolean attribute
(either a dictionary key or object attribute). When the checkbox state changes, it automatically
updates the bound attribute and optionally calls a action function.

Parameters
----------
obj : Union[object, dict]
The object or dictionary containing the boolean attribute to be edited.
attr : str
The name of the attribute/key to be edited.
title : str, optional
The label text to be displayed next to the checkbox. If None, uses the attr name.
action : Callable[[Component, bool], None], optional
A function to call when the checkbox state changes. Receives the component and new boolean value.

Attributes
----------
obj : Union[object, dict]
The object or dictionary containing the boolean attribute being edited.
attr : str
The name of the attribute/key being edited.
action : Callable[[Component, bool], None] or None
The action function to call when the checkbox state changes.
widget : QWidget
The main widget containing the layout.
layout : QHBoxLayout
The horizontal layout containing the label and the checkbox.
label : QLabel
The label displaying the title.
checkbox : QCheckBox
The checkbox widget for toggling the boolean value.

Example
-------
>>> class MyObject:
... def __init__(self):
... self.show_points = True
>>> obj = MyObject()
>>> component = BooleanToggle(obj, "show_points", title="Show Points")
"""

def __init__(
self,
obj: Union[object, dict],
attr: str,
title: str = None,
action: Callable[[Component, bool], None] = None,
):
super().__init__(obj, attr, action=action)

self.widget = QWidget()
self.layout = QHBoxLayout()

title = title if title is not None else attr
self.label = QLabel(title)
self.checkbox = QCheckBox()
self.checkbox.setMaximumSize(85, 25)

# Set the initial state from the bound attribute
initial_value = self.get_attr()
if not isinstance(initial_value, bool):
raise ValueError(f"Attribute '{attr}' must be a boolean value, got {type(initial_value)}")
self.checkbox.setChecked(initial_value)

self.layout.addWidget(self.label)
self.layout.addWidget(self.checkbox)
self.widget.setLayout(self.layout)

# Connect the checkbox state change signal to the action
self.checkbox.stateChanged.connect(self.on_state_changed)

def on_state_changed(self, state):
"""Handle checkbox state change events by updating the bound attribute and calling the action."""
# Convert Qt checkbox state to boolean
is_checked = state == 2 # Qt.Checked = 2
self.set_attr(is_checked)
if self.action:
self.action(self, is_checked)
Loading
Loading