// Copyright (C) 2026 Danilo M. // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License version 2 as // published by the Free Software Foundation. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. import QtQuick import ".." import "Logic.js" as Logic // At rest a record dot, with the elapsed time while recording. A click // morphs the capsule into the controls panel, as the status pill does for // sound: record, pause, webcam, and the scene list. Capsule { id: pill property string panel: "" signal openPanel() expanded: panel === "obs" restWidth: rest.implicitWidth + 24 panelWidth: 260 panelHeight: controls.implicitHeight + 28 Row { id: rest anchors.centerIn: parent spacing: 6 opacity: pill.expanded ? 0 : 1 visible: opacity > 0 Behavior on opacity { NumberAnimation { duration: 120 } } Text { anchors.verticalCenter: parent.verticalCenter text: "\uf111" color: Obs.recording ? Theme.red : Theme.subtext opacity: Obs.paused ? 0.5 : 1 font { family: Theme.iconFamily; pixelSize: 14 } } Text { anchors.verticalCenter: parent.verticalCenter visible: Obs.recording text: Logic.obsElapsed(Obs.duration) color: Theme.text font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 2 } } } MouseArea { anchors.fill: parent enabled: !pill.expanded cursorShape: Qt.PointingHandCursor onClicked: pill.openPanel() } Column { id: controls x: 14 y: 14 width: pill.panelWidth - 28 spacing: 10 opacity: pill.expanded ? 1 : 0 visible: opacity > 0 Behavior on opacity { NumberAnimation { duration: 160 } } Text { text: Obs.recording ? (Obs.paused ? "Paused " : "Recording ") + Logic.obsElapsed(Obs.duration) : "Not recording" color: Obs.recording ? Theme.red : Theme.subtext font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 2 } } Row { spacing: 8 Button { icon: Obs.recording ? "\udb81\ude66" : "\udb83\udec2" text: Obs.recording ? "Stop" : "Record" danger: Obs.recording onClicked: Obs.toggleRecord() } Button { visible: Obs.recording icon: Obs.paused ? "\udb81\udc0a" : "\udb80\udfe4" text: Obs.paused ? "Resume" : "Pause" onClicked: Obs.togglePause() } } Row { spacing: 8 visible: Obs.camId >= 0 || Obs.desktopAudio !== "" Button { visible: Obs.camId >= 0 icon: Obs.camOn ? "\udb81\udd67" : "\udb81\udd68" text: "Webcam" onClicked: Obs.toggleCam() } Button { visible: Obs.desktopAudio !== "" icon: Obs.desktopMuted ? "\udb81\udd81" : "\udb81\udd7e" text: "Audio" onClicked: Obs.toggleAudio() } } // The webcam's four corners on the recorded canvas. Row { spacing: 8 visible: Obs.camId >= 0 Button { icon: "\udb80\udc5b"; onClicked: Obs.moveCam("tl") } Button { icon: "\udb80\udc5c"; onClicked: Obs.moveCam("tr") } Button { icon: "\udb80\udc42"; onClicked: Obs.moveCam("bl") } Button { icon: "\udb80\udc43"; onClicked: Obs.moveCam("br") } } // ponytail: four columns, two rows visible; the wheel scrolls beyond // eight scenes. Selection is click-only, so scrolling never switches. Flickable { width: controls.width height: Math.min(80, grid.childrenRect.height) contentHeight: grid.childrenRect.height clip: true flickableDirection: Flickable.VerticalFlick Grid { id: grid columns: 4 spacing: 8 Repeater { model: Obs.scenes delegate: Rectangle { required property string modelData readonly property bool current: modelData === Obs.scene width: 36 height: 36 radius: 8 color: current ? Qt.alpha(Theme.accent, 0.25) : area.containsMouse ? Qt.alpha(Theme.surface, 0.8) : "transparent" Text { anchors.centerIn: parent text: SceneIcons.iconFor(modelData) color: current ? Theme.text : Theme.subtext font { family: Theme.iconFamily; pixelSize: 18 } } MouseArea { id: area anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: Obs.setScene(modelData) } } } } } } }