From 7c4947df12e9c960ff53c42ed3abf2e23c8c880b Mon Sep 17 00:00:00 2001 From: Isaiah Odhner Date: Tue, 5 Sep 2023 19:24:52 -0400 Subject: [PATCH] ASCII-ize scrollbar in --ascii-only mode --- src/textual_paint/paint.py | 5 ++ src/textual_paint/scrollbars.py | 95 +++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+) create mode 100644 src/textual_paint/scrollbars.py diff --git a/src/textual_paint/paint.py b/src/textual_paint/paint.py index d80866c..7f190a0 100755 --- a/src/textual_paint/paint.py +++ b/src/textual_paint/paint.py @@ -28,6 +28,7 @@ from textual.containers import Container, Vertical, Horizontal from textual.geometry import Offset, Region, Size from textual.css._style_properties import BorderDefinition from textual.reactive import var, reactive +from textual.scrollbar import ScrollBar, ScrollBarRender from textual.strip import Strip from textual.dom import DOMNode from textual.widget import Widget @@ -51,6 +52,7 @@ from .graphics_primitives import ( ) from .menus import MenuBar, Menu, MenuItem, Separator from .windows import Window, DialogWindow, CharacterSelectorDialogWindow, MessageBox, get_warning_icon, get_question_icon, get_paint_icon +from .scrollbars import ASCIIScrollBarRender from .file_dialogs import SaveAsDialogWindow, OpenDialogWindow from .edit_colors import EditColorsDialogWindow from .localization.i18n import get as _, load_language, remove_hotkey @@ -5253,6 +5255,9 @@ if args.ascii_only: RadioButton.BUTTON_LEFT = "[" RadioButton.BUTTON_RIGHT = "]" + ScrollBar.renderer = ASCIIScrollBarRender + + # header_icon_markup = "[on white][blue]\\\\[/][red]|[/][yellow]/[/][/]" # header_icon_markup = "[black]..,[/]\n[blue]\\\\[/][on white][red]|[/][yellow]/[/][/]\n[black on rgb(192,192,192)]\\[_][/]" # trying different geometries for the page going behind the cup of brushes: diff --git a/src/textual_paint/scrollbars.py b/src/textual_paint/scrollbars.py new file mode 100644 index 0000000..03f18f5 --- /dev/null +++ b/src/textual_paint/scrollbars.py @@ -0,0 +1,95 @@ + +from math import ceil +from rich.color import Color +from rich.segment import Segment, Segments +from rich.style import Style +from textual.scrollbar import ScrollBarRender + + +class ASCIIScrollBarRender(ScrollBarRender): + """A scrollbar renderer that uses ASCII characters.""" + @classmethod + def render_bar( + cls, + size: int = 25, + virtual_size: float = 50, + window_size: float = 20, + position: float = 0, + thickness: int = 1, + vertical: bool = True, + back_color: Color = Color.parse("#555555"), + bar_color: Color = Color.parse("bright_magenta"), + ) -> Segments: + if vertical: + # bars = ["▁", "▂", "▃", "▄", "▅", "▆", "▇", " "] + # bars = ["_", "=", "#", " "] + bars = [" "] + else: + # bars = ["▉", "▊", "▋", "▌", "▍", "▎", "▏", " "] + # bars = ["#", " "] + bars = [" "] + + back = back_color + bar = bar_color + + len_bars = len(bars) + + width_thickness = thickness if vertical else 1 + + _Segment = Segment + _Style = Style + blank = " " * width_thickness + + foreground_meta = {"@mouse.up": "release", "@mouse.down": "grab"} + if window_size and size and virtual_size and size != virtual_size: + bar_ratio = virtual_size / size + thumb_size = max(1, window_size / bar_ratio) + + position_ratio = position / (virtual_size - window_size) + position = (size - thumb_size) * position_ratio + + start = int(position * len_bars) + end = start + ceil(thumb_size * len_bars) + + start_index, start_bar = divmod(max(0, start), len_bars) + end_index, end_bar = divmod(max(0, end), len_bars) + + upper = {"@mouse.up": "scroll_up"} + lower = {"@mouse.up": "scroll_down"} + + upper_back_segment = Segment(blank, _Style(bgcolor=back, meta=upper)) + lower_back_segment = Segment(blank, _Style(bgcolor=back, meta=lower)) + + segments = [upper_back_segment] * int(size) + segments[end_index:] = [lower_back_segment] * (size - end_index) + + segments[start_index:end_index] = [ + _Segment(blank, _Style(bgcolor=bar, meta=foreground_meta)) + ] * (end_index - start_index) + + # Apply the smaller bar characters to head and tail of scrollbar for more "granularity" + if start_index < len(segments): + bar_character = bars[len_bars - 1 - start_bar] + if bar_character != " ": + segments[start_index] = _Segment( + bar_character * width_thickness, + _Style(bgcolor=back, color=bar, meta=foreground_meta) + if vertical + else _Style(bgcolor=bar, color=back, meta=foreground_meta), + ) + if end_index < len(segments): + bar_character = bars[len_bars - 1 - end_bar] + if bar_character != " ": + segments[end_index] = _Segment( + bar_character * width_thickness, + _Style(bgcolor=bar, color=back, meta=foreground_meta) + if vertical + else _Style(bgcolor=back, color=bar, meta=foreground_meta), + ) + else: + style = _Style(bgcolor=back) + segments = [_Segment(blank, style=style)] * int(size) + if vertical: + return Segments(segments, new_lines=True) + else: + return Segments((segments + [_Segment.line()]) * thickness, new_lines=False)