mirror of
https://github.com/1j01/textual-paint.git
synced 2024-10-26 17:36:53 +03:00
Decouple ColorsBox from PaintApp
There's no practical utility in this unless I want to make the palette size variable, and this makes performance worse updating the screen when the palette changes, but I'm on a mission to remove `from textual_paint.paint import PaintApp` since it can't be at top level due to cyclic imports, and it has side effects when importing `textual_paint.args` in turn.
This commit is contained in:
parent
a2cf93ae99
commit
0647e7be8b
@ -5,12 +5,19 @@ from textual import events
|
||||
from textual.app import ComposeResult
|
||||
from textual.containers import Container
|
||||
from textual.message import Message
|
||||
from textual.reactive import var
|
||||
from textual.widgets import Button
|
||||
from textual_paint.char_input import DOUBLE_CLICK_TIME, CharInput
|
||||
|
||||
class ColorsBox(Container):
|
||||
"""Color palette widget."""
|
||||
|
||||
palette: var[tuple[str, ...]] = var(tuple())
|
||||
"""A tuple of colors to display.
|
||||
|
||||
A tuple is used because list mutations can't be watched.
|
||||
"""
|
||||
|
||||
class ColorSelected(Message):
|
||||
"""Message sent when a color is selected."""
|
||||
def __init__(self, color: str, as_foreground: bool) -> None:
|
||||
@ -18,14 +25,15 @@ class ColorsBox(Container):
|
||||
self.as_foreground = as_foreground
|
||||
super().__init__()
|
||||
|
||||
class EditColor(Message):
|
||||
"""Message sent when a color is selected."""
|
||||
def __init__(self, color_index: int, as_foreground: bool) -> None:
|
||||
self.color_index = color_index
|
||||
self.as_foreground = as_foreground
|
||||
super().__init__()
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
"""Add our selected color and color well buttons."""
|
||||
if TYPE_CHECKING:
|
||||
from textual_paint.paint import PaintApp
|
||||
assert isinstance(self.app, PaintApp)
|
||||
# TODO: decouple from PaintApp
|
||||
# Could accept palette in constructor
|
||||
palette = self.app.palette
|
||||
|
||||
self.color_by_button: dict[Button, str] = {}
|
||||
with Container(id="palette_selection_box"):
|
||||
@ -33,27 +41,21 @@ class ColorsBox(Container):
|
||||
# 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
|
||||
yield Container(id="available_colors")
|
||||
|
||||
def update_palette(self) -> None: # , palette: list[str]) -> None:
|
||||
"""Update the palette with new colors."""
|
||||
if TYPE_CHECKING:
|
||||
from textual_paint.paint import PaintApp
|
||||
assert isinstance(self.app, PaintApp)
|
||||
# TODO: decouple from PaintApp
|
||||
# Could accept palette as argument
|
||||
palette = self.app.palette
|
||||
|
||||
for button, color in zip(self.query(".color_button").nodes, palette):
|
||||
assert isinstance(button, Button)
|
||||
def watch_palette(self, palette: tuple[str, ...]) -> None:
|
||||
"""Called when the palette is changed."""
|
||||
# TODO: optimize; don't remove or add buttons unless the palette size changes
|
||||
# Side note: the palette size never changes in the current implementation.
|
||||
self.query(".color_button").remove()
|
||||
self.color_by_button.clear()
|
||||
container = self.query_one("#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
|
||||
container.mount(button)
|
||||
|
||||
last_click_time = 0
|
||||
last_click_button: Button | None = None
|
||||
@ -68,10 +70,6 @@ class ColorsBox(Container):
|
||||
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.post_message(self.EditColor(self.query(".color_button").nodes.index(button), secondary))
|
||||
self.last_click_time = event.time
|
||||
self.last_click_button = button
|
@ -281,7 +281,7 @@ class PaintApp(App[None]):
|
||||
|
||||
def watch_palette(self, palette: tuple[str, ...]) -> None:
|
||||
"""Called when palette changes."""
|
||||
self.query_one("ColorsBox", ColorsBox).update_palette()
|
||||
self.query_one("ColorsBox", ColorsBox).palette = palette
|
||||
|
||||
def watch_selected_bg_color(self, selected_bg_color: str) -> None:
|
||||
"""Called when selected_bg_color changes."""
|
||||
@ -1280,6 +1280,10 @@ class PaintApp(App[None]):
|
||||
"""Swap the foreground and background colors."""
|
||||
self.selected_bg_color, self.selected_fg_color = self.selected_fg_color, self.selected_bg_color
|
||||
|
||||
def on_colors_box_edit_color(self, event: ColorsBox.EditColor) -> None:
|
||||
"""Called when a color is double-clicked in the palette."""
|
||||
self.action_edit_colors(color_palette_index=event.color_index, as_foreground=event.as_foreground)
|
||||
|
||||
def action_edit_colors(self, color_palette_index: int|None = None, as_foreground: bool = False) -> None:
|
||||
"""Show dialog to edit colors."""
|
||||
self.close_windows("#edit_colors_dialog")
|
||||
|
Loading…
Reference in New Issue
Block a user