FPGA_WebLab/src/components/equipments/DDSPropertyEditor.vue

1138 lines
32 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>