完成 relative axis 实现

This commit is contained in:
锦恢 2024-09-02 15:19:10 +08:00
parent 93b54f4351
commit 104ee9af5d
17 changed files with 351 additions and 62 deletions

View File

@ -1,6 +1,6 @@
@font-face {
font-family: "iconfont"; /* Project id 4440655 */
src: url('iconfont.woff2?t=1725033317914') format('woff2');
src: url('iconfont.woff2?t=1725251353876') format('woff2');
}
.iconfont {
@ -11,6 +11,10 @@
-moz-osx-font-smoothing: grayscale;
}
.icon-clear:before {
content: "\e619";
}
.icon-axis:before {
content: "\ed1f";
}

Binary file not shown.

View File

@ -49,7 +49,7 @@
<div class="digital-ide-icon big"/>
</div>
<div class="version-caption">
<span>{{ t('current-version') }} &ensp; <span class="version-wrapper">0.3.3</span></span>
<span>{{ t('current-version') }} &ensp; <span class="version-wrapper">0.4.0</span></span>
</div>
<br>
<div style="display: flex;justify-content: space-around;">

View File

@ -0,0 +1,38 @@
<template>
<div
class="axis-item"
:style="itemStyle"
>
<span>{{ props.pivot.label }}</span>
</div>
</template>
<script setup>
import { computed, defineComponent } from 'vue';
import { boxShift } from './pivot-view';
defineComponent({ name: 'axis-item' });
const props = defineProps({
pivot: {
type: Object
}
});
const itemStyle = computed(() => ({
left: props.pivot.x - boxShift + 'px'
}));
</script>
<style scoped>
.axis-item {
position: absolute;
height: 30px;
bottom: 0;
font-size: 1.0rem;
color: var(--sidebar);
font-family: var(--vcd-value-font-family);
}
</style>

View File

@ -32,7 +32,7 @@ export const SystemPivot = reactive({
this.label = formatTime(this.currentTime, timescale);
},
updateLeft() {
this.left = calcCursorLeft(this.currentTime);
this.left = time2cursorX(this.currentTime);
}
});
@ -46,20 +46,37 @@ export function changeCursorLocation() {
SystemPivot.show = true;
const pstate = globalLookup.pstate;
if (pstate) {
const { xCursor, xOffset, xScale, tgcd, timescale } = pstate;
const currentTime = Math.round((xCursor - xOffset) / xScale) * tgcd;
const { xCursor, timescale } = pstate;
const currentTime = cursorX2time(xCursor);
SystemPivot.currentTime = currentTime;
SystemPivot.updateLabel(timescale);
SystemPivot.updateLeft();
}
}
/**
* @description 给出当前左侧偏移亮计算出它实际上代表的时间
* @param {number} cursorX
* @returns {number}
*/
export function cursorX2time(cursorX) {
const pstate = globalLookup.pstate;
if (pstate) {
const { xOffset, xScale, tgcd } = pstate;
const currentTime = Math.round((cursorX - xOffset) / xScale) * tgcd;
return currentTime;
}
return 0;
}
/**
* @description 给出当前的时间比如 23ns计算当前这个点应该相对于左侧偏移多少
* @param {number} currentTime
* @returns {number}
*/
export function calcCursorLeft(currentTime) {
export function time2cursorX(currentTime) {
const pstate = globalLookup.pstate;
if (pstate) {
const { xOffset, xScale, tgcd, timescale } = pstate;

View File

@ -24,6 +24,8 @@
/>
</transition>
<RelativeAxis></RelativeAxis>
</div>
</template>
@ -38,6 +40,7 @@ import Pivot from '@/components/pivot/system-pivot.vue';
import { globalLookup } from '@/hook/global';
import MovingPivotVue from './moving-pivot.vue';
import { UserPivotCtxShows } from './pivot-view';
import RelativeAxis from './relative-axis.vue';
defineComponent({ name: 'main-render' });
@ -49,6 +52,8 @@ function onMousedown() {
updateWireCurrentValue();
changeCursorLocation();
console.log('enter');
//
for (const id of UserPivotCtxShows.keys()) {
UserPivotCtxShows.set(id, false);

View File

@ -21,7 +21,7 @@
import { emitter, globalLookup } from '@/hook/global';
import { eventHandler, registerWheelEvent } from '@/hook/wave-view';
import { computed, defineComponent, ref, onMounted } from 'vue';
import { calcCursorLeft, MovingPivot } from './cursor';
import { time2cursorX, MovingPivot, cursorX2time } from './cursor';
import formatTime from '@/hook/wave-view/format-time';
import { getNearestUserPivot, UserPivots } from './pivot-view';
@ -54,12 +54,12 @@ function onMousemove(event) {
const pstate = globalLookup.pstate;
const x = event.clientX || event.x;
pstate.xCursor = x;
const { xScale, xOffset, tgcd, timescale, xCursor } = pstate;
const currentT = Math.round((xCursor - xOffset) / xScale) * tgcd;
const { timescale, xCursor } = pstate;
const currentT = cursorX2time(xCursor);
globalLookup.currentTime = currentT;
MovingPivot.currentTime = currentT;
MovingPivot.label = formatTime(currentT, timescale);
MovingPivot.left = calcCursorLeft(currentT);
MovingPivot.left = time2cursorX(currentT);
const pivot = UserPivots.get(currentT);
if (pivot !== undefined) {

View File

@ -1,5 +1,5 @@
import { reactive, ref } from "vue";
import { calcCursorLeft, SystemPivot } from "./cursor";
import { time2cursorX, SystemPivot } from "./cursor";
import { globalLookup } from "@/hook/global";
import formatTime from "@/hook/wave-view/format-time";
@ -16,9 +16,21 @@ import formatTime from "@/hook/wave-view/format-time";
/**
* @description 该数据结构描述了所有通过 makePivot 创建的 用户信标
* @type {Map<number, UserPivot>}
* @type {Map<number, UserPivot>} number 为时间
*/
export const UserPivots = reactive(new Map());
/**
* @type {Map<number, UserPivot>} number id
*/
export const Id2Pivot = reactive(new Map());
/**
* @description 应该被渲染到当前屏幕中的
* @type {Set<number>}
*/
export const VisibleUserPivotIds = reactive(new Set());
export const orderedTimes = [];
export const UserPivotColor = ref('#1e90ff');
@ -33,12 +45,15 @@ export function createPivot(time) {
}
const id = Date.now();
const x = calcCursorLeft(time);
const x = time2cursorX(time);
const timescale = globalLookup.timescale;
const label = formatTime(time, timescale);
const show = true;
UserPivots.set(time, { time, x, id, label, show });
const pivot = { time, x, id, label, show };
UserPivots.set(time, pivot);
Id2Pivot.set(id, pivot);
orderedTimes.push(time);
orderedTimes.sort((a, b) => a < b);
}
@ -57,21 +72,39 @@ export function deletePivot(time) {
return;
}
UserPivots.delete(time);
const i = searchOrderedTimeIndex(time);
if (i !== undefined) {
orderedTimes.splice(i, 1);
}
if (UserPivots.has(time)) {
const pivot = UserPivots.get(time);
UserPivots.delete(time);
Id2Pivot.delete(pivot.id);
UserPivotCtxShows.delete(pivot.id);
}
currentPivotId.value = 0;
}
export const boxShift = 25;
export function updateAllPivots() {
// 更新系统信标
SystemPivot.updateLeft();
// 维护可见 Pivots
VisibleUserPivotIds.clear();
// 更新所有用户信标
for (const time of UserPivots.keys()) {
const pivot = UserPivots.get(time);
pivot.x = calcCursorLeft(time);
const x = time2cursorX(time);
const cx = x - boxShift; // 25 是 boxShift也就是展示数字圆形的一半宽度
if (cx > 0 && cx < (globalLookup.pstate.width || window.innerWidth)) {
VisibleUserPivotIds.add(pivot.id);
}
pivot.x = x;
}
}
@ -141,3 +174,5 @@ export function getNearestUserPivot(time) {
* @type {Map<number, boolean>}
*/
export const UserPivotCtxShows = reactive(new Map());
export const currentPivotId = ref(0);

View File

@ -0,0 +1,6 @@
import { reactive } from "vue";
export const RelativeAxis = reactive({
show: false
});

View File

@ -0,0 +1,48 @@
<template>
<div
v-if="RelativeAxis.show"
class="relative-axis"
:style="axisStyle"
>
<AxisItem
v-for="(pivotId) in VisibleUserPivotIds"
:key="pivotId"
:pivot="Id2Pivot.get(pivotId)"
/>
</div>
</template>
<script setup>
import { computed, defineComponent, onMounted } from 'vue';
import { RelativeAxis } from './relative-axis';
import { boxShift, Id2Pivot, UserPivotColor, VisibleUserPivotIds } from './pivot-view';
import AxisItem from './axis-item.vue';
defineComponent({ name: 'relative-axis' });
const axisStyle = computed(() => ({
backgroundColor: UserPivotColor.value
}));
</script>
<style scoped>
.relative-axis {
z-index: 60;
position: fixed;
bottom: 0;
left: 0;
height: 30px;
width: 100vw;
}
.relative-axis .item {
position: fixed;
padding: 5px;
height: 30px;
color: var(--sidebar);
font-size: 1.1rem;
}
</style>

View File

@ -1,6 +1,6 @@
<template>
<div
v-if="renderPivot"
v-if="VisibleUserPivotIds.has(props.id)"
@mouseenter="onEnter()"
@mouseleave="onLeave()"
@contextmenu.prevent="handleUserContextMenu($event)"
@ -36,7 +36,7 @@
<div class="menu-container">
<div
class="menu-item-container"
@click="deleteUserPivot()"
@click="deletePivot(props.time)"
>
<span class="menu-item-icon iconfont icon-delete"></span>
<span class="menu-item-name">{{ t('pivot.context.delete') }}</span>
@ -48,6 +48,14 @@
<span class="menu-item-icon iconfont icon-axis"></span>
<span class="menu-item-name">{{ t('pivot.context.display-axis') }}</span>
</div>
<div
class="menu-item-container"
:class="{ 'disable': !RelativeAxis.show }"
@click="cancelRelativeAxis()"
>
<span class="menu-item-icon iconfont icon-delete"></span>
<span class="menu-item-name">{{ t('pivot.context.cancel-axis') }}</span>
</div>
</div>
</div>
</transition>
@ -59,10 +67,13 @@
import { globalLookup } from '@/hook/global';
import { eventHandler, registerWheelEvent } from '@/hook/wave-view';
import { computed, defineComponent, ref, onMounted, reactive, nextTick } from 'vue';
import { calcCursorLeft, mousemoveEventPipes, MovingPivot } from './cursor';
import { getNearestUserPivot, orderedTimes, searchOrderedTimeIndex, UserPivotCtxShows, UserPivots } from './pivot-view';
import { time2cursorX, mousemoveEventPipes, MovingPivot, cursorX2time } from './cursor';
import { boxShift, currentPivotId, deletePivot, getNearestUserPivot, orderedTimes, searchOrderedTimeIndex, UserPivotCtxShows, UserPivots, VisibleUserPivotIds } from './pivot-view';
import formatTime from '@/hook/wave-view/format-time';
import { useI18n } from 'vue-i18n';
import { increaseBrightness, parseColor } from '@/hook/color';
import { RelativeAxis } from './relative-axis';
// import { handleUserContextMenu } from './user-context-menu';
defineComponent({ name: 'user-pivot' });
@ -88,29 +99,29 @@ const props = defineProps({
}
});
const currentColor = computed(() => {
if (props.id === currentPivotId.value) {
const rgbColor = parseColor(props.color);
const brighterColor = increaseBrightness(rgbColor, 10);
return `rgb(${brighterColor.r}, ${brighterColor.g}, ${brighterColor.b})`;
}
return props.color;
});
const colorStyle = computed(() => ({
backgroundColor: props.color
backgroundColor: currentColor.value
}));
const borderStyle = computed(() => ({
borderLeft: '5px solid ' + props.color,
borderTop: '5px solid ' + props.color
borderLeft: '5px solid ' + currentColor.value,
borderTop: '5px solid ' + currentColor.value
}));
const boxShift = 25;
const cursorStyle = computed(() => ({
left: props.x - boxShift + 'px'
}));
const renderPivot = computed(() => {
const x = props.x - boxShift;
if (x > 0 && x < (globalLookup.pstate.width || window.innerWidth)) {
return true;
}
return false;
})
function onEnter() {
MovingPivot.enterUserPivot = true;
@ -137,6 +148,7 @@ function onVLineMousedown() {
vline.dragEnable = true;
MovingPivot.dragEnable = true;
currentPivotId.value = props.id;
vline.originTime = props.time;
const pivot = UserPivots.get(vline.originTime);
@ -150,9 +162,6 @@ function onVLineMousedown() {
orderedTimes.splice(i, 1);
}
// //
// console.log('begin to drag');
mousemoveEventPipes.set(pivot.id, event => {
if (vline.dragEnable === false) {
return;
@ -161,8 +170,8 @@ function onVLineMousedown() {
pivot.x = x;
const pstate = globalLookup.pstate;
if (pstate) {
const { xOffset, xScale, tgcd, timescale } = pstate;
const t = Math.round((x - xOffset) / xScale) * tgcd;
const { timescale } = pstate;
const t = cursorX2time(x);
pivot.time = t;
pivot.label = formatTime(t, timescale);
}
@ -186,6 +195,7 @@ function onVLineMouseup() {
orderedTimes.sort((a, b) => a < b);
}
currentPivotId.value = 0;
vline.originTime = 0;
vline.dragEnable = false;
MovingPivot.currentTakenPivot = pivot;
@ -193,7 +203,6 @@ function onVLineMouseup() {
}
const contextmenu = reactive({
show: false,
x: 0,
y: 0,
});
@ -210,18 +219,23 @@ function handleUserContextMenu(event) {
UserPivotCtxShows.set(id, false);
}
UserPivotCtxShows.set(props.id, true);
currentPivotId.value = props.id;
}
function deleteUserPivot() {
const i = searchOrderedTimeIndex(props.time);
orderedTimes.splice(i, 1);
UserPivots.delete(props.time);
contextmenu.show = false;
UserPivotCtxShows.delete(props.id);
}
function displayRelativeAxis() {
currentPivotId.value = 0;
UserPivotCtxShows.delete(props.id);
}
function cancelRelativeAxis() {
if (RelativeAxis.show === false) {
return;
}
}
onMounted(() => {
@ -317,4 +331,16 @@ onMounted(() => {
background-color: var(--sidebar);
border: solid 1px var(--sidebar-border);
}
.menu-item-container.disable {
opacity: 0.5;
cursor: not-allowed !important;
}
.menu-item-container.disable:hover {
transition: unset;
color: unset !important;
background: unset !important;
animation: unset;
}
</style>

View File

@ -1,8 +1,8 @@
import { globalLookup } from "@/hook/global";
import { calcCursorLeft, SystemPivot } from "../pivot/cursor";
import { time2cursorX, SystemPivot } from "../pivot/cursor";
import { sidebarSelectedWires } from "@/hook/sidebar-select-wire";
import { updateWireCurrentValue } from "@/hook/utils";
import { createPivot } from "../pivot/pivot-view";
import { createPivot, currentPivotId, orderedTimes, UserPivotCtxShows, UserPivots } from "../pivot/pivot-view";
/**
@ -142,6 +142,14 @@ export function makePivot() {
createPivot(currentT);
}
export function clearAllPivot() {
orderedTimes.length === 0;
UserPivots.clear();
currentPivotId.value = 0;
UserPivotCtxShows.clear();
}
/**
* @description 二分查找找到最近的索引下标
* @param {number[][]} wave

View File

@ -69,21 +69,59 @@
/>
</el-tooltip>
<el-tooltip
effect="dark"
:content="t('toolbar.location.clear-location')"
placement="bottom"
raw-content
>
<div
class="item iconfont icon-clear"
:class="{ 'disable' : UserPivots.size === 0 }"
@click="clearLocationDialogShow = true"
/>
</el-tooltip>
<el-dialog
v-model="clearLocationDialogShow"
class="language-dialog"
:title="t('tips')"
width="500"
>
<span>{{ t('toolbar.location.clear-location-dialog') }}</span>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="confirmClearLocation()">
{{ t('confirm') }}
</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { defineComponent } from 'vue';
import { defineComponent, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { makePivot, toBegin, toEnd, toNextChange, toPrevChange } from './cursor-location';
import { clearAllPivot, makePivot, toBegin, toEnd, toNextChange, toPrevChange } from './cursor-location';
import { globalLookup } from '@/hook/global';
import { SystemPivot } from '../pivot/cursor';
import { currentPivotId, Id2Pivot, orderedTimes, UserPivotCtxShows, UserPivots } from '../pivot/pivot-view';
defineComponent({ name: 'cursor-location' });
const { t } = useI18n();
const clearLocationDialogShow = ref(false);
function confirmClearLocation() {
orderedTimes.length === 0;
UserPivots.clear();
Id2Pivot.clear();
currentPivotId.value = 0;
UserPivotCtxShows.clear();
clearLocationDialogShow.value = false;
}
</script>

View File

@ -32,17 +32,7 @@ import { MovingPivot } from '../pivot/cursor';
defineComponent({ name: 'toolbar' });
function onEnter() {
console.log('enter');
MovingPivot.show = false;
console.log(MovingPivot.show);
nextTick(() => {
MovingPivot.show = false;
});
setTimeout(() => {
console.log(MovingPivot.show);
MovingPivot.show = false;
}, 20);
}
</script>

68
src/hook/color.js Normal file
View File

@ -0,0 +1,68 @@
/**
* @typedef {Object} RgbColor
* @property {number} r
* @property {number} g
* @property {number} b
*/
/**
* @description 解析 rgb 字符串
* @param {string} colorString 形如 #1e90ff 或者 rgba(0, 206, 209, 1) 这样的字符串
* @returns {RgbColor | undefined}
*/
export function parseColor(colorString) {
// 检查是否是十六进制颜色
if (colorString.startsWith('#')) {
let hex = colorString.slice(1);
if (hex.length === 3) {
hex = hex.split('').map(c => c + c).join('');
}
const r = parseInt(hex.slice(0, 2), 16);
const g = parseInt(hex.slice(2, 4), 16);
const b = parseInt(hex.slice(4, 6), 16);
return { r, g, b };
}
// 检查是否是 RGBA 颜色
else if (colorString.startsWith('rgba')) {
const matches = colorString.match(/rgba\((\d+),\s*(\d+),\s*(\d+),\s*(\d+(\.\d+)?)\)/);
if (matches) {
const r = parseInt(matches[1], 10);
const g = parseInt(matches[2], 10);
const b = parseInt(matches[3], 10);
return { r, g, b };
}
}
// 检查是否是 RGB 颜色
else if (colorString.startsWith('rgb')) {
const matches = colorString.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
if (matches) {
const r = parseInt(matches[1], 10);
const g = parseInt(matches[2], 10);
const b = parseInt(matches[3], 10);
return { r, g, b };
}
}
return undefined;
}
/**
* @description 提升颜色的亮度
* @param {RgbColor} rgb
* @param {number} percent 0 - 100 的数字代表增强的亮度比例
* @returns {RgbColor}
*/
export function increaseBrightness(rgb, percent) {
// 确保 percent 在 0 到 100 之间
percent = Math.max(0, Math.min(100, percent));
// 计算每个颜色分量的增量
const increment = (percent / 100) * 255;
// 提升每个颜色分量的亮度
const r = Math.min(255, Math.round(rgb.r + increment));
const g = Math.min(255, Math.round(rgb.g + increment));
const b = Math.min(255, Math.round(rgb.b + increment));
return { r, g, b };
}

View File

@ -92,6 +92,8 @@
"toolbar.location.to-next-change": "Go to Next Change Edge",
"toolbar.location.to-prev-change": "Go to Previous Change Edge",
"toolbar.location.make-location": "Create New Pivot",
"toolbar.location.clear-location": "Clear All Pivots",
"toolbar.location.clear-location-dialog": "Are you sure to clear all the pivots ?",
"context-menu.cannot-join-repeat-group": "current signal is already contained in this group",
@ -102,6 +104,7 @@
"pivot.context.delete": "delete pivot",
"pivot.context.display-axis": "create relative axis",
"pivot.context.cancel-axis": "cancel relative axis",
"setting.appearance.pivot-color": "pivot color",
"setting.appearance.moving-pivot": "moving pivot",

View File

@ -90,6 +90,8 @@
"toolbar.location.to-next-change": "前往下一个变化的边沿",
"toolbar.location.to-prev-change": "前往上一个变化的边沿",
"toolbar.location.make-location": "创建新的信标",
"toolbar.location.clear-location": "清除所有信标",
"toolbar.location.clear-location-dialog": "您确定要清除所有的信标吗?",
"context-menu.cannot-join-repeat-group": "当前信号已在此分组中",
@ -100,6 +102,7 @@
"pivot.context.delete": "删除信标",
"pivot.context.display-axis": "创建相对坐标轴",
"pivot.context.cancel-axis": "取消相对坐标轴",
"setting.appearance.pivot-color": "信标颜色",
"setting.appearance.moving-pivot": "移动信标",