add Mom/Tog to ToggleWidget, Big Title, Layout panel scaffold

- ToggleWidget: add Momentary/Toggle mode selector (mirrors TransportWidget
  minus OSC); HW routing respects trigger_mode; fix invisible button on init
  by removing explicit empty stylesheet; saves/loads trigger_mode per preset
- Big Title: full-width 28px bold QLineEdit above fader row, saves per-preset
  as big_title; green underline on focus; single bottom border, no separator
- Layout panel: floating 220px window to the left of main window, toggled by
  new Layout button next to Messages; shows Sections checkboxes (Preset
  Browser, Big Title, Fader Row, Toggle Row, Transport Row); tracks window
  move/resize; checkboxes not yet wired to functionality

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Paul Lipscomb
2026-06-29 18:49:50 -04:00
parent b68e5e3ac2
commit d8f4cbcdd4
3 changed files with 179 additions and 24 deletions
+107 -11
View File
@@ -113,6 +113,7 @@ view_uuid_error_overlay = None
label_overlay_message = None
placeholder_fader_empty = None
placeholder_toggle_empty = None
big_title_edit = None
@@ -346,6 +347,11 @@ button_osc_messages_log = QPushButton("Messages")
button_osc_messages_log.setFixedWidth(80)
button_osc_messages_log.setCheckable(True)
# Widget: button_layout_panel
button_layout_panel = QPushButton("Layout")
button_layout_panel.setFixedWidth(65)
button_layout_panel.setCheckable(True)
# Layout: hstack_osc_status
hstack_osc_status = QHBoxLayout()
@@ -553,6 +559,54 @@ textedit_midi_out_log.setStyleSheet("""
}
""")
# MARK: LAYOUT PANEL
container_layout_panel = QWidget(None, Qt.WindowType.Window)
container_layout_panel.setWindowTitle("Layout")
container_layout_panel.setStyleSheet("background-color: #1a1a1a;")
container_layout_panel.setFixedWidth(220)
vstack_layout_panel = QVBoxLayout(container_layout_panel)
vstack_layout_panel.setContentsMargins(10, 10, 10, 10)
vstack_layout_panel.setSpacing(6)
label_layout_panel_title = QLabel("Sections")
label_layout_panel_title.setStyleSheet("color: #ffffff; font-weight: bold; font-size: 12px;")
vstack_layout_panel.addWidget(label_layout_panel_title)
_section_sep = QFrame()
_section_sep.setFrameShape(QFrame.Shape.HLine)
_section_sep.setStyleSheet("color: #3a3a3a;")
vstack_layout_panel.addWidget(_section_sep)
# Section rows (layout only — not wired yet)
_section_rows = [
("Preset Browser", "chk_section_preset_browser"),
("Big Title", "chk_section_big_title"),
("Fader Row", "chk_section_fader_row"),
("Toggle Row", "chk_section_toggle_row"),
("Transport Row", "chk_section_transport_row"),
]
from PyQt6.QtWidgets import QCheckBox
for _lbl, _attr in _section_rows:
_row = QWidget()
_row.setStyleSheet("background: transparent;")
_hbox = QHBoxLayout(_row)
_hbox.setContentsMargins(4, 2, 4, 2)
_hbox.setSpacing(8)
_chk = QCheckBox()
_chk.setChecked(True)
_chk.setFixedSize(16, 16)
_name_lbl = QLabel(_lbl)
_name_lbl.setStyleSheet("color: #d4d4d4; font-size: 12px; background: transparent;")
_hbox.addWidget(_chk)
_hbox.addWidget(_name_lbl)
_hbox.addStretch()
globals()[_attr] = _chk
vstack_layout_panel.addWidget(_row)
del _lbl, _attr, _row, _hbox, _chk, _name_lbl, _section_rows
vstack_layout_panel.addStretch()
# MARK: PRESET BROWSER ROW MODULE
label_preset_browser_title = QLabel("Preset Browser")
hstack_preset_browser = QHBoxLayout()
@@ -931,6 +985,7 @@ class MainWindow(QMainWindow):
# build hstack for control buttons
hstack_osc_buttons.addWidget(button_osc_toggle)
hstack_osc_buttons.addWidget(button_osc_messages_log)
hstack_osc_buttons.addWidget(button_layout_panel)
hstack_osc_buttons.addStretch()
# add all rows to vstack
@@ -944,6 +999,7 @@ class MainWindow(QMainWindow):
spinner_osc_send_port.valueChanged.connect(update_osc_server_port)
button_osc_toggle.clicked.connect(start_stop_osc_server)
button_osc_messages_log.clicked.connect(log_ui_show_hide)
button_layout_panel.clicked.connect(layout_panel_show_hide)
# add the combined stack to the row
hstack_io_row.addLayout(vstack_osc)
@@ -1061,6 +1117,9 @@ class MainWindow(QMainWindow):
button_midi_out_clear.clicked.connect(lambda: textedit_midi_out_log.clear())
container_midi_out_log.setVisible(False)
#MARK: LAYOUT PANEL
container_layout_panel.setVisible(False)
main_layout.addWidget(make_separator())
# MARK: TRANSPORT ROW CONTROLS
@@ -1122,12 +1181,22 @@ class MainWindow(QMainWindow):
global container_rows_and_widgets
global view_uuid_error_overlay
global label_overlay_message
global big_title_edit
container_rows_and_widgets = QWidget()
controls_layout = QVBoxLayout(container_rows_and_widgets)
controls_layout.setContentsMargins(0, 0, 0, 0)
main_layout.addWidget(container_rows_and_widgets)
controls_layout.addWidget(make_separator())
# MARK: BIG TITLE
big_title_edit = QLineEdit("")
big_title_edit.setPlaceholderText("Title")
big_title_edit.setAlignment(Qt.AlignmentFlag.AlignCenter)
big_title_edit.setStyleSheet(
"QLineEdit { background: transparent; border: none; border-bottom: 1px solid #3a3a3a;"
" color: #ffffff; font-size: 28px; font-weight: bold; padding: 4px 0; }"
"QLineEdit:focus { border-bottom: 1px solid #00e676; }"
)
controls_layout.addWidget(big_title_edit)
# MARK: FADER ROW CONTROLS
global hstack_row_fader_control_buttons
@@ -1288,11 +1357,15 @@ class MainWindow(QMainWindow):
super().resizeEvent(event)
if container_osc_in_log.isVisible():
log_ui_window_calc()
if container_layout_panel.isVisible():
layout_panel_window_calc()
def moveEvent(self, event):
super().moveEvent(event)
if container_osc_in_log.isVisible():
log_ui_window_calc()
if container_layout_panel.isVisible():
layout_panel_window_calc()
def make_separator():
sep = QWidget()
@@ -1347,12 +1420,13 @@ def new_blank_preset():
if not ok or not name.strip():
return
name = name.strip()
loaded_presets["presets"][name] = {"faders": [], "toggles": [], "preset_uuid": "", "browser": {"back": preset_browser_back.get_state(), "fwd": preset_browser_fwd.get_state()}, "show_hide": {}}
loaded_presets["presets"][name] = {"faders": [], "toggles": [], "preset_uuid": "", "browser": {"back": preset_browser_back.get_state(), "fwd": preset_browser_fwd.get_state()}, "show_hide": {}, "big_title": ""}
loaded_presets["__last_used__"] = name
save_presets_file(loaded_presets)
populate_preset_dropdown_menu()
menuPresets.setCurrentText(name)
clear_all_strips()
big_title_edit.setText("")
show_save_status()
def show_save_status():
@@ -1904,6 +1978,7 @@ def preset_build_snapshot(name):
"back": preset_browser_back.get_state(),
"fwd": preset_browser_fwd.get_state(),
},
"big_title": big_title_edit.text(),
"preset_uuid": label_preset_uuid.text().strip(),
"show_hide": {
"fader": fader_show_hide_config,
@@ -2024,6 +2099,7 @@ def preset_load(preset):
button_preset_browser_show_hide_config.setText("Show Parameters" if show_hide.get("browser", False) else "Hide Parameters")
button_preset_browser_show_hide_config.setChecked(show_hide.get("browser", False))
big_title_edit.setText(preset.get("big_title", ""))
label_preset_uuid.setText(preset.get("preset_uuid", ""))
check_and_index_strip_labels()
@@ -2249,15 +2325,21 @@ def midi_message_route_hw_to_output_cc(fader_uid, value):
if toggle.uid == fader_uid:
out_cc = toggle.cc_spin.value()
out_ch = toggle.ch_spin.value()
print(f"[toggle convert] HW CC{toggle.hw_cc} CH{toggle.hw_channel} ({value}) → CC{out_cc} CH{out_ch} ({value})")
should_be_on = value > 63
if toggle.toggle_state != should_be_on:
toggle.toggle_state = should_be_on
out_val = 127 if should_be_on else 0
send_cc(out_cc, out_val, channel=out_ch)
toggle.cc_input_lbl.setText(toggle._cc_input_label(value))
toggle.cc_output_lbl.setText(toggle._cc_output_label(out_val))
toggle._update_btn_style()
print(f"[toggle convert] HW CC{toggle.hw_cc} CH{toggle.hw_channel} ({value}) → CC{out_cc} CH{out_ch}")
toggle.cc_input_lbl.setText(toggle._cc_input_label(value))
if toggle.trigger_mode == "momentary":
send_cc(out_cc, 127, channel=out_ch)
toggle.cc_output_lbl.setText(toggle._cc_output_label(127))
toggle.led_dot.setStyleSheet("background-color: #00e676; border-radius: 3px;")
QTimer.singleShot(150, lambda: toggle.led_dot.setStyleSheet("background-color: #3c3c3c; border-radius: 3px;"))
else:
should_be_on = value > 63
if toggle.toggle_state != should_be_on:
toggle.toggle_state = should_be_on
out_val = 127 if should_be_on else 0
send_cc(out_cc, out_val, channel=out_ch)
toggle.cc_output_lbl.setText(toggle._cc_output_label(out_val))
toggle._update_btn_style()
return
for t in transports:
if t.uid == fader_uid:
@@ -2312,6 +2394,20 @@ def log_ui_window_calc():
container_midi_out_log.setFixedHeight(half)
container_midi_out_log.move(geo.right() + 368, geo.top() + half + 4)
def layout_panel_show_hide(checked):
container_layout_panel.setVisible(checked)
if checked:
layout_panel_window_calc()
def layout_panel_window_calc():
geo = app_window.frameGeometry()
container_layout_panel.setFixedWidth(220)
container_layout_panel.setFixedHeight(geo.height())
container_layout_panel.move(geo.left() - 224, geo.top())
def start_osc_server(port=9000):
global _osc_server, _osc_thread
stop_osc_server()