From 7f7abf008a5f571b4be89581e0f538c74681310d Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Thu, 24 Apr 2025 09:19:33 +0100 Subject: [PATCH 1/3] :hammer: Simplify the undo facility --- src/complexitty/screens/main.py | 41 ++++++++++++++++----------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/src/complexitty/screens/main.py b/src/complexitty/screens/main.py index 9d72a87..2008147 100644 --- a/src/complexitty/screens/main.py +++ b/src/complexitty/screens/main.py @@ -3,6 +3,7 @@ ############################################################################## # Python imports. from argparse import Namespace +from collections import deque from math import floor, log10 from re import Pattern, compile from typing import Final, NamedTuple, TypeAlias @@ -18,7 +19,6 @@ from textual_enhanced.commands import ChangeTheme, Command, Help from textual_enhanced.dialogs import ModalInput from textual_enhanced.screen import EnhancedScreen -from textual_enhanced.tools import History ############################################################################## # Local imports. @@ -77,7 +77,7 @@ class Situation(NamedTuple): ############################################################################## -PlotHistory: TypeAlias = History[Situation] +PlotHistory: TypeAlias = deque[Situation] """Type of the plot history.""" @@ -144,7 +144,7 @@ def __init__(self, arguments: Namespace) -> None: """ self._arguments = arguments """The command line arguments passed to the application.""" - self._history = PlotHistory() + self._history = PlotHistory(maxlen=128) """The plot situation history.""" super().__init__() @@ -166,7 +166,6 @@ def on_mount(self) -> None: if self._arguments.colour_map is None else get_colour_map(self._arguments.colour_map), ) - self._remember() @on(Mandelbrot.Plotted) def _update_situation(self, message: Mandelbrot.Plotted) -> None: @@ -198,7 +197,7 @@ def _update_situation(self, message: Mandelbrot.Plotted) -> None: def _remember(self) -> None: """Remember the current situation.""" plot = self.query_one(Mandelbrot) - self._history.add( + self._history.append( Situation( plot.x_position, plot.y_position, @@ -214,8 +213,8 @@ def action_zoom(self, change: float) -> None: Args: change: The amount to change the zoom by. """ - self.query_one(Mandelbrot).zoom *= change self._remember() + self.query_one(Mandelbrot).zoom *= change def action_move(self, x: int, y: int) -> None: """Move the plot in the indicated direction. @@ -224,8 +223,8 @@ def action_move(self, x: int, y: int) -> None: x: The number of pixels to move in the X direction. y: The number of pixels to move in the Y direction. """ - self.query_one(Mandelbrot).move(x, y) self._remember() + self.query_one(Mandelbrot).move(x, y) def action_iterate(self, change: int) -> None: """Change the maximum iteration. @@ -233,8 +232,8 @@ def action_iterate(self, change: int) -> None: Args: change: The change to make to the maximum iterations. """ - self.query_one(Mandelbrot).max_iteration += change self._remember() + self.query_one(Mandelbrot).max_iteration += change def action_set_colour(self, colour_map: str) -> None: """Set the colour map for the plot. @@ -250,8 +249,8 @@ def action_multibrot(self, change: int) -> None: Args: change: The change to make to the 'multibrot' value. """ - self.query_one(Mandelbrot).multibrot += change self._remember() + self.query_one(Mandelbrot).multibrot += change def action_goto(self, x: int, y: int) -> None: """Go to a specific location. @@ -265,8 +264,8 @@ def action_goto(self, x: int, y: int) -> None: def action_reset_command(self) -> None: """Reset the plot to its default values.""" - self.query_one(Mandelbrot).reset() self._remember() + self.query_one(Mandelbrot).reset() _VALID_LOCATION: Final[Pattern[str]] = compile( r"(?P[^, ]+) *[, ] *(?P[^, ]+)" @@ -318,17 +317,17 @@ def action_copy_command_line_to_clipboard_command(self) -> None: def action_undo_command(self) -> None: """Undo through the history.""" - if ( - self._history.backward() - and (situation := self._history.current_item) is not None - ): - self.query_one(Mandelbrot).set( - x_position=situation.x_position, - y_position=situation.y_position, - zoom=situation.zoom, - max_iteration=situation.max_iteration, - multibrot=situation.multibrot, - ).plot() + try: + situation = self._history.pop() + except IndexError: + return + self.query_one(Mandelbrot).set( + x_position=situation.x_position, + y_position=situation.y_position, + zoom=situation.zoom, + max_iteration=situation.max_iteration, + multibrot=situation.multibrot, + ).plot() ### main.py ends here From b1fcb4c2446d28f541d17bac04ae2e89656709c3 Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Thu, 24 Apr 2025 09:23:08 +0100 Subject: [PATCH 2/3] :lipstick: Show Undo in the footer Also rearrange some things in the footer. --- src/complexitty/commands/main.py | 1 + src/complexitty/screens/main.py | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/complexitty/commands/main.py b/src/complexitty/commands/main.py index abe490b..52010ee 100644 --- a/src/complexitty/commands/main.py +++ b/src/complexitty/commands/main.py @@ -26,6 +26,7 @@ class Undo(Command): """Undo the latest change""" BINDING_KEY = "backspace" + SHOW_IN_FOOTER = True ### main.py ends here diff --git a/src/complexitty/screens/main.py b/src/complexitty/screens/main.py index 2008147..1cc0750 100644 --- a/src/complexitty/screens/main.py +++ b/src/complexitty/screens/main.py @@ -96,25 +96,28 @@ class Main(EnhancedScreen[None]): # Keep these together as they're bound to function keys and destined # for the footer. Help, - ChangeTheme, + MoveLeft, + MoveRight, + MoveUp, + MoveDown, + ZoomIn, + ZoomOut, + IncreaseMaximumIteration, + DecreaseMaximumIteration, + Undo, Quit, # Everything else. + ChangeTheme, CopyCommandLineToClipboard, - DecreaseMaximumIteration, DecreaseMultibrot, GoMiddle, GoTo, GreatlyDecreaseMaximumIteration, GreatlyIncreaseMaximumIteration, - IncreaseMaximumIteration, IncreaseMultibrot, - MoveDown, MoveDownSlowly, - MoveLeft, MoveLeftSlowly, - MoveRight, MoveRightSlowly, - MoveUp, MoveUpSlowly, Reset, SetColourToBluesAndBrowns, @@ -124,11 +127,8 @@ class Main(EnhancedScreen[None]): SetColourToShadesOfBlue, SetColourToShadesOfGreen, SetColourToShadesOfRed, - Undo, ZeroZero, - ZoomIn, ZoomInFaster, - ZoomOut, ZoomOutFaster, ) From a2184ce4c78737dcd9bde3057f6116fcf258b972 Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Thu, 24 Apr 2025 09:29:41 +0100 Subject: [PATCH 3/3] :lipstick: Only make Undo available if there are things to undo --- src/complexitty/providers/main.py | 2 +- src/complexitty/screens/main.py | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/complexitty/providers/main.py b/src/complexitty/providers/main.py index 3f45082..b681852 100644 --- a/src/complexitty/providers/main.py +++ b/src/complexitty/providers/main.py @@ -85,7 +85,7 @@ def commands(self) -> CommandHits: yield SetColourToShadesOfBlue() yield SetColourToShadesOfGreen() yield SetColourToShadesOfRed() - yield Undo() + yield from self.maybe(Undo) yield ZeroZero() yield ZoomIn() yield ZoomInFaster() diff --git a/src/complexitty/screens/main.py b/src/complexitty/screens/main.py index 1cc0750..ae3f99e 100644 --- a/src/complexitty/screens/main.py +++ b/src/complexitty/screens/main.py @@ -167,6 +167,27 @@ def on_mount(self) -> None: else get_colour_map(self._arguments.colour_map), ) + def check_action(self, action: str, parameters: tuple[object, ...]) -> bool | None: + """Check if an action is possible to perform right now. + + Args: + action: The action to perform. + parameters: The parameters of the action. + + Returns: + `True` if it can perform, `False` or `None` if not. + """ + if not self.is_mounted: + # Surprisingly it seems that Textual's "dynamic bindings" can + # cause this method to be called before the DOM is up and + # running. This breaks the rule of least astonishment, I'd say, + # but okay let's be defensive... (when I can come up with a nice + # little MRE I'll report it). + return True + if action == Undo.action_name(): + return bool(self._history) or None + return True + @on(Mandelbrot.Plotted) def _update_situation(self, message: Mandelbrot.Plotted) -> None: """Update the current situation after the latest plot. @@ -206,6 +227,7 @@ def _remember(self) -> None: plot.multibrot, ) ) + self.refresh_bindings() def action_zoom(self, change: float) -> None: """Change the zoom value. @@ -321,6 +343,7 @@ def action_undo_command(self) -> None: situation = self._history.pop() except IndexError: return + self.refresh_bindings() self.query_one(Mandelbrot).set( x_position=situation.x_position, y_position=situation.y_position,