5
5
from argparse import Namespace
6
6
from math import floor , log10
7
7
from re import Pattern , compile
8
- from typing import Final
8
+ from typing import Final , NamedTuple , TypeAlias
9
9
10
10
##############################################################################
11
11
# Textual imports.
18
18
from textual_enhanced .commands import ChangeTheme , Command , Help
19
19
from textual_enhanced .dialogs import ModalInput
20
20
from textual_enhanced .screen import EnhancedScreen
21
+ from textual_enhanced .tools import History
21
22
22
23
##############################################################################
23
24
# Local imports.
48
49
SetColourToShadesOfBlue ,
49
50
SetColourToShadesOfGreen ,
50
51
SetColourToShadesOfRed ,
52
+ Undo ,
51
53
ZeroZero ,
52
54
ZoomIn ,
53
55
ZoomInFaster ,
58
60
from ..providers import MainCommands
59
61
60
62
63
+ ##############################################################################
64
+ class Situation (NamedTuple ):
65
+ """A class to hold a particular situation we can undo to."""
66
+
67
+ x_position : float
68
+ """The X position in the plot."""
69
+ y_position : float
70
+ """The Y position in the plot."""
71
+ zoom : float
72
+ """The zoom level."""
73
+ max_iteration : int
74
+ """The maximum iteration."""
75
+ multibrot : float
76
+ """The multibrot setting."""
77
+
78
+
79
+ ##############################################################################
80
+ PlotHistory : TypeAlias = History [Situation ]
81
+ """Type of the plot history."""
82
+
83
+
61
84
##############################################################################
62
85
class Main (EnhancedScreen [None ]):
63
86
"""The main screen for the application."""
@@ -101,6 +124,7 @@ class Main(EnhancedScreen[None]):
101
124
SetColourToShadesOfBlue ,
102
125
SetColourToShadesOfGreen ,
103
126
SetColourToShadesOfRed ,
127
+ Undo ,
104
128
ZeroZero ,
105
129
ZoomIn ,
106
130
ZoomInFaster ,
@@ -120,6 +144,8 @@ def __init__(self, arguments: Namespace) -> None:
120
144
"""
121
145
self ._arguments = arguments
122
146
"""The command line arguments passed to the application."""
147
+ self ._history = PlotHistory ()
148
+ """The plot situation history."""
123
149
super ().__init__ ()
124
150
125
151
def compose (self ) -> ComposeResult :
@@ -140,6 +166,7 @@ def on_mount(self) -> None:
140
166
if self ._arguments .colour_map is None
141
167
else get_colour_map (self ._arguments .colour_map ),
142
168
)
169
+ self ._remember ()
143
170
144
171
@on (Mandelbrot .Plotted )
145
172
def _update_situation (self , message : Mandelbrot .Plotted ) -> None :
@@ -168,13 +195,27 @@ def _update_situation(self, message: Mandelbrot.Plotted) -> None:
168
195
f"{ message .elapsed :0.4f} seconds"
169
196
)
170
197
198
+ def _remember (self ) -> None :
199
+ """Remember the current situation."""
200
+ plot = self .query_one (Mandelbrot )
201
+ self ._history .add (
202
+ Situation (
203
+ plot .x_position ,
204
+ plot .y_position ,
205
+ plot .zoom ,
206
+ plot .max_iteration ,
207
+ plot .multibrot ,
208
+ )
209
+ )
210
+
171
211
def action_zoom (self , change : float ) -> None :
172
212
"""Change the zoom value.
173
213
174
214
Args:
175
215
change: The amount to change the zoom by.
176
216
"""
177
217
self .query_one (Mandelbrot ).zoom *= change
218
+ self ._remember ()
178
219
179
220
def action_move (self , x : int , y : int ) -> None :
180
221
"""Move the plot in the indicated direction.
@@ -184,6 +225,7 @@ def action_move(self, x: int, y: int) -> None:
184
225
y: The number of pixels to move in the Y direction.
185
226
"""
186
227
self .query_one (Mandelbrot ).move (x , y )
228
+ self ._remember ()
187
229
188
230
def action_iterate (self , change : int ) -> None :
189
231
"""Change the maximum iteration.
@@ -192,6 +234,7 @@ def action_iterate(self, change: int) -> None:
192
234
change: The change to make to the maximum iterations.
193
235
"""
194
236
self .query_one (Mandelbrot ).max_iteration += change
237
+ self ._remember ()
195
238
196
239
def action_set_colour (self , colour_map : str ) -> None :
197
240
"""Set the colour map for the plot.
@@ -208,6 +251,7 @@ def action_multibrot(self, change: int) -> None:
208
251
change: The change to make to the 'multibrot' value.
209
252
"""
210
253
self .query_one (Mandelbrot ).multibrot += change
254
+ self ._remember ()
211
255
212
256
def action_goto (self , x : int , y : int ) -> None :
213
257
"""Go to a specific location.
@@ -217,10 +261,12 @@ def action_goto(self, x: int, y: int) -> None:
217
261
y: The Y location to go to.
218
262
"""
219
263
self .query_one (Mandelbrot ).goto (x , y )
264
+ self ._remember ()
220
265
221
266
def action_reset_command (self ) -> None :
222
267
"""Reset the plot to its default values."""
223
268
self .query_one (Mandelbrot ).reset ()
269
+ self ._remember ()
224
270
225
271
_VALID_LOCATION : Final [Pattern [str ]] = compile (
226
272
r"(?P<x>[^, ]+) *[, ] *(?P<y>[^, ]+)"
@@ -270,5 +316,19 @@ def action_copy_command_line_to_clipboard_command(self) -> None:
270
316
self .app .copy_to_clipboard (command )
271
317
self .notify (command , title = "Copied" )
272
318
319
+ def action_undo_command (self ) -> None :
320
+ """Undo through the history."""
321
+ if (
322
+ self ._history .backward ()
323
+ and (situation := self ._history .current_item ) is not None
324
+ ):
325
+ self .query_one (Mandelbrot ).set (
326
+ x_position = situation .x_position ,
327
+ y_position = situation .y_position ,
328
+ zoom = situation .zoom ,
329
+ max_iteration = situation .max_iteration ,
330
+ multibrot = situation .multibrot ,
331
+ ).plot ()
332
+
273
333
274
334
### main.py ends here
0 commit comments