From 3922d1e3d0f14894a96bf3b6fbf09a129c15ddbd Mon Sep 17 00:00:00 2001 From: Isaiah Odhner Date: Wed, 13 Sep 2023 22:36:32 -0400 Subject: [PATCH] Move ColorsBox to a new file --- src/textual_paint/char_input.py | 5 ++- src/textual_paint/colors_box.py | 65 +++++++++++++++++++++++++++++++++ src/textual_paint/paint.py | 56 +--------------------------- 3 files changed, 70 insertions(+), 56 deletions(-) create mode 100644 src/textual_paint/colors_box.py diff --git a/src/textual_paint/char_input.py b/src/textual_paint/char_input.py index 31ad4fb..4d5c405 100644 --- a/src/textual_paint/char_input.py +++ b/src/textual_paint/char_input.py @@ -11,7 +11,7 @@ from textual.message import Message from textual.strip import Strip from textual.widgets import Input -DOUBLE_CLICK_TIME = 0.8 # seconds; overridden in tests to avoid flakiness +DOUBLE_CLICK_TIME = 0.8 # seconds class CharInput(Input, inherit_bindings=False): """Widget for entering a single character.""" @@ -59,6 +59,7 @@ class CharInput(Input, inherit_bindings=False): if TYPE_CHECKING: from textual_paint.paint import PaintApp assert isinstance(self.app, PaintApp) + # TODO: decouple from PaintApp self.app.on_paste(event) def validate_cursor_position(self, cursor_position: int) -> int: @@ -74,6 +75,7 @@ class CharInput(Input, inherit_bindings=False): if TYPE_CHECKING: from textual_paint.paint import PaintApp assert isinstance(self.app, PaintApp) + # TODO: decouple from PaintApp # Textural style, repeating the character: # This doesn't support a blinking cursor, and it can't extend all the way # to the edges, even when removing padding, due to the border, which takes up a cell on each side. @@ -91,6 +93,7 @@ class CharInput(Input, inherit_bindings=False): if TYPE_CHECKING: from textual_paint.paint import PaintApp assert isinstance(self.app, PaintApp) + # TODO: decouple from PaintApp if event.ctrl or event.button == 3: # right click self.app.action_swap_colors() return diff --git a/src/textual_paint/colors_box.py b/src/textual_paint/colors_box.py new file mode 100644 index 0000000..864e7ef --- /dev/null +++ b/src/textual_paint/colors_box.py @@ -0,0 +1,65 @@ +"""The ColorsBox widget for selecting colors.""" + +from typing import TYPE_CHECKING +from textual import events +from textual.app import ComposeResult +from textual.containers import Container +from textual.message import Message +from textual.widgets import Button +from textual_paint.char_input import DOUBLE_CLICK_TIME, CharInput + +class ColorsBox(Container): + """Color palette widget.""" + + class ColorSelected(Message): + """Message sent when a color is selected.""" + def __init__(self, color: str, as_foreground: bool) -> None: + self.color = color + self.as_foreground = as_foreground + super().__init__() + + def compose(self) -> ComposeResult: + """Add our selected color and color well buttons.""" + self.color_by_button: dict[Button, str] = {} + with Container(id="palette_selection_box"): + # This widget is doing double duty, showing the current color + # and showing/editing the current character. + # I haven't settled on naming for this yet. + yield CharInput(id="selected_color_char_input", classes="color_well") + with Container(id="available_colors"): + from textual_paint.paint import palette # TODO: restructure data flow + for color in palette: + button = Button("", classes="color_button color_well") + button.styles.background = color + button.can_focus = False + self.color_by_button[button] = color + yield button + + def update_palette(self) -> None: # , palette: list[str]) -> None: + """Update the palette with new colors.""" + from textual_paint.paint import palette # TODO: restructure data flow + for button, color in zip(self.query(".color_button").nodes, palette): + assert isinstance(button, Button) + button.styles.background = color + self.color_by_button[button] = color + + last_click_time = 0 + last_click_button: Button | None = None + # def on_button_pressed(self, event: Button.Pressed) -> None: + # """Called when a button is clicked.""" + def on_mouse_down(self, event: events.MouseDown) -> None: + """Called when a mouse button is pressed.""" + button, _ = self.app.get_widget_at(*event.screen_offset) + if "color_button" in button.classes: + assert isinstance(button, Button) + secondary = event.ctrl or event.button == 3 + self.post_message(self.ColorSelected(self.color_by_button[button], secondary)) + # Detect double click and open Edit Colors dialog. + if event.time - self.last_click_time < DOUBLE_CLICK_TIME and button == self.last_click_button: + if TYPE_CHECKING: + from textual_paint.paint import PaintApp + assert isinstance(self.app, PaintApp) + # TODO: decouple from PaintApp + self.app.action_edit_colors(self.query(".color_button").nodes.index(button), secondary) + self.last_click_time = event.time + self.last_click_button = button \ No newline at end of file diff --git a/src/textual_paint/paint.py b/src/textual_paint/paint.py index 21e8500..c437de0 100755 --- a/src/textual_paint/paint.py +++ b/src/textual_paint/paint.py @@ -23,7 +23,6 @@ from textual.containers import Container, Horizontal, Vertical from textual.css._style_properties import BorderDefinition from textual.dom import DOMNode from textual.geometry import Offset, Region, Size -from textual.message import Message from textual.reactive import var from textual.widget import Widget from textual.widgets import (Button, Header, Input, RadioButton, RadioSet, @@ -44,6 +43,7 @@ from textual_paint.auto_restart import restart_on_changes, restart_program from textual_paint.canvas import Canvas from textual_paint.char_input import CharInput from textual_paint.character_picker import CharacterSelectorDialogWindow +from textual_paint.colors_box import ColorsBox from textual_paint.edit_colors import EditColorsDialogWindow from textual_paint.file_dialogs import OpenDialogWindow, SaveAsDialogWindow from textual_paint.graphics_primitives import (bezier_curve_walk, @@ -66,8 +66,6 @@ from textual_paint.windows import DialogWindow, MessageBox, Window MAX_FILE_SIZE = 500000 # 500 KB -DOUBLE_CLICK_TIME = 0.8 # seconds; overridden in tests to avoid flakiness - # Most arguments are handled at the end of the file, # but it may be important to do this one early. load_language(args.language) @@ -75,58 +73,6 @@ load_language(args.language) palette = list(DEFAULT_PALETTE) -class ColorsBox(Container): - """Color palette widget.""" - - class ColorSelected(Message): - """Message sent when a color is selected.""" - def __init__(self, color: str, as_foreground: bool) -> None: - self.color = color - self.as_foreground = as_foreground - super().__init__() - - def compose(self) -> ComposeResult: - """Add our selected color and color well buttons.""" - self.color_by_button: dict[Button, str] = {} - with Container(id="palette_selection_box"): - # This widget is doing double duty, showing the current color - # and showing/editing the current character. - # I haven't settled on naming for this yet. - yield CharInput(id="selected_color_char_input", classes="color_well") - with Container(id="available_colors"): - for color in palette: - button = Button("", classes="color_button color_well") - button.styles.background = color - button.can_focus = False - self.color_by_button[button] = color - yield button - - def update_palette(self) -> None: # , palette: list[str]) -> None: - """Update the palette with new colors.""" - for button, color in zip(self.query(".color_button").nodes, palette): - assert isinstance(button, Button) - button.styles.background = color - self.color_by_button[button] = color - - last_click_time = 0 - last_click_button: Button | None = None - # def on_button_pressed(self, event: Button.Pressed) -> None: - # """Called when a button is clicked.""" - def on_mouse_down(self, event: events.MouseDown) -> None: - """Called when a mouse button is pressed.""" - button, _ = self.app.get_widget_at(*event.screen_offset) - if "color_button" in button.classes: - assert isinstance(button, Button) - secondary = event.ctrl or event.button == 3 - self.post_message(self.ColorSelected(self.color_by_button[button], secondary)) - # Detect double click and open Edit Colors dialog. - if event.time - self.last_click_time < DOUBLE_CLICK_TIME and button == self.last_click_button: - assert isinstance(self.app, PaintApp) - self.app.action_edit_colors(self.query(".color_button").nodes.index(button), secondary) - self.last_click_time = event.time - self.last_click_button = button - - def offset_to_text_index(textbox: Selection, offset: Offset) -> int: """Converts an offset in the textbox to an index in the text.""" assert textbox.textbox_mode, "offset_to_text_index called on non-textbox selection"