This repository has been archived on 2025-10-29. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
FPGA_WebLab/src/components/equipments/DDSPropertyEditor.vue
T
2025-05-11 14:41:38 +08:00

1138 lines
32 KiB
Vue

<template> <div class="dds-property-editor">
<CollapsibleSection title="信号发生器" :isExpanded="true">
<div class="dds-editor-container">
<div class="dds-display">
<!-- 波形显示区域 -->
<div class="waveform-display">
<svg width="100%" height="120" viewBox="0 0 300 120">
<rect width="300" height="120" fill="#1a1f25" />
<path :d="currentWaveformPath" stroke="lime" stroke-width="2" fill="none" />
<!-- 频率和相位显示 -->
<text x="20" y="25" fill="#0f0" font-size="14">{{ displayFrequency }}</text>
<text x="200" y="25" fill="#0f0" font-size="14">φ: {{ phase }}°</text>
<text x="150" y="110" fill="#0f0" font-size="14" text-anchor="middle">{{ displayTimebase }}</text>
</svg>
<!-- 时基控制 -->
<div class="timebase-controls">
<button class="timebase-button" @click="decreaseTimebase">-</button>
<span class="timebase-label">时基</span>
<button class="timebase-button" @click="increaseTimebase">+</button>
</div>
</div>
<!-- 波形选择区 -->
<div class="waveform-selector">
<div
v-for="(name, index) in waveformNames"
:key="`wave-${index}`"
:class="['waveform-option', { active: currentWaveformIndex === index }]"
@click="selectWaveform(index)"
>
{{ name }}
</div>
</div>
<!-- 频率和相位控制 -->
<div class="control-row">
<div class="control-group">
<span class="control-label">频率:</span>
<div class="control-buttons">
<button class="control-button" @click="decreaseFrequency">-</button>
<input
v-model="frequencyInput"
@blur="applyFrequencyInput"
@keyup.enter="applyFrequencyInput"
class="control-input"
type="text"
/>
<button class="control-button" @click="increaseFrequency">+</button>
</div>
</div>
<div class="control-group">
<span class="control-label">相位:</span>
<div class="control-buttons">
<button class="control-button" @click="decreasePhase">-</button>
<input
v-model="phaseInput"
@blur="applyPhaseInput"
@keyup.enter="applyPhaseInput"
class="control-input"
type="text"
/>
<button class="control-button" @click="increasePhase">+</button>
</div>
</div>
</div>
<!-- 自定义波形输入 -->
<div class="custom-waveform">
<div class="section-heading">自定义波形</div> <div class="input-group">
<label class="input-label">函数表达式:</label>
<input
v-model="customWaveformExpression"
class="function-input"
placeholder="例如: sin(t) 或 x^(2/3)+0.9*sqrt(3.3-x^2)*sin(a*PI*x) [a=7.8]"
@keyup.enter="applyCustomWaveform"
/>
<button class="apply-button" @click="applyCustomWaveform">应用</button>
</div>
<div class="example-functions">
<div class="example-label">示例函数:</div>
<div class="example-buttons">
<button
class="example-button"
@click="applyExampleFunction('sin(t)')"
>正弦波</button>
<button
class="example-button"
@click="applyExampleFunction('sin(t)^3')"
>立方正弦</button> <button
class="example-button"
@click="applyExampleFunction('((x)^(2/3)+0.9*sqrt(3.3-(x)^2)*sin(10*PI*(x)))*0.75')"
>心形函数</button>
</div>
</div>
<div class="drawing-area">
<div class="section-heading">波形绘制</div>
<div
class="waveform-canvas-container"
ref="canvasContainer"
>
<canvas
ref="drawingCanvas"
class="drawing-canvas"
width="280"
height="100"
@mousedown="startDrawing"
@mousemove="draw"
@mouseup="stopDrawing"
@mouseleave="stopDrawing"
></canvas>
<div class="canvas-actions">
<button class="canvas-button" @click="clearCanvas">清除</button>
<button class="canvas-button" @click="applyDrawnWaveform">应用绘制</button>
</div>
</div>
</div>
</div>
<!-- 保存的波形 -->
<div class="saved-waveforms">
<div class="section-heading">波形存储槽</div>
<div class="slot-container">
<div
v-for="(slot, index) in waveformSlots"
:key="`slot-${index}`"
:class="['waveform-slot', { empty: !slot.name }]"
@click="loadWaveformSlot(index)"
>
<span class="slot-name">{{ slot.name || `槽 ${index+1}` }}</span>
<button
class="save-button"
@click.stop="saveCurrentToSlot(index)"
>
保存
</button>
</div>
</div>
</div>
</div>
</div>
</CollapsibleSection>
</div>
</template>
<script setup lang="ts">
import { ref, computed, watch, onMounted } from 'vue';
import CollapsibleSection from '../CollapsibleSection.vue';
const props = defineProps<{
modelValue: any;
}>();
const emit = defineEmits(['update:modelValue']);
// 波形状态
const frequency = ref(props.modelValue?.frequency || 1000);
const phase = ref(props.modelValue?.phase || 0);
const timebase = ref(props.modelValue?.timebase || 1); // 时基默认为1倍
const currentWaveformIndex = ref(0);
const waveformNames = ['正弦波', '方波', '三角波', '锯齿波', '自定义'];
const waveforms = ['sine', 'square', 'triangle', 'sawtooth', 'custom'];
// 波形函数集合
interface WaveformFunction {
(x: number, width: number, height: number, phaseRad: number): number;
}
interface WaveformFunctions {
[key: string]: WaveformFunction;
}
const waveformFunctions: WaveformFunctions = {
// 正弦波函数: sin(2π*x + φ)
sine: (x: number, width: number, height: number, phaseRad: number): number => {
return height/2 * Math.sin(2 * Math.PI * (x / width) * 2 + phaseRad);
},
// 方波函数: 周期性的高低电平
square: (x: number, width: number, height: number, phaseRad: number): number => {
const normX = (x / width + phaseRad / (2 * Math.PI)) % 1;
return normX < 0.5 ? height/4 : -height/4;
},
// 三角波函数: 线性上升和下降
triangle: (x: number, width: number, height: number, phaseRad: number): number => {
const normX = (x / width + phaseRad / (2 * Math.PI)) % 1;
return height/2 - height * Math.abs(2 * normX - 1);
},
// 锯齿波函数: 线性上升,瞬间下降
sawtooth: (x: number, width: number, height: number, phaseRad: number): number => {
const normX = (x / width + phaseRad / (2 * Math.PI)) % 1;
return height/2 - height/2 * (2 * normX);
},
// 自定义波形函数占位符
custom: (x: number, width: number, height: number, phaseRad: number): number => {
return 0; // 默认返回0,会在应用自定义表达式时更新
}
};
// 输入控制
const frequencyInput = ref(formatFrequency(frequency.value));
const phaseInput = ref(phase.value.toString());
const customWaveformExpression = ref('');
// 波形槽
const waveformSlots = ref<{ name: string; type: string; data: number[][] | null }[]>([
{ name: '正弦波', type: 'sine', data: null },
{ name: '方波', type: 'square', data: null },
{ name: '三角波', type: 'triangle', data: null },
{ name: '', type: '', data: null }
]);
// 绘图相关
const drawingCanvas = ref<HTMLCanvasElement | null>(null);
const canvasContainer = ref<HTMLDivElement | null>(null);
const isDrawing = ref(false);
const drawPoints = ref<number[][]>([]);
// 格式化频率显示
function formatFrequency(freq: number): string {
if (freq >= 1000000) {
return `${(freq / 1000000).toFixed(2)} MHz`;
} else if (freq >= 1000) {
return `${(freq / 1000).toFixed(2)} kHz`;
} else {
return `${freq.toFixed(2)} Hz`;
}
}
// 格式化时基显示
function formatTimebase(tb: number): string {
if (tb < 0.1) {
return `${(tb * 1000).toFixed(0)} ms/div`;
} else if (tb < 1) {
return `${(tb * 1000).toFixed(0)} ms/div`;
} else {
return `${tb.toFixed(1)} s/div`;
}
}
// 计算当前显示时基
const displayTimebase = computed(() => formatTimebase(timebase.value));
// 时基调整函数
function increaseTimebase() {
if (timebase.value < 0.1) {
timebase.value *= 2;
} else if (timebase.value < 1) {
timebase.value += 0.1;
} else {
timebase.value += 0.5;
}
timebase.value = Math.min(timebase.value, 5); // 最大5s/div
updateModelValue();
}
function decreaseTimebase() {
if (timebase.value <= 0.1) {
timebase.value /= 2;
} else if (timebase.value <= 1) {
timebase.value -= 0.1;
} else {
timebase.value -= 0.5;
}
timebase.value = Math.max(timebase.value, 0.01); // 最小10ms/div
updateModelValue();
}
// 计算当前显示频率
const displayFrequency = computed(() => formatFrequency(frequency.value));
// 生成波形路径
const currentWaveformPath = computed(() => {
const width = 300;
const height = 80;
const xOffset = 0;
const yOffset = 30;
const currentWaveform = waveforms[currentWaveformIndex.value];
const phaseRadians = phase.value * Math.PI / 180;
// 时基和频率共同影响周期数量
// 频率因素 - 频率越高,一个屏幕内显示的周期越多
// 使用对数缩放可以更好地表示广泛范围的频率变化
const freqLog = Math.log10(frequency.value) - 2; // 从100Hz开始作为基准
const frequencyFactor = Math.max(0.1, Math.min(10, freqLog)); // 限制在合理范围内
// 时基影响周期数量 - 时基越小,显示的周期越多
const timebaseFactor = 1 / timebase.value;
// 组合因素
const scaleFactor = timebaseFactor * frequencyFactor;
let path = '';
// 使用函数生成波形
if (currentWaveform === 'custom') {
// 自定义波形
if (drawPoints.value.length > 0) {
path = `M${xOffset + drawPoints.value[0][0]},${yOffset + drawPoints.value[0][1]}`;
for (let i = 1; i < drawPoints.value.length; i++) {
path += ` L${xOffset + drawPoints.value[i][0]},${yOffset + drawPoints.value[i][1]}`;
}
} else {
// 如果没有绘制点但选择了自定义波形,仍然使用函数生成
const waveFunction = waveformFunctions.custom; path = `M${xOffset},${yOffset + height/2}`;
for (let x = 0; x <= width; x++) {
const scaledX = x * scaleFactor;
const y = waveFunction(scaledX, width, height, phaseRadians);
// 注意:心形函数可能返回undefined或极端值,需要处理
if (typeof y === 'number' && isFinite(y)) {
path += ` L${x + xOffset},${yOffset + height/2 - y}`;
} else {
// 如果返回异常值,保持当前位置
path += ` L${x + xOffset},${yOffset + height/2}`;
}
}
}
} else {
// 使用预定义的波形函数
const waveFunction = waveformFunctions[currentWaveform as keyof typeof waveformFunctions];
// 生成路径点
path = `M${xOffset},${yOffset + height/2}`;
for (let x = 0; x <= width; x++) {
// 应用组合缩放因素 - 影响x轴的缩放
const scaledX = x * scaleFactor;
const y = waveFunction(scaledX, width, height, phaseRadians);
path += ` L${x + xOffset},${yOffset + height/2 - y}`;
}
}
return path;
});
// 波形操作函数
function selectWaveform(index: number) {
currentWaveformIndex.value = index;
updateModelValue();
}
function increaseFrequency() {
if (frequency.value < 10) {
frequency.value += 0.1;
} else if (frequency.value < 100) {
frequency.value += 1;
} else if (frequency.value < 1000) {
frequency.value += 10;
} else if (frequency.value < 10000) {
frequency.value += 100;
} else if (frequency.value < 100000) {
frequency.value += 1000;
} else {
frequency.value += 10000;
}
frequency.value = Math.min(frequency.value, 10000000); // 最大10MHz
frequency.value = parseFloat(frequency.value.toFixed(1)); // 修复浮点数精度问题
frequencyInput.value = formatFrequency(frequency.value);
updateModelValue();
}
function decreaseFrequency() {
if (frequency.value <= 10) {
frequency.value -= 0.1;
} else if (frequency.value <= 100) {
frequency.value -= 1;
} else if (frequency.value <= 1000) {
frequency.value -= 10;
} else if (frequency.value <= 10000) {
frequency.value -= 100;
} else if (frequency.value <= 100000) {
frequency.value -= 1000;
} else {
frequency.value -= 10000;
}
frequency.value = Math.max(frequency.value, 0.1); // 最小0.1Hz
frequency.value = parseFloat(frequency.value.toFixed(1)); // 修复浮点数精度问题
frequencyInput.value = formatFrequency(frequency.value);
updateModelValue();
}
function applyFrequencyInput() {
let value = parseFloat(frequencyInput.value);
// 处理单位
if (frequencyInput.value.includes('MHz')) {
value = parseFloat(frequencyInput.value) * 1000000;
} else if (frequencyInput.value.includes('kHz')) {
value = parseFloat(frequencyInput.value) * 1000;
} else if (frequencyInput.value.includes('Hz')) {
value = parseFloat(frequencyInput.value);
}
if (!isNaN(value)) {
frequency.value = Math.min(Math.max(value, 0.1), 10000000);
frequencyInput.value = formatFrequency(frequency.value);
updateModelValue();
} else {
frequencyInput.value = formatFrequency(frequency.value);
}
}
function increasePhase() {
phase.value += 15;
if (phase.value >= 360) {
phase.value -= 360;
}
phaseInput.value = phase.value.toString();
updateModelValue();
}
function decreasePhase() {
phase.value -= 15;
if (phase.value < 0) {
phase.value += 360;
}
phaseInput.value = phase.value.toString();
updateModelValue();
}
function applyPhaseInput() {
let value = parseFloat(phaseInput.value);
if (!isNaN(value)) {
// 确保相位在0-360之间
while (value >= 360) value -= 360;
while (value < 0) value += 360;
phase.value = value;
phaseInput.value = phase.value.toString();
updateModelValue();
} else {
phaseInput.value = phase.value.toString();
}
}
function applyCustomWaveform() {
if (customWaveformExpression.value) {
try {
// 创建自定义波形函数
createCustomWaveformFunction();
currentWaveformIndex.value = waveforms.indexOf('custom');
drawCustomWaveformFromExpression();
updateModelValue();
} catch (error) {
console.error('Invalid expression:', error);
// 这里可以添加一些错误提示
}
}
}
// 应用示例函数
function applyExampleFunction(expression: string) {
customWaveformExpression.value = expression;
applyCustomWaveform();
}
// 创建自定义波形函数
function createCustomWaveformFunction() {
// 使用 mathjs 解析表达式并创建函数
const expression = customWaveformExpression.value;
// 导入 mathjs
import('mathjs').then((math) => {
try {
// 预编译表达式以提高性能
const compiledExpression = math.compile(expression);
// 添加自定义函数到波形函数集合中
waveformFunctions.custom = (x: number, width: number, height: number, phaseRad: number): number => {
try {
// 相位调整 - 将相位转换为x轴的位移
const phaseShift = phaseRad / (2 * Math.PI);
// 标准化参数,使x落在 0-1 范围内,并应用相位
let normalizedX = ((x / width) + phaseShift) % 1;
// 心形函数需要x映射到[-1.5,1.5]范围,以确保完整显示心形
// 这个范围比[-1,1]稍大,确保心形两侧完整显示
const scaledX = (normalizedX * 3) - 1.5;
// 创建参数对象,包括各种变量和常量供表达式使用
const scope = {
x: scaledX, // 映射后的x变量
t: normalizedX * 2 * Math.PI, // 角度变量 (0-2π),不包括相位
phase: phaseRad, // 相位值,独立参数
PI: Math.PI, // 常量 PI
a: 7.8, // 心形函数振荡参数
};
// 计算表达式
let result = compiledExpression.evaluate(scope);
// 确保结果在合理范围内
if (typeof result !== 'number' || isNaN(result) || !isFinite(result)) {
result = 0;
}
// 对于心形函数,我们可能需要额外调整振幅因子
// 确保振幅在合适范围内
result = Math.max(-1, Math.min(1, result));
// 返回适当的振幅
return height/2 * result;
} catch (e) {
console.error('Error evaluating expression:', e);
return 0;
}
};
// 立即更新波形显示
updateModelValue();
} catch (parseError) {
console.error('Error parsing expression:', parseError);
// 解析错误时使用默认正弦波
waveformFunctions.custom = (x: number, width: number, height: number, phaseRad: number): number => {
return height/2 * Math.sin(2 * Math.PI * (x / width) * 2 + phaseRad);
};
}
}).catch(error => {
console.error('Error loading mathjs:', error);
});
};
// 绘制自定义波形
function drawCustomWaveformFromExpression() {
const canvas = drawingCanvas.value;
if (!canvas) return;
const ctx = canvas.getContext('2d');
if (!ctx) return;
const width = canvas.width;
const height = canvas.height;
// 清除画布
ctx.clearRect(0, 0, width, height);
ctx.strokeStyle = '#0f0';
ctx.lineWidth = 2;
// 创建自定义函数 - 先创建函数再绘制
createCustomWaveformFunction();
// 使用波形函数来绘制
const phaseRad = phase.value * Math.PI / 180;
// 频率因素,与主波形显示保持一致
// 频率越高,显示的周期数就越多
const freqLog = Math.log10(frequency.value) - 2; // 从100Hz开始作为基准
const frequencyFactor = Math.max(0.1, Math.min(10, freqLog));
// 时基因素 - 时基越小,显示周期越多
const timebaseFactor = 1 / timebase.value;
// 组合因素 - 同时应用频率和时基缩放
const scaleFactor = timebaseFactor * frequencyFactor;
// 基于表达式生成点的坐标
drawPoints.value = [];
// 等待一些时间让 mathjs 加载和编译表达式
setTimeout(() => {
ctx.beginPath();
let firstPoint = true;
let previousY = null;
const samplePoints = 300; // 增加采样点以提高平滑度
for (let i = 0; i <= samplePoints; i++) {
const x = (i / samplePoints) * width;
// 应用缩放因素计算 y 值
const scaledX = x * scaleFactor;
const y = waveformFunctions.custom(scaledX, width, height, phaseRad);
const canvasY = height / 2 - y;
// 检测是否是有效值
if (isNaN(canvasY) || !isFinite(canvasY)) {
continue; // 跳过无效点
}
// 检测是否有大跳变(可能是不连续函数)
if (previousY !== null && Math.abs(canvasY - previousY) > height / 2) {
// 在大跳变处断开路径
ctx.stroke();
ctx.beginPath();
firstPoint = true;
}
if (firstPoint) {
ctx.moveTo(x, canvasY);
firstPoint = false;
} else {
ctx.lineTo(x, canvasY);
}
previousY = canvasY;
drawPoints.value.push([x, canvasY]);
}
ctx.stroke();
}, 100); // 短暂延迟以确保 mathjs 已加载
}
// 绘图功能
function startDrawing(event: MouseEvent) {
isDrawing.value = true;
const canvas = drawingCanvas.value;
if (!canvas) return;
const rect = canvas.getBoundingClientRect();
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
const ctx = canvas.getContext('2d');
if (!ctx) return;
// 清除画布
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 开始新的绘制
drawPoints.value = [[x, y]];
ctx.beginPath();
ctx.strokeStyle = '#0f0';
ctx.lineWidth = 2;
ctx.moveTo(x, y);
}
function draw(event: MouseEvent) {
if (!isDrawing.value) return;
const canvas = drawingCanvas.value;
if (!canvas) return;
const ctx = canvas.getContext('2d');
if (!ctx) return;
const rect = canvas.getBoundingClientRect();
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
// 添加点
drawPoints.value.push([x, y]);
// 绘制线
ctx.lineTo(x, y);
ctx.stroke();
}
function stopDrawing() {
isDrawing.value = false;
}
function clearCanvas() {
const canvas = drawingCanvas.value;
if (!canvas) return;
const ctx = canvas.getContext('2d');
if (!ctx) return;
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawPoints.value = [];
}
function applyDrawnWaveform() {
if (drawPoints.value.length > 0) {
currentWaveformIndex.value = waveforms.indexOf('custom');
updateModelValue();
}
}
// 波形存储槽操作
function saveCurrentToSlot(index: number) {
waveformSlots.value[index] = {
name: waveformNames[currentWaveformIndex.value],
type: waveforms[currentWaveformIndex.value],
data: drawPoints.value.length > 0 && currentWaveformIndex.value === waveforms.indexOf('custom')
? [...drawPoints.value]
: null
};
}
function loadWaveformSlot(index: number) {
const slot = waveformSlots.value[index];
if (!slot.type) return;
const waveformIndex = waveforms.indexOf(slot.type);
if (waveformIndex !== -1) {
currentWaveformIndex.value = waveformIndex;
// 如果是自定义波形且有数据,加载数据
if (slot.type === 'custom' && slot.data !== null && slot.data.length > 0) {
drawPoints.value = [...slot.data];
// 更新画布
const canvas = drawingCanvas.value;
if (canvas) {
const ctx = canvas.getContext('2d');
if (ctx) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.strokeStyle = '#0f0';
ctx.lineWidth = 2;
for (let i = 0; i < slot.data.length; i++) {
const [x, y] = slot.data[i];
if (i === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
}
ctx.stroke();
}
}
}
updateModelValue();
}
}
// 更新模型值
function updateModelValue() {
const newValue = {
...props.modelValue,
frequency: frequency.value,
phase: phase.value,
timebase: timebase.value,
waveform: waveforms[currentWaveformIndex.value],
customWaveformPoints: currentWaveformIndex.value === waveforms.indexOf('custom') ? [...drawPoints.value] : []
};
emit('update:modelValue', newValue);
}
// 初始化
onMounted(() => {
// 初始化波形和参数
if (props.modelValue) {
if (props.modelValue.frequency !== undefined) {
frequency.value = props.modelValue.frequency;
frequencyInput.value = formatFrequency(frequency.value);
}
if (props.modelValue.phase !== undefined) {
phase.value = props.modelValue.phase;
phaseInput.value = phase.value.toString();
}
if (props.modelValue.timebase !== undefined) {
timebase.value = props.modelValue.timebase;
}
if (props.modelValue.waveform) {
const index = waveforms.indexOf(props.modelValue.waveform);
if (index !== -1) {
currentWaveformIndex.value = index;
}
}
// 加载自定义波形点
if (props.modelValue.customWaveformPoints && props.modelValue.customWaveformPoints.length > 0) {
drawPoints.value = [...props.modelValue.customWaveformPoints];
// 绘制到画布上
const canvas = drawingCanvas.value;
if (canvas && currentWaveformIndex.value === waveforms.indexOf('custom')) {
const ctx = canvas.getContext('2d');
if (ctx) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.strokeStyle = '#0f0';
ctx.lineWidth = 2;
for (let i = 0; i < drawPoints.value.length; i++) {
const [x, y] = drawPoints.value[i];
if (i === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
}
ctx.stroke();
}
}
}
}
});
// 监听model变化
watch(() => props.modelValue, (newVal) => {
if (newVal && newVal.frequency !== undefined && newVal.frequency !== frequency.value) {
frequency.value = newVal.frequency;
frequencyInput.value = formatFrequency(frequency.value);
}
if (newVal && newVal.phase !== undefined && newVal.phase !== phase.value) {
phase.value = newVal.phase;
phaseInput.value = phase.value.toString();
}
if (newVal && newVal.timebase !== undefined && newVal.timebase !== timebase.value) {
timebase.value = newVal.timebase;
}
if (newVal && newVal.waveform) {
const index = waveforms.indexOf(newVal.waveform);
if (index !== -1 && index !== currentWaveformIndex.value) {
currentWaveformIndex.value = index;
}
}
}, { deep: true });
</script>
<style scoped>
.dds-property-editor {
width: 100%;
}
.dds-editor-container {
padding: 8px;
border-radius: 4px;
background-color: var(--base-200, #2a303c);
}
.dds-display {
display: flex;
flex-direction: column;
gap: 12px;
}
.waveform-display {
border-radius: 4px;
overflow: hidden;
position: relative;
}
.timebase-controls {
position: absolute;
bottom: 2px;
right: 2px;
display: flex;
align-items: center;
gap: 4px;
padding: 3px;
background-color: rgba(26, 31, 37, 0.7);
border-radius: 4px;
}
.timebase-button {
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
background-color: var(--base-300, #374151);
border: none;
border-radius: 4px;
color: var(--base-content, #a6adbb);
cursor: pointer;
transition: background-color 0.2s ease;
font-size: 14px;
font-weight: bold;
}
.timebase-button:hover {
background-color: var(--base-content, #a6adbb);
color: var(--base-300, #374151);
}
.timebase-label {
font-size: 0.8rem;
color: var(--base-content, #a6adbb);
white-space: nowrap;
}
.waveform-selector {
display: flex;
justify-content: space-between;
gap: 8px;
margin-top: 8px;
}
.waveform-option {
flex: 1;
padding: 8px 4px;
text-align: center;
background-color: var(--base-100, #1d232a);
border-radius: 4px;
cursor: pointer;
transition: background-color 0.2s ease;
font-size: 0.9rem;
}
.waveform-option:hover {
background-color: var(--base-300, #374151);
}
.waveform-option.active {
background-color: var(--primary, #570df8);
color: white;
}
.control-row {
display: flex;
justify-content: space-between;
gap: 12px;
margin-top: 8px;
}
.control-group {
flex: 1;
display: flex;
flex-direction: column;
gap: 4px;
}
.control-label {
font-size: 0.9rem;
color: var(--base-content, #a6adbb);
}
.control-buttons {
display: flex;
align-items: center;
gap: 4px;
}
.control-button {
width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
background-color: var(--base-300, #374151);
border: none;
border-radius: 4px;
color: var(--base-content, #a6adbb);
cursor: pointer;
transition: background-color 0.2s ease;
}
.control-button:hover {
background-color: var(--base-content, #a6adbb);
color: var(--base-300, #374151);
}
.control-input {
flex: 1;
height: 28px;
padding: 0 8px;
background-color: var(--base-100, #1d232a);
border: 1px solid var(--base-300, #374151);
border-radius: 4px;
color: var(--base-content, #a6adbb);
text-align: center;
}
.custom-waveform {
margin-top: 16px;
padding: 12px;
background-color: var(--base-100, #1d232a);
border-radius: 4px;
}
.section-heading {
font-size: 0.9rem;
font-weight: 600;
margin-bottom: 8px;
color: var(--base-content, #a6adbb);
}
.input-group {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 8px;
}
.example-functions {
margin-bottom: 12px;
}
.example-label {
font-size: 0.85rem;
color: var(--base-content, #a6adbb);
margin-bottom: 4px;
}
.example-buttons {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.example-button {
padding: 4px 8px;
background-color: var(--base-200, #2a303c);
border: 1px solid var(--base-300, #374151);
border-radius: 4px;
color: var(--base-content, #a6adbb);
cursor: pointer;
transition: background-color 0.2s ease, color 0.2s ease;
font-size: 0.8rem;
}
.example-button:hover {
background-color: var(--primary, #570df8);
color: white;
}
.input-label {
font-size: 0.9rem;
color: var(--base-content, #a6adbb);
white-space: nowrap;
}
.function-input {
flex: 1;
height: 32px;
padding: 0 8px;
background-color: var(--base-200, #2a303c);
border: 1px solid var(--base-300, #374151);
border-radius: 4px;
color: var(--base-content, #a6adbb);
}
.apply-button {
height: 32px;
padding: 0 12px;
background-color: var(--primary, #570df8);
border: none;
border-radius: 4px;
color: white;
cursor: pointer;
transition: background-color 0.2s ease;
}
.apply-button:hover {
background-color: var(--primary-focus, #4406cb);
}
.drawing-area {
margin-top: 12px;
}
.waveform-canvas-container {
border: 1px solid var(--base-300, #374151);
border-radius: 4px;
overflow: hidden;
}
.drawing-canvas {
width: 100%;
height: 100px;
background-color: var(--base-200, #2a303c);
cursor: crosshair;
}
.canvas-actions {
display: flex;
justify-content: flex-end;
gap: 8px;
padding: 8px;
background-color: var(--base-200, #2a303c);
}
.canvas-button {
padding: 4px 8px;
background-color: var(--base-300, #374151);
border: none;
border-radius: 4px;
color: var(--base-content, #a6adbb);
cursor: pointer;
transition: background-color 0.2s ease;
font-size: 0.8rem;
}
.canvas-button:hover {
background-color: var(--base-content, #a6adbb);
color: var(--base-300, #374151);
}
.saved-waveforms {
margin-top: 16px;
}
.slot-container {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 8px;
}
.waveform-slot {
position: relative;
padding: 8px;
background-color: var(--base-200, #2a303c);
border: 1px solid var(--base-300, #374151);
border-radius: 4px;
cursor: pointer;
transition: background-color 0.2s ease;
}
.waveform-slot:hover {
background-color: var(--base-300, #374151);
}
.waveform-slot.empty {
background-color: var(--base-100, #1d232a);
}
.slot-name {
display: block;
font-size: 0.9rem;
margin-right: 50px;
}
.save-button {
position: absolute;
right: 8px;
top: 6px;
padding: 2px 6px;
background-color: var(--primary, #570df8);
border: none;
border-radius: 4px;
color: white;
cursor: pointer;
transition: background-color 0.2s ease;
font-size: 0.8rem;
}
.save-button:hover {
background-color: var(--primary-focus, #4406cb);
}
</style>