Skip to content

Commit 27b9464

Browse files
committed
добавлен графический редактор
1 parent 9d70629 commit 27b9464

File tree

4 files changed

+191
-92
lines changed

4 files changed

+191
-92
lines changed
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
<template>
2+
<div class="canvas-container">
3+
<canvas ref="canvas" @mousedown="startDrawing" @mousemove="draw" @mouseup="stopDrawing"></canvas>
4+
<div class="controls">
5+
<button @click="undo" :disabled="!canUndo">Undo</button>
6+
<button @click="redo" :disabled="!canRedo">Redo</button>
7+
<button @click="clear">Clear</button>
8+
</div>
9+
</div>
10+
</template>
11+
12+
<script setup lang="ts">
13+
import { ref, onMounted } from 'vue';
14+
15+
class Node<T> {
16+
data: T;
17+
next: Node<T> | null = null;
18+
prev: Node<T> | null = null;
19+
20+
constructor(data: T) {
21+
this.data = data;
22+
}
23+
}
24+
25+
class LinkedList<T> {
26+
private head: Node<T> | null = null;
27+
private current: Node<T> | null = null;
28+
29+
add(data: T): void {
30+
const newNode = new Node(data);
31+
32+
if (!this.head) {
33+
this.head = newNode;
34+
this.current = this.head;
35+
} else if (this.current) {
36+
this.current.next = newNode;
37+
newNode.prev = this.current;
38+
this.current = newNode;
39+
}
40+
}
41+
42+
undo(): T | null {
43+
if (this.current && this.current.prev) {
44+
this.current = this.current.prev;
45+
return this.current.data;
46+
}
47+
return null;
48+
}
49+
50+
redo(): T | null {
51+
if (this.current && this.current.next) {
52+
this.current = this.current.next;
53+
return this.current.data;
54+
}
55+
return null;
56+
}
57+
58+
canUndo(): boolean {
59+
return !!this.current && !!this.current.prev;
60+
}
61+
62+
canRedo(): boolean {
63+
return !!this.current && !!this.current.next;
64+
}
65+
66+
getCurrentData(): T | null {
67+
return this.current ? this.current.data : null;
68+
}
69+
}
70+
71+
const canvas = ref<HTMLCanvasElement | null>(null);
72+
let ctx: CanvasRenderingContext2D | null = null;
73+
let isDrawing = false;
74+
let lastX = 0;
75+
let lastY = 0;
76+
77+
const stateList = new LinkedList<ImageData>();
78+
79+
const canUndo = ref(false);
80+
const canRedo = ref(false);
81+
82+
onMounted(() => {
83+
if (canvas.value) {
84+
ctx = canvas.value.getContext('2d');
85+
if (ctx) {
86+
ctx.strokeStyle = '#000000';
87+
ctx.lineWidth = 2;
88+
saveState();
89+
}
90+
}
91+
});
92+
93+
const startDrawing = (e: MouseEvent) => {
94+
isDrawing = true;
95+
if (ctx && canvas.value) {
96+
const rect = canvas.value.getBoundingClientRect();
97+
lastX = e.clientX - rect.left;
98+
lastY = e.clientY - rect.top;
99+
}
100+
};
101+
102+
const draw = (e: MouseEvent) => {
103+
if (!isDrawing || !ctx || !canvas.value) return;
104+
const rect = canvas.value.getBoundingClientRect();
105+
const currentX = e.clientX - rect.left;
106+
const currentY = e.clientY - rect.top;
107+
108+
ctx.beginPath();
109+
ctx.moveTo(lastX, lastY);
110+
ctx.lineTo(currentX, currentY);
111+
ctx.stroke();
112+
113+
lastX = currentX;
114+
lastY = currentY;
115+
};
116+
117+
const stopDrawing = () => {
118+
isDrawing = false;
119+
saveState();
120+
};
121+
122+
const saveState = () => {
123+
if (ctx && canvas.value) {
124+
const imageData = ctx.getImageData(0, 0, canvas.value.width, canvas.value.height);
125+
stateList.add(imageData);
126+
updateButtonStates();
127+
}
128+
};
129+
130+
const updateButtonStates = () => {
131+
canUndo.value = stateList.canUndo();
132+
canRedo.value = stateList.canRedo();
133+
};
134+
135+
const undo = () => {
136+
const prevState = stateList.undo();
137+
if (prevState && ctx && canvas.value) {
138+
ctx.putImageData(prevState, 0, 0);
139+
updateButtonStates();
140+
}
141+
};
142+
143+
const redo = () => {
144+
const nextState = stateList.redo();
145+
if (nextState && ctx && canvas.value) {
146+
ctx.putImageData(nextState, 0, 0);
147+
updateButtonStates();
148+
}
149+
};
150+
151+
const clear = () => {
152+
if (ctx && canvas.value) {
153+
ctx.clearRect(0, 0, canvas.value.width, canvas.value.height);
154+
saveState();
155+
}
156+
};
157+
</script>
158+
159+
<style lang="scss" scoped>
160+
.canvas-container {
161+
display: flex;
162+
flex-direction: column;
163+
align-items: center;
164+
165+
canvas {
166+
border: 1px solid #000;
167+
cursor: crosshair;
168+
}
169+
170+
.controls {
171+
margin-top: 10px;
172+
173+
button {
174+
margin: 0 5px;
175+
padding: 5px 10px;
176+
cursor: pointer;
177+
178+
&:disabled {
179+
opacity: 0.5;
180+
cursor: not-allowed;
181+
}
182+
}
183+
}
184+
}
185+
</style>

src/structures/list/view-list.vue

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<script setup lang="ts">
22
import AppSection from '@/components/section/app-section.vue'
33
import MusicPlayer from '@/structures/list/examples/music-player.vue'
4+
import Paint from '@/structures/list/examples/paint.vue'
45
</script>
56

67
<template>
@@ -18,6 +19,11 @@ import MusicPlayer from '@/structures/list/examples/music-player.vue'
1819
<music-player />
1920
</template>
2021

22+
<template #example2>
23+
<p>Реализация графического редактора с функциональностью undo / redo</p>
24+
<paint />
25+
</template>
26+
2127
<template #pros>
2228
Преимущество связного списка заключается в том, что удаление элемента выполняется за константное время O(1). В массиве для удаления элемента необходимо не только удалить сам элемент, но и сдвинуть все последующие элементы. В связном списке же достаточно изменить ссылки prev и next у соответствующей ноды. </template>
2329

src/structures/stack/examples/text-editor-case.vue

Lines changed: 0 additions & 86 deletions
This file was deleted.

src/structures/stack/view-stack.vue

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,5 @@ import TextEditorCase from '@/structures/stack/examples/text-editor-case.vue'
1919
<p>При этом у нас возникает следующая дилемма: мы хотим написать код, чтобы при нажатии на клавишу Esc модальное окно закрывалось, но при обычной реализации, если на обеих модальных окнах стоит обработчик закрытия по нажатию Esc, они закроются одновременно. Решение заключается в том, чтобы складывать модальные окна в Stack, тогда при нажатии на Esc удалится последний элемент из стека, в котором содержатся обе модалки.</p>
2020
<modal-flow-case />
2121
</template>
22-
23-
<!-- <template #example2>-->
24-
<!-- <p>Описание примера</p>-->
25-
26-
<!-- <text-editor-case />-->
27-
<!-- </template>-->
2822
</app-section>
2923
</template>

0 commit comments

Comments
 (0)