@@ -41,42 +41,136 @@ def __init__(self, motor_id, motor_name="Motor", default_angle=0, parent=None):
4141 self .angle = default_angle # Angle in degrees
4242 self .position = int (default_angle * POSITIONS_PER_DEGREE ) # Position in Dynamixel units
4343 self .radius = 100
44- self .circle_center = QPoint (self .radius + 20 , self .radius + 20 )
44+ self .circle_center = QPoint (self .radius + 10 , self .radius + 10 ) # Adjusted for new layout
4545 self .dragging = False
4646 self .torque_enabled = False
4747 self .motor_connected = False # Track motor connection status
48- self .setMinimumSize (2 * (self .radius + 40 ), 2 * (self .radius + 60 ))
48+ self .setMinimumSize (2 * (self .radius + 60 ), 2 * (self .radius + 120 )) # Larger for card design
49+
50+ # Card styling with shadow effect
51+ self .setStyleSheet ("""
52+ MotorControlWidget {
53+ background-color: #ffffff;
54+ border-radius: 15px;
55+ border: 1px solid #e1e8ed;
56+ margin: 10px;
57+ }
58+ MotorControlWidget:hover {
59+ border: 1px solid #3498db;
60+ box-shadow: 0 4px 12px rgba(52, 152, 219, 0.1);
61+ }
62+ """ )
4963
50- # Create layout for the control buttons
64+ # Create layout for the card content
5165 self .main_layout = QVBoxLayout (self )
52- self .main_layout .setContentsMargins (0 , 0 , 0 , 0 )
66+ self .main_layout .setContentsMargins (20 , 20 , 20 , 20 ) # Card padding
67+ self .main_layout .setSpacing (15 ) # Space between card sections
68+
69+ # Card header with motor name and ID
70+ header_layout = QHBoxLayout ()
71+
72+ # Motor name
73+ self .name_label = QLabel (self .motor_name )
74+ self .name_label .setFont (QFont ('Segoe UI' , 16 , QFont .Bold ))
75+ self .name_label .setStyleSheet ("color: #2c3e50; margin-bottom: 5px;" )
76+ header_layout .addWidget (self .name_label )
77+
78+ # Motor ID badge
79+ self .id_badge = QLabel (f"ID: { self .motor_id } " )
80+ self .id_badge .setFont (QFont ('Segoe UI' , 10 , QFont .Medium ))
81+ self .id_badge .setStyleSheet ("""
82+ QLabel {
83+ background-color: #3498db;
84+ color: white;
85+ border-radius: 10px;
86+ padding: 4px 8px;
87+ max-width: 50px;
88+ }
89+ """ )
90+ self .id_badge .setAlignment (Qt .AlignCenter )
91+ header_layout .addWidget (self .id_badge )
92+
93+ self .main_layout .addLayout (header_layout )
5394
5495 # Widget for drawing the circle
5596 self .circle_widget = QWidget ()
56- self .circle_widget .setMinimumSize (2 * (self .radius + 40 ), 2 * (self .radius + 40 ))
97+ self .circle_widget .setMinimumSize (2 * (self .radius + 20 ), 2 * (self .radius + 20 )) # Smaller since we have header
5798 self .circle_widget .paintEvent = self .paintCircleWidget
5899 self .circle_widget .mousePressEvent = self .circleMousePressEvent
59100 self .circle_widget .mouseMoveEvent = self .circleMouseMoveEvent
60101 self .circle_widget .mouseReleaseEvent = self .circleMouseReleaseEvent
61102 self .main_layout .addWidget (self .circle_widget )
62103
63- # Controls
64- controls_layout = QHBoxLayout ()
104+ # Controls section with better styling
105+ controls_container = QWidget ()
106+ controls_container .setStyleSheet ("""
107+ QWidget {
108+ background-color: #f8f9fa;
109+ border-radius: 8px;
110+ padding: 10px;
111+ margin-top: 10px;
112+ }
113+ """ )
65114
66- # Torque toggle
115+ controls_layout = QVBoxLayout (controls_container ) # Changed to vertical for better card layout
116+ controls_layout .setSpacing (10 )
117+
118+ # Torque toggle with modern styling
67119 self .torque_checkbox = QCheckBox ("Enable Torque" )
68120 self .torque_checkbox .setChecked (self .torque_enabled )
69121 self .torque_checkbox .setEnabled (False ) # Disabled until motor connects
122+ self .torque_checkbox .setFont (QFont ('Segoe UI' , 11 ))
123+ self .torque_checkbox .setStyleSheet ("""
124+ QCheckBox {
125+ color: #2c3e50;
126+ spacing: 8px;
127+ }
128+ QCheckBox::indicator {
129+ width: 18px;
130+ height: 18px;
131+ border-radius: 9px;
132+ border: 2px solid #bdc3c7;
133+ }
134+ QCheckBox::indicator:checked {
135+ background-color: #27ae60;
136+ border: 2px solid #27ae60;
137+ }
138+ QCheckBox::indicator:disabled {
139+ background-color: #ecf0f1;
140+ border: 2px solid #bdc3c7;
141+ }
142+ """ )
70143 self .torque_checkbox .stateChanged .connect (self .toggleTorque )
71144 controls_layout .addWidget (self .torque_checkbox )
72145
73- # Reset position button
74- self .reset_button = QPushButton ("Reset Position" )
146+ # Reset position button with modern styling
147+ self .reset_button = QPushButton ("↻ Reset Position" )
75148 self .reset_button .setEnabled (False ) # Disabled until motor connects
149+ self .reset_button .setFont (QFont ('Segoe UI' , 11 ))
150+ self .reset_button .setStyleSheet ("""
151+ QPushButton {
152+ background-color: #3498db;
153+ color: white;
154+ border: none;
155+ border-radius: 6px;
156+ padding: 8px 16px;
157+ font-weight: 500;
158+ }
159+ QPushButton:hover {
160+ background-color: #2980b9;
161+ }
162+ QPushButton:pressed {
163+ background-color: #21618c;
164+ }
165+ QPushButton:disabled {
166+ background-color: #bdc3c7;
167+ color: #7f8c8d;
168+ }
169+ """ )
76170 self .reset_button .clicked .connect (self .resetPosition )
77171 controls_layout .addWidget (self .reset_button )
78172
79- self .main_layout .addLayout ( controls_layout )
173+ self .main_layout .addWidget ( controls_container )
80174
81175 # Conversion helpers
82176 self .rosnode = None # Will be set by parent
@@ -98,20 +192,31 @@ def paintCircleWidget(self, event):
98192 else :
99193 painter .setPen (QPen (Qt .black , 2 ))
100194 painter .setBrush (QBrush (QColor (240 , 240 , 240 ))) # Light gray when disabled
101- circle_rect = QRect (20 , 20 , 2 * self .radius , 2 * self .radius )
195+ circle_rect = QRect (10 , 10 , 2 * self .radius , 2 * self .radius ) # Adjusted for new layout
102196 painter .drawEllipse (circle_rect )
103197
104- # Draw motor name
105- painter .setFont (QFont ("Arial" , 12 , QFont .Bold ))
106- name_rect = QRect (20 , 2 * self .radius + 30 , 2 * self .radius , 30 )
107- painter .drawText (name_rect , Qt .AlignCenter , self .motor_name )
108-
109- # Draw angle text with connection status
198+ # Draw angle text with connection status (centered below circle)
199+ painter .setFont (QFont ("Segoe UI" , 12 , QFont .Medium ))
200+ if self .motor_connected :
201+ angle_text = f"{ self .angle :.1f} °"
202+ pos_text = f"Position: { self .position } "
203+ else :
204+ angle_text = f"{ self .angle :.1f} °"
205+ pos_text = "DISCONNECTED"
206+
207+ # Angle text
208+ angle_rect = QRect (10 , 2 * self .radius + 20 , 2 * self .radius , 25 )
209+ painter .setPen (QPen (Qt .black , 1 ))
210+ painter .drawText (angle_rect , Qt .AlignCenter , angle_text )
211+
212+ # Position/status text
213+ pos_rect = QRect (10 , 2 * self .radius + 40 , 2 * self .radius , 25 )
110214 if self .motor_connected :
111- angle_text = f" { self . angle :.1f } ° (Pos: { self . position } )"
215+ painter . setPen ( QPen ( QColor ( "#7f8c8d" ), 1 ))
112216 else :
113- angle_text = f"{ self .angle :.1f} ° (DISCONNECTED)"
114- painter .drawText (name_rect .translated (0 , 25 ), Qt .AlignCenter , angle_text )
217+ painter .setPen (QPen (QColor ("#e74c3c" ), 1 ))
218+ painter .setFont (QFont ("Segoe UI" , 10 ))
219+ painter .drawText (pos_rect , Qt .AlignCenter , pos_text )
115220
116221 # Draw line from center to edge (like a clock hand)
117222 if self .torque_enabled :
@@ -221,53 +326,77 @@ def __init__(self, node):
221326
222327 def initUI (self ):
223328 self .setWindowTitle ('Dynamixel Motor Control' )
224- self .setGeometry (100 , 100 , 600 , 500 )
329+ self .setGeometry (100 , 100 , 800 , 650 ) # Larger window for card layout
225330
226- # Main widget and layout
331+ # Main widget and layout with better spacing
227332 main_widget = QWidget ()
333+ main_widget .setStyleSheet ("""
334+ QWidget {
335+ background-color: #f8f9fa;
336+ font-family: 'Segoe UI', 'Arial', sans-serif;
337+ }
338+ """ )
228339 self .setCentralWidget (main_widget )
229340 main_layout = QVBoxLayout (main_widget )
341+ main_layout .setSpacing (20 ) # Better spacing between sections
342+ main_layout .setContentsMargins (30 , 20 , 30 , 20 ) # Consistent margins
230343
231- # Title label
344+ # Title label with modern styling
232345 title_label = QLabel ('Dynamixel Motor Control' )
233346 title_label .setAlignment (Qt .AlignCenter )
234- title_label .setFont (QFont ('Arial' , 16 , QFont .Bold ))
347+ title_label .setFont (QFont ('Segoe UI' , 24 , QFont .Bold ))
348+ title_label .setStyleSheet ("""
349+ QLabel {
350+ color: #2c3e50;
351+ padding: 15px;
352+ margin-bottom: 10px;
353+ }
354+ """ )
235355 main_layout .addWidget (title_label )
236356
237- # Service status indicator
357+ # Service status indicator with card styling
238358 self .status_label = QLabel ('Service Status: Checking connection...' )
239359 self .status_label .setAlignment (Qt .AlignCenter )
240- self .status_label .setFont (QFont ('Arial ' , 11 ))
360+ self .status_label .setFont (QFont ('Segoe UI ' , 12 , QFont . Medium ))
241361 self .update_service_status (False ) # Initialize as disconnected
242362 main_layout .addWidget (self .status_label )
243363
244- # Motor controls layout
245- motors_layout = QHBoxLayout ()
364+ # Motor controls in card layout
365+ motors_container = QWidget ()
366+ motors_container .setStyleSheet ("""
367+ QWidget {
368+ background-color: transparent;
369+ }
370+ """ )
371+ motors_layout = QHBoxLayout (motors_container )
372+ motors_layout .setSpacing (30 ) # Space between motor cards
373+ motors_layout .setContentsMargins (0 , 0 , 0 , 0 )
246374
247- # Pan motor control (default angle: 90 degrees)
375+ # Pan motor control card
248376 self .pan_motor = MotorControlWidget (DXL_PAN_ID , "Pan Motor" , DEFAULT_PAN_ANGLE )
249377 self .pan_motor .set_ros_node (self .node )
250378 motors_layout .addWidget (self .pan_motor )
251379
252- # Tilt motor control (default angle: 180 degrees)
380+ # Tilt motor control card
253381 self .tilt_motor = MotorControlWidget (DXL_TILT_ID , "Tilt Motor" , DEFAULT_TILT_ANGLE )
254382 self .tilt_motor .set_ros_node (self .node )
255383 motors_layout .addWidget (self .tilt_motor )
256384
257- main_layout .addLayout ( motors_layout )
385+ main_layout .addWidget ( motors_container )
258386
259- # Help instructions
260- help_label = QLabel ('Instructions: To control motors, run "ros2 run dynamixel_sdk_examples read_write_node" in another terminal' )
387+ # Help instructions with modern styling
388+ help_label = QLabel ('💡 To control motors, run "ros2 run dynamixel_sdk_examples read_write_node" in another terminal' )
261389 help_label .setAlignment (Qt .AlignCenter )
262- help_label .setFont (QFont ('Arial ' , 9 ))
390+ help_label .setFont (QFont ('Segoe UI ' , 11 ))
263391 help_label .setStyleSheet ("""
264392 QLabel {
265- color: #666666;
266- background-color: #f5f5f5;
267- border: 1px solid #cccccc;
268- border-radius: 3px;
269- padding: 5px;
270- margin: 5px;
393+ color: #6c757d;
394+ background-color: #ffffff;
395+ border: 1px solid #dee2e6;
396+ border-radius: 8px;
397+ padding: 15px 20px;
398+ margin: 10px 0px;
399+ line-height: 1.4;
271400 }
272401 """ )
273402 main_layout .addWidget (help_label )
@@ -279,24 +408,26 @@ def update_service_status(self, connected):
279408 self .status_label .setText ('✓ Dynamixel Service: CONNECTED - Motors ready for control' )
280409 self .status_label .setStyleSheet ("""
281410 QLabel {
282- background-color: #d4e8d4;
283- color: #2e7d2e;
284- border: 2px solid #4caf50;
285- border-radius: 5px;
286- padding: 8px;
287- font-weight: bold;
411+ background-color: #d5f4e6;
412+ color: #27ae60;
413+ border: 2px solid #27ae60;
414+ border-radius: 10px;
415+ padding: 12px 20px;
416+ font-weight: 600;
417+ margin: 5px;
288418 }
289419 """ )
290420 else :
291421 self .status_label .setText ('⚠ Dynamixel Service: DISCONNECTED - Start "read_write_node" to control motors' )
292422 self .status_label .setStyleSheet ("""
293423 QLabel {
294- background-color: #f8d7da;
295- color: #721c24;
296- border: 2px solid #f44336;
297- border-radius: 5px;
298- padding: 8px;
299- font-weight: bold;
424+ background-color: #ffeaa7;
425+ color: #d63031;
426+ border: 2px solid #fdcb6e;
427+ border-radius: 10px;
428+ padding: 12px 20px;
429+ font-weight: 600;
430+ margin: 5px;
300431 }
301432 """ )
302433
0 commit comments