diff --git a/CHANGELOG.md b/CHANGELOG.md index 462d3650d4..9d6daa61ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). -## Unreleased +## [0.86.0] ### Fixed @@ -30,6 +30,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Added `textual.theme.ThemeProvider`, a command palette provider which returns all registered themes https://github.com/Textualize/textual/pull/5087 - Added several new built-in CSS variables https://github.com/Textualize/textual/pull/5087 - Added support for in-band terminal resize protocol https://github.com/Textualize/textual/pull/5217 +- Added TEXTUAL_THEME environment var, which should be a comma separated list of desired themes https://github.com/Textualize/textual/pull/5238 +- Added `Widget.is_scrolling` https://github.com/Textualize/textual/pull/5238 +- Added `Tree.add_json` https://github.com/Textualize/textual/pull/5238 ### Changed @@ -38,12 +41,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Added `can_focus` and `can_focus_children` parameters to scrollable container types. https://github.com/Textualize/textual/pull/5226 - Added `textual.lazy.Reveal` https://github.com/Textualize/textual/pull/5226 - Added `Screen.action_blur` https://github.com/Textualize/textual/pull/5226 - -### Changed - +- `Click` events can now be used with the on decorator to match the originally clicked widget https://github.com/Textualize/textual/pull/5238 - Breaking change: Removed `App.dark` reactive attribute https://github.com/Textualize/textual/pull/5087 - Breaking change: To improve consistency, several changes have been made to default widget CSS and the CSS variables which ship with Textual. On upgrading, your app will likely look different. All of these changes can be overidden with your own CSS. https://github.com/Textualize/textual/pull/5087 +### Removed + +- Removed `App.HOVER_EFFECTS_SCROLL_PAUSE` https://github.com/Textualize/textual/pull/5238 + ## [0.85.2] - 2024-11-02 - Fixed broken focus-within https://github.com/Textualize/textual/pull/5190 @@ -2532,6 +2537,7 @@ https://textual.textualize.io/blog/2022/11/08/version-040/#version-040 - New handler system for messages that doesn't require inheritance - Improved traceback handling +[0.86.0]: https://github.com/Textualize/textual/compare/v0.85.2...v0.86.0 [0.85.2]: https://github.com/Textualize/textual/compare/v0.85.1...v0.85.2 [0.85.1]: https://github.com/Textualize/textual/compare/v0.85.0...v0.85.1 [0.85.0]: https://github.com/Textualize/textual/compare/v0.84.0...v0.85.0 diff --git a/pyproject.toml b/pyproject.toml index 57a2f605e7..daec8095f8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "textual" -version = "0.85.2" +version = "0.86.0" homepage = "https://github.com/Textualize/textual" repository = "https://github.com/Textualize/textual" documentation = "https://textual.textualize.io/" diff --git a/src/textual/_xterm_parser.py b/src/textual/_xterm_parser.py index ddb935f505..6382413c77 100644 --- a/src/textual/_xterm_parser.py +++ b/src/textual/_xterm_parser.py @@ -93,6 +93,7 @@ def parse_mouse_code(self, code: str) -> Message | None: event_class = events.MouseDown if state == "M" else events.MouseUp event = event_class( + None, x, y, delta_x, diff --git a/src/textual/app.py b/src/textual/app.py index 55225474b2..1ac01a85f4 100644 --- a/src/textual/app.py +++ b/src/textual/app.py @@ -462,9 +462,6 @@ class MyApp(App[None]): SUSPENDED_SCREEN_CLASS: ClassVar[str] = "" """Class to apply to suspended screens, or empty string for no class.""" - HOVER_EFFECTS_SCROLL_PAUSE: ClassVar[float] = 0.2 - """Seconds to pause hover effects for when scrolling.""" - _PSEUDO_CLASSES: ClassVar[dict[str, Callable[[App[Any]], bool]]] = { "focus": lambda app: app.app_focus, "blur": lambda app: not app.app_focus, @@ -487,7 +484,7 @@ class MyApp(App[None]): get focus when the terminal widget has focus. """ - theme: Reactive[str] = Reactive("textual-dark") + theme: Reactive[str] = Reactive(constants.DEFAULT_THEME) """The name of the currently active theme.""" ansi_theme_dark = Reactive(MONOKAI, init=False) @@ -760,9 +757,6 @@ def __init__( self._previous_inline_height: int | None = None """Size of previous inline update.""" - self._paused_hover_effects: bool = False - """Have the hover effects been paused?""" - self._hover_effects_timer: Timer | None = None self._resize_event: events.Resize | None = None @@ -1195,12 +1189,17 @@ def get_theme(self, theme_name: str) -> Theme | None: """Get a theme by name. Args: - theme_name: The name of the theme to get. + theme_name: The name of the theme to get. May also be a comma + separated list of names, to pick the first available theme. Returns: A Theme instance and None if the theme doesn't exist. """ - return self.available_themes[theme_name] + theme_names = [token.strip() for token in theme_name.split(",")] + for theme_name in theme_names: + if theme_name in self.available_themes: + return self.available_themes[theme_name] + return None def register_theme(self, theme: Theme) -> None: """Register a theme with the app. @@ -1236,6 +1235,8 @@ def available_themes(self) -> dict[str, Theme]: @property def current_theme(self) -> Theme: theme = self.get_theme(self.theme) + if theme is None: + theme = self.get_theme("textual-dark") assert theme is not None # validated by _validate_theme return theme @@ -2817,43 +2818,12 @@ def set_focus(self, widget: Widget | None, scroll_visible: bool = True) -> None: """ self.screen.set_focus(widget, scroll_visible) - def _pause_hover_effects(self): - """Pause any hover effects based on Enter and Leave events for 200ms.""" - if not self.HOVER_EFFECTS_SCROLL_PAUSE or self.is_headless: - return - self._paused_hover_effects = True - if self._hover_effects_timer is None: - self._hover_effects_timer = self.set_interval( - self.HOVER_EFFECTS_SCROLL_PAUSE, self._resume_hover_effects - ) - else: - self._hover_effects_timer.reset() - self._hover_effects_timer.resume() - - def _resume_hover_effects(self): - """Resume sending Enter and Leave for hover effects.""" - if not self.HOVER_EFFECTS_SCROLL_PAUSE or self.is_headless: - return - if self._paused_hover_effects: - self._paused_hover_effects = False - if self._hover_effects_timer is not None: - self._hover_effects_timer.pause() - try: - widget, _ = self.screen.get_widget_at(*self.mouse_position) - except NoWidget: - pass - else: - if widget is not self.mouse_over: - self._set_mouse_over(widget) - def _set_mouse_over(self, widget: Widget | None) -> None: """Called when the mouse is over another widget. Args: widget: Widget under mouse, or None for no widgets. """ - if self._paused_hover_effects: - return if widget is None: if self.mouse_over is not None: try: @@ -3712,7 +3682,9 @@ async def on_event(self, event: events.Event) -> None: self.get_widget_at(event.x, event.y)[0] is self._mouse_down_widget ): - click_event = events.Click.from_event(event) + click_event = events.Click.from_event( + self._mouse_down_widget, event + ) self.screen._forward_event(click_event) except NoWidget: pass diff --git a/src/textual/command.py b/src/textual/command.py index 9e2441d21d..68677ba001 100644 --- a/src/textual/command.py +++ b/src/textual/command.py @@ -555,7 +555,7 @@ class CommandPalette(SystemModalScreen[None]): CommandPalette > .command-palette--help-text { color: $text-muted; background: transparent; - text-style: not bold dim; + text-style: not bold; } CommandPalette > .command-palette--highlight { @@ -571,6 +571,7 @@ class CommandPalette(SystemModalScreen[None]): height: 100%; visibility: hidden; background: $surface; + &:dark { background: $panel-darken-1; } } CommandPalette #--input { diff --git a/src/textual/constants.py b/src/textual/constants.py index f4d1673888..7cc88938fb 100644 --- a/src/textual/constants.py +++ b/src/textual/constants.py @@ -152,3 +152,8 @@ def _get_textual_animations() -> AnimationLevel: """The time threshold (in milliseconds) after which a warning is logged if message processing exceeds this duration. """ + +DEFAULT_THEME: Final[str] = get_environ("TEXTUAL_THEME", "textual-dark") +"""Textual theme to make default. More than one theme may be specified in a comma separated list. +Textual will use the first theme that exists. +""" diff --git a/src/textual/containers.py b/src/textual/containers.py index aebd3273c5..18a46d1455 100644 --- a/src/textual/containers.py +++ b/src/textual/containers.py @@ -82,6 +82,7 @@ def __init__( disabled: bool = False, can_focus: bool | None = None, can_focus_children: bool | None = None, + can_maximize: bool | None = None, ) -> None: """ @@ -93,6 +94,7 @@ def __init__( disabled: Whether the widget is disabled or not. can_focus: Can this container be focused? can_focus_children: Can this container's children be focused? + can_maximized: Allow this container to maximize? `None` to use default logic., """ super().__init__( @@ -106,6 +108,13 @@ def __init__( self.can_focus = can_focus if can_focus_children is not None: self.can_focus_children = can_focus_children + self.can_maximize = can_maximize + + @property + def allow_maximize(self) -> bool: + if self.can_maximize is None: + return super().allow_maximize + return self.can_maximize class Vertical(Widget, inherit_bindings=False): diff --git a/src/textual/css/stylesheet.py b/src/textual/css/stylesheet.py index dd321a2fa4..fe3e3fac6e 100644 --- a/src/textual/css/stylesheet.py +++ b/src/textual/css/stylesheet.py @@ -482,7 +482,10 @@ def apply( node._has_hover_style = "hover" in all_pseudo_classes node._has_focus_within = "focus-within" in all_pseudo_classes node._has_order_style = not all_pseudo_classes.isdisjoint( - {"first-of-type", "last-of-type", "odd", "even"} + {"first-of-type", "last-of-type"} + ) + node._has_odd_or_even = ( + "odd" in all_pseudo_classes or "even" in all_pseudo_classes ) cache_key: tuple | None = None diff --git a/src/textual/demo/data.py b/src/textual/demo/data.py index 6023ec83e2..01ca66e58d 100644 --- a/src/textual/demo/data.py +++ b/src/textual/demo/data.py @@ -1,3 +1,5 @@ +import json + COUNTRIES = [ "Afghanistan", "Albania", @@ -196,3 +198,261 @@ "Zambia", "Zimbabwe", ] +# Sort by length for auto-complete +COUNTRIES.sort(key=str.__len__) + +# Thanks, Claude +MOVIES = """\ +Date,Title,Genre,Director,Box Office (millions),Rating,Runtime (min) +1980-01-18,The Fog,Horror,John Carpenter,21,R,89 +1980-02-15,Coal Miner's Daughter,Biography,Michael Apted,67,PG,124 +1980-03-07,Little Miss Marker,Comedy,Walter Bernstein,12,PG,103 +1980-04-11,The Long Riders,Western,Walter Hill,15,R,100 +1980-05-21,The Empire Strikes Back,Sci-Fi,Irvin Kershner,538,PG,124 +1980-06-13,The Blues Brothers,Comedy,John Landis,115,R,133 +1980-07-02,Airplane!,Comedy,Jim Abrahams,83,PG,88 +1980-08-01,Caddyshack,Comedy,Harold Ramis,39,R,98 +1980-09-19,The Big Red One,War,Samuel Fuller,24,PG,113 +1980-10-10,Private Benjamin,Comedy,Howard Zieff,69,R,109 +1980-11-07,The Stunt Man,Action,Richard Rush,7,R,131 +1980-12-19,Nine to Five,Comedy,Colin Higgins,103,PG,109 +1981-01-23,Scanners,Horror,David Cronenberg,14,R,103 +1981-02-20,The Final Conflict,Horror,Graham Baker,20,R,108 +1981-03-20,Raiders of the Lost Ark,Action,Steven Spielberg,389,PG,115 +1981-04-10,Excalibur,Fantasy,John Boorman,35,R,140 +1981-05-22,Outland,Sci-Fi,Peter Hyams,17,R,109 +1981-06-19,Superman II,Action,Richard Lester,108,PG,127 +1981-07-17,Escape from New York,Sci-Fi,John Carpenter,25,R,99 +1981-08-07,An American Werewolf in London,Horror,John Landis,30,R,97 +1981-09-25,Continental Divide,Romance,Michael Apted,15,PG,103 +1981-10-16,True Confessions,Drama,Ulu Grosbard,12,R,108 +1981-11-20,Time Bandits,Fantasy,Terry Gilliam,42,PG,116 +1981-12-04,Rollover,Drama,Alan J. Pakula,11,R,116 +1982-01-15,The Beast Within,Horror,Philippe Mora,7,R,98 +1982-02-12,Quest for Fire,Adventure,Jean-Jacques Annaud,20,R,100 +1982-03-19,Porky's,Comedy,Bob Clark,105,R,94 +1982-04-16,The Sword and the Sorcerer,Fantasy,Albert Pyun,39,R,99 +1982-05-14,Conan the Barbarian,Fantasy,John Milius,68,R,129 +1982-06-04,Star Trek II: The Wrath of Khan,Sci-Fi,Nicholas Meyer,97,PG,113 +1982-06-11,E.T. the Extra-Terrestrial,Sci-Fi,Steven Spielberg,792,PG,115 +1982-06-25,Blade Runner,Sci-Fi,Ridley Scott,33,R,117 +1982-07-16,The World According to Garp,Comedy-Drama,George Roy Hill,29,R,136 +1982-08-13,Fast Times at Ridgemont High,Comedy,Amy Heckerling,27,R,90 +1982-09-17,The Challenge,Action,John Frankenheimer,9,R,108 +1982-10-22,First Blood,Action,Ted Kotcheff,47,R,93 +1982-11-12,The Man from Snowy River,Western,George Miller,20,PG,102 +1982-12-08,48 Hrs.,Action,Walter Hill,79,R,96 +1983-01-21,The Entity,Horror,Sidney J. Furie,13,R,125 +1983-02-18,The Year of Living Dangerously,Drama,Peter Weir,10,PG,115 +1983-03-25,The Outsiders,Drama,Francis Ford Coppola,25,PG,91 +1983-04-22,Something Wicked This Way Comes,Horror,Jack Clayton,5,PG,95 +1983-05-25,Return of the Jedi,Sci-Fi,Richard Marquand,475,PG,131 +1983-06-17,Superman III,Action,Richard Lester,60,PG,125 +1983-07-15,Class,Comedy,Lewis John Carlino,21,R,98 +1983-08-19,Curse of the Pink Panther,Comedy,Blake Edwards,9,PG,109 +1983-09-23,The Big Chill,Drama,Lawrence Kasdan,56,R,105 +1983-10-07,The Right Stuff,Drama,Philip Kaufman,21,PG,193 +1983-11-04,Deal of the Century,Comedy,William Friedkin,10,PG,99 +1983-12-09,Scarface,Crime,Brian De Palma,65,R,170 +1984-01-13,Terms of Endearment,Drama,James L. Brooks,108,PG,132 +1984-02-17,Unfaithfully Yours,Comedy,Howard Zieff,12,PG,96 +1984-03-16,Splash,Romance,Ron Howard,69,PG,111 +1984-04-13,Friday the 13th: The Final Chapter,Horror,Joseph Zito,32,R,91 +1984-05-04,Sixteen Candles,Comedy,John Hughes,23,PG,93 +1984-06-08,Ghostbusters,Comedy,Ivan Reitman,295,PG,105 +1984-07-06,The Last Starfighter,Sci-Fi,Nick Castle,28,PG,101 +1984-08-10,Red Dawn,Action,John Milius,38,PG-13,114 +1984-09-14,All of Me,Comedy,Carl Reiner,40,PG,93 +1984-10-26,The Terminator,Sci-Fi,James Cameron,78,R,107 +1984-11-16,Missing in Action,Action,Joseph Zito,22,R,101 +1984-12-14,Dune,Sci-Fi,David Lynch,30,PG-13,137 +1985-01-18,A Nightmare on Elm Street,Horror,Wes Craven,25,R,91 +1985-02-15,The Breakfast Club,Drama,John Hughes,45,R,97 +1985-03-29,Mask,Drama,Peter Bogdanovich,42,PG-13,120 +1985-04-26,Code of Silence,Action,Andrew Davis,20,R,101 +1985-05-22,Rambo: First Blood Part II,Action,George P. Cosmatos,150,R,96 +1985-06-07,The Goonies,Adventure,Richard Donner,61,PG,114 +1985-07-03,Back to the Future,Sci-Fi,Robert Zemeckis,381,PG,116 +1985-08-16,Year of the Dragon,Crime,Michael Cimino,18,R,134 +1985-09-20,Invasion U.S.A.,Action,Joseph Zito,17,R,107 +1985-10-18,Silver Bullet,Horror,Daniel Attias,12,R,95 +1985-11-22,Rocky IV,Drama,Sylvester Stallone,127,PG,91 +1985-12-20,The Color Purple,Drama,Steven Spielberg,142,PG-13,154 +1986-01-17,Iron Eagle,Action,Sidney J. Furie,24,PG-13,117 +1986-02-21,Crossroads,Drama,Walter Hill,5,R,99 +1986-03-21,Highlander,Fantasy,Russell Mulcahy,12,R,116 +1986-04-18,Legend,Fantasy,Ridley Scott,15,PG,89 +1986-05-16,Top Gun,Action,Tony Scott,357,PG,110 +1986-06-27,Running Scared,Action,Peter Hyams,38,R,107 +1986-07-18,Aliens,Sci-Fi,James Cameron,131,R,137 +1986-08-08,Stand By Me,Drama,Rob Reiner,52,R,89 +1986-09-19,Blue Velvet,Mystery,David Lynch,8,R,120 +1986-10-24,The Name of the Rose,Mystery,Jean-Jacques Annaud,7,R,130 +1986-11-21,An American Tail,Animation,Don Bluth,47,G,80 +1986-12-19,Star Trek IV: The Voyage Home,Sci-Fi,Leonard Nimoy,109,PG,119 +1987-01-23,Critical Condition,Comedy,Michael Apted,19,R,98 +1987-02-20,Death Before Dishonor,Action,Terry Leonard,3,R,91 +1987-03-13,Lethal Weapon,Action,Richard Donner,65,R,110 +1987-04-10,Project X,Drama,Jonathan Kaplan,28,PG,108 +1987-05-22,Beverly Hills Cop II,Action,Tony Scott,276,R,100 +1987-06-19,Predator,Sci-Fi,John McTiernan,98,R,107 +1987-07-17,RoboCop,Action,Paul Verhoeven,53,R,102 +1987-08-14,No Way Out,Thriller,Roger Donaldson,35,R,114 +1987-09-18,Fatal Beauty,Action,Tom Holland,12,R,104 +1987-10-23,Fatal Attraction,Thriller,Adrian Lyne,320,R,119 +1987-11-13,Running Man,Sci-Fi,Paul Michael Glaser,38,R,101 +1987-12-18,Wall Street,Drama,Oliver Stone,43,R,126 +1988-01-15,Return of the Living Dead Part II,Horror,Ken Wiederhorn,9,R,89 +1988-02-12,Action Jackson,Action,Craig R. Baxley,20,R,96 +1988-03-18,D.O.A.,Thriller,Rocky Morton,12,R,96 +1988-04-29,Colors,Crime,Dennis Hopper,46,R,120 +1988-05-20,Willow,Fantasy,Ron Howard,57,PG,126 +1988-06-21,Big,Comedy,Penny Marshall,151,PG,104 +1988-07-15,Die Hard,Action,John McTiernan,140,R,132 +1988-08-05,Young Guns,Western,Christopher Cain,45,R,107 +1988-09-16,Moon Over Parador,Comedy,Paul Mazursky,11,PG-13,103 +1988-10-21,Halloween 4,Horror,Dwight H. Little,17,R,88 +1988-11-11,Child's Play,Horror,Tom Holland,33,R,87 +1988-12-21,Rain Man,Drama,Barry Levinson,172,R,133 +1989-01-13,Deep Star Six,Sci-Fi,Sean S. Cunningham,8,R,99 +1989-02-17,Bill & Ted's Excellent Adventure,Comedy,Stephen Herek,40,PG,90 +1989-03-24,Leviathan,Sci-Fi,George P. Cosmatos,15,R,98 +1989-04-14,Major League,Comedy,David S. Ward,49,R,107 +1989-05-24,Indiana Jones and the Last Crusade,Action,Steven Spielberg,474,PG-13,127 +1989-06-23,Batman,Action,Tim Burton,411,PG-13,126 +1989-07-07,Lethal Weapon 2,Action,Richard Donner,227,R,114 +1989-08-11,A Nightmare on Elm Street 5,Horror,Stephen Hopkins,22,R,89 +1989-09-22,Black Rain,Action,Ridley Scott,46,R,125 +1989-10-20,Look Who's Talking,Comedy,Amy Heckerling,140,PG-13,93 +1989-11-17,All Dogs Go to Heaven,Animation,Don Bluth,27,G,84 +1989-12-20,Tango & Cash,Action,Andrei Konchalovsky,63,R,104 +""" + +MOVIES_JSON = """{ + "decades": { + "1980s": { + "genres": { + "action": { + "franchises": { + "terminator": { + "name": "The Terminator", + "movies": [ + { + "title": "The Terminator", + "year": 1984, + "director": "James Cameron", + "stars": ["Arnold Schwarzenegger", "Linda Hamilton", "Michael Biehn"], + "boxOffice": 78371200, + "quotes": ["I'll be back", "Come with me if you want to live"] + } + ] + }, + "rambo": { + "name": "Rambo", + "movies": [ + { + "title": "First Blood", + "year": 1982, + "director": "Ted Kotcheff", + "stars": ["Sylvester Stallone", "Richard Crenna", "Brian Dennehy"], + "boxOffice": 47212904 + }, + { + "title": "Rambo: First Blood Part II", + "year": 1985, + "director": "George P. Cosmatos", + "stars": ["Sylvester Stallone", "Richard Crenna", "Charles Napier"], + "boxOffice": 150415432 + } + ] + } + }, + "standalone_classics": { + "die_hard": { + "title": "Die Hard", + "year": 1988, + "director": "John McTiernan", + "stars": ["Bruce Willis", "Alan Rickman", "Reginald VelJohnson"], + "boxOffice": 140700000, + "location": "Nakatomi Plaza", + "quotes": ["Yippee-ki-yay, motherf***er"] + }, + "predator": { + "title": "Predator", + "year": 1987, + "director": "John McTiernan", + "stars": ["Arnold Schwarzenegger", "Carl Weathers", "Jesse Ventura"], + "boxOffice": 98267558, + "location": "Val Verde jungle", + "quotes": ["Get to the chopper!"] + } + }, + "common_themes": [ + "Cold War politics", + "One man army", + "Revenge plots", + "Military operations", + "Law enforcement" + ], + "typical_elements": { + "weapons": ["M60 machine gun", "Desert Eagle", "Explosive arrows"], + "vehicles": ["Military helicopters", "Muscle cars", "Tanks"], + "locations": ["Urban jungle", "Actual jungle", "Industrial facilities"] + } + } + } + } + }, + "metadata": { + "total_movies": 4, + "date_compiled": "2024", + "box_office_total": 467654094, + "most_frequent_actor": "Arnold Schwarzenegger", + "most_frequent_director": "John McTiernan" + } +}""" + +MOVIES_TREE = json.loads(MOVIES_JSON) + +DUNE_BIOS = [ + { + "name": "Paul Atreides", + "description": "Heir to House Atreides who becomes the Fremen messiah Muad'Dib. Born with extraordinary mental abilities due to Bene Gesserit breeding program.", + }, + { + "name": "Lady Jessica", + "description": "Bene Gesserit concubine to Duke Leto and mother of Paul. Defied her order by bearing a son instead of a daughter, disrupting centuries of careful breeding.", + }, + { + "name": "Baron Vladimir Harkonnen", + "description": "Cruel and corpulent leader of House Harkonnen, sworn enemy of House Atreides. Known for his cunning and brutality in pursuing power.", + }, + { + "name": "Leto Atreides", + "description": "Noble Duke and father of Paul, known for his honor and just rule. Accepts governorship of Arrakis despite knowing it's likely a trap.", + }, + { + "name": "Stilgar", + "description": "Leader of the Fremen Sietch Tabr, becomes a loyal supporter of Paul. Skilled warrior who helps train Paul in Fremen ways.", + }, + { + "name": "Chani", + "description": "Fremen warrior and daughter of planetologist Liet-Kynes. Becomes Paul's concubine and true love after appearing in his prescient visions.", + }, + { + "name": "Thufir Hawat", + "description": "Mentat and Master of Assassins for House Atreides. Serves three generations of Atreides with his superhuman computational skills.", + }, + { + "name": "Duncan Idaho", + "description": "Swordmaster of the Ginaz, loyal to House Atreides. Known for his exceptional fighting skills and sacrifice to save Paul and Jessica.", + }, + { + "name": "Gurney Halleck", + "description": "Warrior-troubadour of House Atreides, skilled with sword and baliset. Serves as Paul's weapons teacher and loyal friend.", + }, + { + "name": "Dr. Yueh", + "description": "Suk doctor conditioned against taking human life, but betrays House Atreides after the Harkonnens torture his wife. Imperial Conditioning broken.", + }, +] diff --git a/src/textual/demo/demo_app.py b/src/textual/demo/demo_app.py index 0121f031b8..66b4d5837e 100644 --- a/src/textual/demo/demo_app.py +++ b/src/textual/demo/demo_app.py @@ -15,6 +15,14 @@ class DemoApp(App): align: center top; &>*{ max-width: 100; } } + Screen .-maximized { + margin: 1 2; + max-width: 100%; + &.column { margin: 1 2; padding: 1 2; } + &.column > * { + max-width: 100%; + } + } """ MODES = { @@ -48,4 +56,32 @@ class DemoApp(App): "Screenshot", tooltip="Save an SVG 'screenshot' of the current screen", ), + Binding( + "ctrl+a", + "app.maximize", + "Maximize", + tooltip="Maximized the focused widget (if possible)", + ), ] + + def action_maximize(self) -> None: + if self.screen.is_maximized: + return + if self.screen.focused is None: + self.notify( + "Nothing to be maximized (try pressing [b]tab[/b])", + title="Maximize", + severity="warning", + ) + else: + if self.screen.maximize(self.screen.focused): + self.notify( + "You are now in the maximized view. Press [b]escape[/b] to return.", + title="Maximize", + ) + else: + self.notify( + "This widget may not be maximized.", + title="Maximize", + severity="warning", + ) diff --git a/src/textual/demo/home.py b/src/textual/demo/home.py index df78333dd4..7a8cb836e6 100644 --- a/src/textual/demo/home.py +++ b/src/textual/demo/home.py @@ -66,18 +66,27 @@ ```python # Start building! -import textual +from textual import App, ComposeResult +from textual.widgets import Label + +class MyApp(App): + def compose(self) -> ComposeResult: + yield Label("Hello, World!") + +MyApp().run() ``` -Well documented, typed, and intuitive. -Textual's API is accessible to Python developers of all skill levels. +* Intuitive, batteries-included, API. +* Well documented: See the [tutorial](https://textual.textualize.io/tutorial/), [guide](https://textual.textualize.io/guide/app/), and [reference](https://textual.textualize.io/reference/). +* Fully typed, with modern type annotations. +* Accessible to Python developers of all skill levels. **Hint:** press **C** to view the code for this page. ## Built on Rich -With over 1.5 *billion* downloads, Rich is the most popular terminal library out there. -Textual builds on Rich to add interactivity, and is compatible with Rich renderables. +With over 1.6 *billion* downloads, Rich is the most popular terminal library out there. +Textual builds on Rich to add interactivity, and is fully-compatible with Rich renderables. ## Re-usable widgets @@ -113,6 +122,8 @@ """ DEPLOY_MD = """\ +Textual apps have extremely low system requirements, and will run on virtually any OS and hardware; locally or remotely via SSH. + There are a number of ways to deploy and share Textual apps. ## As a Python library @@ -145,7 +156,7 @@ class StarCount(Vertical): color: $text-warning; #stars { align: center top; } #forks { align: right top; } - Label { text-style: bold; } + Label { text-style: bold; color: $foreground; } LoadingIndicator { background: transparent !important; } Digits { width: auto; margin-right: 1; } Label { margin-right: 1; } diff --git a/src/textual/demo/widgets.py b/src/textual/demo/widgets.py index dd36cb1f8c..d9b17bc01d 100644 --- a/src/textual/demo/widgets.py +++ b/src/textual/demo/widgets.py @@ -8,13 +8,14 @@ from rich.table import Table from rich.traceback import Traceback -from textual import containers, lazy +from textual import containers, events, lazy, on from textual.app import ComposeResult from textual.binding import Binding -from textual.demo.data import COUNTRIES +from textual.demo.data import COUNTRIES, DUNE_BIOS, MOVIES, MOVIES_TREE from textual.demo.page import PageScreen from textual.reactive import reactive, var from textual.suggester import SuggestFromList +from textual.theme import BUILTIN_THEMES from textual.widgets import ( Button, Checkbox, @@ -32,8 +33,13 @@ RadioButton, RadioSet, RichLog, + Select, Sparkline, + Static, + Switch, TabbedContent, + TextArea, + Tree, ) WIDGETS_MD = """\ @@ -49,6 +55,7 @@ class Buttons(containers.VerticalGroup): """Buttons demo.""" + ALLOW_MAXIMIZE = True DEFAULT_CLASSES = "column" DEFAULT_CSS = """ Buttons { @@ -63,6 +70,8 @@ class Buttons(containers.VerticalGroup): A simple button, with a number of semantic styles. May be rendered unclickable by setting `disabled=True`. +Press `return` to active a button when focused (or click it). + """ def compose(self) -> ComposeResult: @@ -116,29 +125,35 @@ class Checkboxes(containers.VerticalGroup): Checkboxes to toggle booleans. Radio buttons for exclusive booleans. -Radio sets for a managed set of options where only a single option may be selected. + +Hit `return` to toggle an checkbox / radio button, when focused. """ + RADIOSET_MD = """\ +### Radio Sets + +A *radio set* is a list of mutually exclusive options. +Use the `up` and `down` keys to navigate the list. +Press `return` to toggle a radio button. + +""" def compose(self) -> ComposeResult: yield Markdown(self.CHECKBOXES_MD) - with containers.HorizontalGroup(): - with containers.VerticalGroup(): - yield Checkbox("Arrakis") - yield Checkbox("Caladan") - yield RadioButton("Chusuk") - yield RadioButton("Giedi Prime") - yield RadioSet( - "Amanda", - "Connor MacLeod", - "Duncan MacLeod", - "Heather MacLeod", - "Joe Dawson", - "Kurgan, [bold italic red]The[/]", - "Methos", - "Rachel Ellenstein", - "Ramírez", - ) + yield Checkbox("A Checkbox") + yield RadioButton("A Radio Button") + yield Markdown(self.RADIOSET_MD) + yield RadioSet( + "Amanda", + "Connor MacLeod", + "Duncan MacLeod", + "Heather MacLeod", + "Joe Dawson", + "Kurgan, [bold italic red]The[/]", + "Methos", + "Rachel Ellenstein", + "Ramírez", + ) class Datatables(containers.VerticalGroup): @@ -151,34 +166,35 @@ class Datatables(containers.VerticalGroup): A fully-featured DataTable, with cell, row, and columns cursors. Cells may be individually styled, and may include Rich renderables. +**Tip:** Focus the table and press `ctrl+m` + """ - ROWS = [ - ("lane", "swimmer", "country", "time"), - (4, "Joseph Schooling", "Singapore", 50.39), - (2, "Michael Phelps", "United States", 51.14), - (5, "Chad le Clos", "South Africa", 51.14), - (6, "László Cseh", "Hungary", 51.14), - (3, "Li Zhuhao", "China", 51.26), - (8, "Mehdy Metella", "France", 51.58), - (7, "Tom Shields", "United States", 51.73), - (1, "Aleksandr Sadovnikov", "Russia", 51.84), - (10, "Darren Burns", "Scotland", 51.84), - ] + DEFAULT_CSS = """ + DataTable { + height: 16 !important; + &.-maximized { + height: auto !important; + } + } + + """ def compose(self) -> ComposeResult: yield Markdown(self.DATATABLES_MD) with containers.Center(): - yield DataTable() + yield DataTable(fixed_columns=1) def on_mount(self) -> None: + ROWS = list(csv.reader(io.StringIO(MOVIES))) table = self.query_one(DataTable) - table.add_columns(*self.ROWS[0]) - table.add_rows(self.ROWS[1:]) + table.add_columns(*ROWS[0]) + table.add_rows(ROWS[1:]) class Inputs(containers.VerticalGroup): """Demonstrates Inputs.""" + ALLOW_MAXIMIZE = True DEFAULT_CLASSES = "column" INPUTS_MD = """\ ## Inputs and MaskedInputs @@ -234,6 +250,7 @@ def compose(self) -> ComposeResult: class ListViews(containers.VerticalGroup): """Demonstrates List Views and Option Lists.""" + ALLOW_MAXIMIZE = True DEFAULT_CLASSES = "column" LISTS_MD = """\ ## List Views and Option Lists @@ -292,6 +309,10 @@ class Logs(containers.VerticalGroup): } } TabPane { padding: 0; } + TabbedContent.-maximized { + height: 1fr; + Log, RichLog { height: 1fr; } + } } """ @@ -349,7 +370,9 @@ def on_mount(self) -> None: def update_log(self) -> None: """Update the Log with new content.""" log = self.query_one(Log) - if not self.screen.can_view_partial(log) or not self.screen.is_active: + if self.is_scrolling: + return + if not self.app.screen.can_view_entire(log) and not log.is_in_maximized_view: return self.log_count += 1 line_no = self.log_count % len(self.TEXT) @@ -359,7 +382,12 @@ def update_log(self) -> None: def update_rich_log(self) -> None: """Update the Rich Log with content.""" rich_log = self.query_one(RichLog) - if not self.screen.can_view_partial(rich_log) or not self.screen.is_active: + if self.is_scrolling: + return + if ( + not self.app.screen.can_view_entire(rich_log) + and not rich_log.is_in_maximized_view + ): return self.rich_log_count += 1 log_option = self.rich_log_count % 3 @@ -381,6 +409,108 @@ def update_rich_log(self) -> None: rich_log.write(traceback, animate=True) +class Markdowns(containers.VerticalGroup): + DEFAULT_CLASSES = "column" + DEFAULT_CSS = """ + Markdowns { + #container { + border: tall transparent; + height: 16; + padding: 0 1; + &:focus { border: tall $border; } + &.-maximized { height: 1fr; } + } + #movies { + padding: 0 1; + MarkdownBlock { padding: 0 1 0 0; } + } + } + """ + MD_MD = """\ +## Markdown + +Display Markdown in your apps with the Markdown widget. +Most of the text on this page is Markdown. + +Here's an AI generated Markdown document: + +""" + MOVIES_MD = """\ +# The Golden Age of Action Cinema: The 1980s + +The 1980s marked a transformative era in action cinema, defined by **excessive machismo**, explosive practical effects, and unforgettable one-liners. This decade gave birth to many of Hollywood's most enduring action franchises, from _Die Hard_ to _Rambo_, setting templates that filmmakers still reference today. + +## Technical Innovation + +Technologically, the 80s represented a sweet spot between practical effects and early CGI. Filmmakers relied heavily on: + +* Practical stunts +* Pyrotechnics +* Hand-built models + +These elements lent the films a tangible quality that many argue remains superior to modern digital effects. + +## The Action Hero Archetype + +The quintessential action hero emerged during this period, with key characteristics: + +1. Impressive physique +2. Military background +3. Anti-authority attitude +4. Memorable catchphrases + +> "I'll be back" - The Terminator (1984) + +Heroes like Arnold Schwarzenegger and Sylvester Stallone became global icons. However, the decade also saw more nuanced characters emerge, like Bruce Willis's everyman John McClane in *Die Hard*, and powerful female protagonists like Sigourney Weaver's Ellen Ripley in *Aliens*. + +### Political Influence + +Cold War politics heavily influenced these films' narratives, with many plots featuring American heroes facing off against Soviet adversaries. This political subtext, combined with themes of individual triumph over bureaucratic systems, perfectly captured the era's zeitgeist. + +--- + +While often dismissed as simple entertainment, 80s action films left an indelible mark on cinema history, influencing everything from filming techniques to narrative structures, and continuing to inspire filmmakers and delight audiences decades later. + +""" + + def compose(self) -> ComposeResult: + yield Markdown(self.MD_MD) + with containers.VerticalScroll( + id="container", can_focus=True, can_maximize=True + ): + yield Markdown(self.MOVIES_MD, id="movies") + + +class Selects(containers.VerticalGroup): + DEFAULT_CLASSES = "column" + SELECTS_MD = """\ +## Selects + +Selects (AKA *Combo boxes*), present a list of options in a menu that may be expanded by the user. +""" + HEROS = [ + "Arnold Schwarzenegger", + "Brigitte Nielsen", + "Bruce Willis", + "Carl Weathers", + "Chuck Norris", + "Dolph Lundgren", + "Grace Jones", + "Harrison Ford", + "Jean-Claude Van Damme", + "Kurt Russell", + "Linda Hamilton", + "Mel Gibson", + "Michelle Yeoh", + "Sigourney Weaver", + "Sylvester Stallone", + ] + + def compose(self) -> ComposeResult: + yield Markdown(self.SELECTS_MD) + yield Select.from_values(self.HEROS, prompt="80s action hero") + + class Sparklines(containers.VerticalGroup): """Demonstrates sparklines.""" @@ -404,6 +534,11 @@ class Sparklines(containers.VerticalGroup): &#third > .sparkline--min-color { color: $primary; } &#third > .sparkline--max-color { color: $accent; } } + VerticalScroll { + height: auto; + border: heavy transparent; + &:focus { border: heavy $border; } + } } """ @@ -413,41 +548,236 @@ class Sparklines(containers.VerticalGroup): def compose(self) -> ComposeResult: yield Markdown(self.LOGS_MD) - yield Sparkline([], summary_function=max, id="first").data_bind( - Sparklines.data, - ) - yield Sparkline([], summary_function=max, id="second").data_bind( - Sparklines.data, - ) - yield Sparkline([], summary_function=max, id="third").data_bind( - Sparklines.data, - ) + with containers.VerticalScroll( + id="container", can_focus=True, can_maximize=True + ): + yield Sparkline([], summary_function=max, id="first").data_bind( + Sparklines.data, + ) + yield Sparkline([], summary_function=max, id="second").data_bind( + Sparklines.data, + ) + yield Sparkline([], summary_function=max, id="third").data_bind( + Sparklines.data, + ) def on_mount(self) -> None: - self.set_interval(0.2, self.update_sparks) + self.set_interval(0.1, self.update_sparks) def update_sparks(self) -> None: """Update the sparks data.""" - if not self.screen.can_view_partial(self) or not self.screen.is_active: + if self.is_scrolling: + return + if ( + not self.app.screen.can_view_partial(self) + and not self.query_one(Sparkline).is_in_maximized_view + ): return self.count += 1 offset = self.count * 40 self.data = [abs(sin(x / 3.14)) for x in range(offset, offset + 360 * 6, 20)] +class Switches(containers.VerticalGroup): + """Demonstrate the Switch widget.""" + + ALLOW_MAXIMIZE = True + DEFAULT_CLASSES = "column" + SWITCHES_MD = """\ +## Switches + +Functionally almost identical to a Checkbox, but more displays more prominently in the UI. +""" + DEFAULT_CSS = """\ +Switches { + Label { + padding: 1; + &:hover {text-style:underline; } + } +} +""" + + def compose(self) -> ComposeResult: + yield Markdown(self.SWITCHES_MD) + with containers.ItemGrid(min_column_width=32): + for theme in BUILTIN_THEMES: + if theme.endswith("-ansi"): + continue + with containers.HorizontalGroup(): + yield Switch(id=theme) + yield Label(theme, name=theme) + + @on(events.Click, "Label") + def on_click(self, event: events.Click) -> None: + """Make the label toggle the switch.""" + # TODO: Add a dedicated form label + event.stop() + if event.widget is not None: + self.query_one(f"#{event.widget.name}", Switch).toggle() + + def on_switch_changed(self, event: Switch.Changed) -> None: + # Don't issue more Changed events + if not event.value: + self.query_one("#textual-dark", Switch).value = True + return + + with self.prevent(Switch.Changed): + # Reset all other switches + for switch in self.query("Switch").results(Switch): + if switch.id != event.switch.id: + switch.value = False + assert event.switch.id is not None + theme_id = event.switch.id + + def switch_theme() -> None: + """Callback to switch the theme.""" + self.app.theme = theme_id + + # Call after a short delay, so we see the Switch animation + self.set_timer(0.3, switch_theme) + + +class TabsDemo(containers.VerticalGroup): + DEFAULT_CLASSES = "column" + TABS_MD = """\ +## Tabs + +A navigable list of section headers. + +Typically used with `ContentTabs`, to display additional content associate with each tab. + +Use the cursor keys to navigate. + +""" + DEFAULT_CSS = """ + .bio { padding: 1 2; background: $boost; color: $foreground-muted; } + """ + + def compose(self) -> ComposeResult: + yield Markdown(self.TABS_MD) + with TabbedContent(*[bio["name"] for bio in DUNE_BIOS]): + for bio in DUNE_BIOS: + yield Static(bio["description"], classes="bio") + + +class Trees(containers.VerticalGroup): + DEFAULT_CLASSES = "column" + TREES_MD = """\ +## Tree + +The Tree widget displays hierarchical data. + +There is also the Tree widget's cousin, DirectoryTree, to navigate folders and files on the filesystem. + """ + DEFAULT_CSS = """ + Trees { + Tree { + height: 16; + &.-maximized { height: 1fr; } + } + VerticalGroup { + border: heavy transparent; + &:focus-within { border: heavy $border; } + } + } + + """ + + def compose(self) -> ComposeResult: + yield Markdown(self.TREES_MD) + with containers.VerticalGroup(): + tree = Tree("80s movies") + tree.show_root = False + tree.add_json(MOVIES_TREE) + tree.root.expand() + yield tree + + +class TextAreas(containers.VerticalGroup): + ALLOW_MAXIMIZE = True + DEFAULT_CLASSES = "column" + TEXTAREA_MD = """\ +## TextArea + +A powerful and highly configurable text area that supports syntax highlighting, line numbers, soft wrapping, and more. + +""" + DEFAULT_CSS = """ + TextAreas { + TextArea { + height: 16; + } + &.-maximized { + height: 1fr; + } + } + """ + DEFAULT_TEXT = """\ +# Start building! +from textual import App, ComposeResult +""" + + def compose(self) -> ComposeResult: + yield Markdown(self.TEXTAREA_MD) + yield Select.from_values( + [ + "Bash", + "Css", + "Go", + "HTML", + "Java", + "Javascript", + "JSON", + "Kotlin", + "Markdown", + "Python", + "Rust", + "Regex", + "Sql", + "TOML", + "YAML", + ], + value="Python", + prompt="Highlight language", + ) + + yield TextArea(self.DEFAULT_TEXT, show_line_numbers=True, language=None) + + def on_select_changed(self, event: Select.Changed) -> None: + self.query_one(TextArea).language = ( + event.value.lower() if isinstance(event.value, str) else None + ) + + +class YourWidgets(containers.VerticalGroup): + DEFAULT_CLASSES = "column" + YOUR_MD = """\ +## Your Widget Here! + +The Textual API allows you to [build custom re-usable widgets](https://textual.textualize.io/guide/widgets/#custom-widgets) and share them across projects. +Custom widgets can be themed, just like the builtin widget library. + +Combine existing widgets to add new functionality, or use the powerful [Line API](https://textual.textualize.io/guide/widgets/#line-api) for unique creations. + +""" + DEFAULT_CSS = """ + YourWidgets { margin-bottom: 2; } + """ + + def compose(self) -> ComposeResult: + yield Markdown(self.YOUR_MD) + + class WidgetsScreen(PageScreen): """The Widgets screen""" CSS = """ WidgetsScreen { align-horizontal: center; - Markdown { - background: transparent; - } - & > VerticalScroll { + Markdown { background: transparent; } + #container { scrollbar-gutter: stable; - &> * { - &:last-of-type { margin-bottom: 2; } + & > * { &:even { background: $boost; } padding-bottom: 1; } @@ -458,7 +788,7 @@ class WidgetsScreen(PageScreen): BINDINGS = [Binding("escape", "blur", "Unfocus any focused widget", show=False)] def compose(self) -> ComposeResult: - with lazy.Reveal(containers.VerticalScroll(can_focus=False)): + with lazy.Reveal(containers.VerticalScroll(id="container", can_focus=True)): yield Markdown(WIDGETS_MD, classes="column") yield Buttons() yield Checkboxes() @@ -466,5 +796,12 @@ def compose(self) -> ComposeResult: yield Inputs() yield ListViews() yield Logs() + yield Markdowns() + yield Selects() yield Sparklines() + yield Switches() + yield TabsDemo() + yield TextAreas() + yield Trees() + yield YourWidgets() yield Footer() diff --git a/src/textual/dom.py b/src/textual/dom.py index de4e9c8fe3..68212bc8b6 100644 --- a/src/textual/dom.py +++ b/src/textual/dom.py @@ -223,6 +223,8 @@ def __init__( self._has_focus_within: bool = False self._has_order_style: bool = False """The node has an ordered dependent pseudo-style (`:odd`, `:even`, `:first-of-type`, `:last-of-type`)""" + self._has_odd_or_even: bool = False + """The node has the pseudo class `odd` or `even`.""" self._reactive_connect: ( dict[str, tuple[MessagePump, Reactive[object] | object]] | None ) = None diff --git a/src/textual/driver.py b/src/textual/driver.py index b16b982b5e..1fa57c406b 100644 --- a/src/textual/driver.py +++ b/src/textual/driver.py @@ -113,6 +113,7 @@ def process_message(self, message: messages.Message) -> None: for button in buttons: self.send_message( MouseUp( + message.widget, x=move_event.x, y=move_event.y, delta_x=0, diff --git a/src/textual/events.py b/src/textual/events.py index e8fa1fa9e8..6e7a21e8da 100644 --- a/src/textual/events.py +++ b/src/textual/events.py @@ -327,6 +327,7 @@ class MouseEvent(InputEvent, bubble=True): - [ ] Verbose Args: + widget: The widget under the mouse. x: The relative x coordinate. y: The relative y coordinate. delta_x: Change in x since the last message. @@ -341,6 +342,7 @@ class MouseEvent(InputEvent, bubble=True): """ __slots__ = [ + "widget", "x", "y", "delta_x", @@ -356,6 +358,7 @@ class MouseEvent(InputEvent, bubble=True): def __init__( self, + widget: Widget | None, x: int, y: int, delta_x: int, @@ -369,6 +372,8 @@ def __init__( style: Style | None = None, ) -> None: super().__init__() + self.widget: Widget | None = widget + """The widget under the mouse at the time of a click.""" self.x = x """The relative x coordinate.""" self.y = y @@ -392,8 +397,11 @@ def __init__( self._style = style or Style() @classmethod - def from_event(cls: Type[MouseEventT], event: MouseEvent) -> MouseEventT: + def from_event( + cls: Type[MouseEventT], widget: Widget, event: MouseEvent + ) -> MouseEventT: new_event = cls( + widget, event.x, event.y, event.delta_x, @@ -409,6 +417,7 @@ def from_event(cls: Type[MouseEventT], event: MouseEvent) -> MouseEventT: return new_event def __rich_repr__(self) -> rich.repr.Result: + yield self.widget yield "x", self.x yield "y", self.y yield "delta_x", self.delta_x, 0 @@ -422,6 +431,10 @@ def __rich_repr__(self) -> rich.repr.Result: yield "meta", self.meta, False yield "ctrl", self.ctrl, False + @property + def control(self) -> Widget | None: + return self.widget + @property def offset(self) -> Offset: """The mouse coordinate as an offset. @@ -478,6 +491,7 @@ def get_content_offset_capture(self, widget: Widget) -> Offset: def _apply_offset(self, x: int, y: int) -> MouseEvent: return self.__class__( + self.widget, x=self.x + x, y=self.y + y, delta_x=self.delta_x, diff --git a/src/textual/lazy.py b/src/textual/lazy.py index 436025f063..58ab6a3e2e 100644 --- a/src/textual/lazy.py +++ b/src/textual/lazy.py @@ -4,8 +4,6 @@ from __future__ import annotations -from functools import partial - from textual.widget import Widget @@ -66,82 +64,78 @@ async def mount() -> None: class Reveal(Widget): + """Similar to [Lazy][textual.lazy.Lazy], but mounts children sequentially. + + This is useful when you have so many child widgets that there is a noticeable delay before + you see anything. By mounting the children over several frames, the user will feel that + something is happening. + + Example: + ```python + def compose(self) -> ComposeResult: + with lazy.Reveal(containers.VerticalScroll(can_focus=False)): + yield Markdown(WIDGETS_MD, classes="column") + yield Buttons() + yield Checkboxes() + yield Datatables() + yield Inputs() + yield ListViews() + yield Logs() + yield Sparklines() + yield Footer() + ``` + """ + DEFAULT_CSS = """ Reveal { display: none; } """ - def __init__(self, widget: Widget, delay: float = 1 / 60) -> None: - """Similar to [Lazy][textual.lazy.Lazy], but also displays *children* sequentially. - - The first frame will display the first child with all other children hidden. - The remaining children will be displayed 1-by-1, over as may frames are required. - - This is useful when you have so many child widgets that there is a noticeable delay before - you see anything. By mounting the children over several frames, the user will feel that - something is happening. - - Example: - ```python - def compose(self) -> ComposeResult: - with lazy.Reveal(containers.VerticalScroll(can_focus=False)): - yield Markdown(WIDGETS_MD, classes="column") - yield Buttons() - yield Checkboxes() - yield Datatables() - yield Inputs() - yield ListViews() - yield Logs() - yield Sparklines() - yield Footer() - ``` - + def __init__(self, widget: Widget) -> None: + """ Args: - widget: A widget that should be mounted after a refresh. - delay: A (short) delay between mounting widgets. + widget: A widget to mount. """ self._replace_widget = widget - self._delay = delay + self._widgets: list[Widget] = [] super().__init__() @classmethod - def _reveal(cls, parent: Widget, delay: float = 1 / 60) -> None: + def _reveal(cls, parent: Widget, widgets: list[Widget]) -> None: """Reveal children lazily. Args: parent: The parent widget. - delay: A delay between reveals. + widgets: Child widgets. """ - def check_children() -> None: - """Check for un-displayed children.""" - iter_children = iter(parent.children) - for child in iter_children: - if not child.display: - child.display = True - break - for child in iter_children: - if not child.display: - parent.set_timer( - delay, partial(parent.call_after_refresh, check_children) - ) - break - - check_children() + async def check_children() -> None: + """Check for pending children""" + if not widgets: + return + widget = widgets.pop(0) + try: + await parent.mount(widget) + except Exception: + # I think this can occur if the parent is removed before all children are added + # Only noticed this on shutdown + return + + if widgets: + parent.set_timer(0.02, check_children) + + parent.call_next(check_children) def compose_add_child(self, widget: Widget) -> None: - widget.display = False - self._replace_widget.compose_add_child(widget) + self._widgets.append(widget) async def mount_composed_widgets(self, widgets: list[Widget]) -> None: parent = self.parent if parent is None: return assert isinstance(parent, Widget) - - if self._replace_widget.children: - self._replace_widget.children[0].display = True await parent.mount(self._replace_widget, after=self) await self.remove() - self._reveal(self._replace_widget, self._delay) + self._reveal(self._replace_widget, self._widgets.copy()) + self._widgets.clear() diff --git a/src/textual/message_pump.py b/src/textual/message_pump.py index 6ac31ac8eb..d47c51cf1c 100644 --- a/src/textual/message_pump.py +++ b/src/textual/message_pump.py @@ -725,6 +725,8 @@ def _get_dispatch_methods( continue for attribute, selector in selectors.items(): node = getattr(message, attribute) + if node is None: + break if not isinstance(node, Widget): raise OnNoWidget( f"on decorator can't match against {attribute!r} as it is not a widget." @@ -834,6 +836,13 @@ def post_message(self, message: Message) -> bool: async def on_callback(self, event: events.Callback) -> None: if self.app._closing: return + try: + self.app.screen + except Exception: + self.log.warning( + f"Not invoking timer callback {event.callback!r} because there is no screen." + ) + return await invoke(event.callback) async def on_timer(self, event: events.Timer) -> None: @@ -842,6 +851,13 @@ async def on_timer(self, event: events.Timer) -> None: event.prevent_default() event.stop() if event.callback is not None: + try: + self.app.screen + except Exception: + self.log.warning( + f"Not invoking timer callback {event.callback!r} because there is no screen." + ) + return try: await invoke(event.callback) except Exception as error: diff --git a/src/textual/pilot.py b/src/textual/pilot.py index 63f3ac3db0..e7362ea4e6 100644 --- a/src/textual/pilot.py +++ b/src/textual/pilot.py @@ -32,6 +32,7 @@ def _get_mouse_message_arguments( """Get the arguments to pass into mouse messages for the click and hover methods.""" click_x, click_y = target.region.offset + offset message_arguments = { + "widget": target, "x": click_x, "y": click_y, "delta_x": 0, diff --git a/src/textual/renderables/_blend_colors.py b/src/textual/renderables/_blend_colors.py index 305675103a..24476faf69 100644 --- a/src/textual/renderables/_blend_colors.py +++ b/src/textual/renderables/_blend_colors.py @@ -15,8 +15,8 @@ def blend_colors(color1: Color, color2: Color, ratio: float) -> Color: Returns: A Color representing the blending of the two supplied colors. """ - assert color1.triplet is not None - assert color2.triplet is not None + if color1.triplet is None or color2.triplet is None: + return color2 r1, g1, b1 = color1.triplet r2, g2, b2 = color2.triplet diff --git a/src/textual/renderables/digits.py b/src/textual/renderables/digits.py index c6d982f5dc..bcea3ed612 100644 --- a/src/textual/renderables/digits.py +++ b/src/textual/renderables/digits.py @@ -13,7 +13,7 @@ ┏━┓ ┃ ┃ ┗━┛ - ┓ +╺┓ ┃ ╺┻╸ ╺━┓ @@ -78,7 +78,7 @@ ╰╫╯ ╭─╮ ╪═ -┴─╴ +┷━╸ ╭─╮ ╪═ ╰─╯ @@ -163,7 +163,7 @@ ╰╫╯ ╭─╮ ╪═ -┴─╴ +┷━╸ ╭─╮ ╪═ ╰─╯ diff --git a/src/textual/renderables/sparkline.py b/src/textual/renderables/sparkline.py index 4da25fa233..c2c15608d4 100644 --- a/src/textual/renderables/sparkline.py +++ b/src/textual/renderables/sparkline.py @@ -6,6 +6,7 @@ from rich.color import Color from rich.console import Console, ConsoleOptions, RenderResult +from rich.measure import Measurement from rich.segment import Segment from rich.style import Style @@ -95,6 +96,11 @@ def __rich_console__( bucket_index += step yield Segment(self.BARS[bar_index], Style.from_color(bar_color)) + def __rich_measure__( + self, console: "Console", options: "ConsoleOptions" + ) -> Measurement: + return Measurement(self.width or options.max_width, 1) + if __name__ == "__main__": console = Console() diff --git a/src/textual/screen.py b/src/textual/screen.py index 9bea8be709..81cc3c733a 100644 --- a/src/textual/screen.py +++ b/src/textual/screen.py @@ -746,12 +746,15 @@ def focus_previous(self, selector: str | type[QueryType] = "*") -> Widget | None """ return self._move_focus(-1, selector) - def maximize(self, widget: Widget, container: bool = True) -> None: + def maximize(self, widget: Widget, container: bool = True) -> bool: """Maximize a widget, so it fills the screen. Args: widget: Widget to maximize. container: If one of the widgets ancestors is a maximizeable widget, maximize that instead. + + Returns: + `True` if the widget was maximized, otherwise `False`. """ if widget.allow_maximize: if container: @@ -761,9 +764,11 @@ def maximize(self, widget: Widget, container: bool = True) -> None: break if maximize_widget.allow_maximize: self.maximized = maximize_widget - return + return True self.maximized = widget + return True + return False def minimize(self) -> None: """Restore any maximized widget to normal state.""" @@ -1371,6 +1376,7 @@ def _translate_mouse_move_event( the origin of the specified region. """ return events.MouseMove( + event.widget, event.x - region.x, event.y - region.y, event.delta_x, diff --git a/src/textual/timer.py b/src/textual/timer.py index 3a26774b8a..2470453c29 100644 --- a/src/textual/timer.py +++ b/src/textual/timer.py @@ -177,6 +177,11 @@ async def _run(self) -> None: async def _tick(self, *, next_timer: float, count: int) -> None: """Triggers the Timer's action: either call its callback, or sends an event to its target""" + + app = active_app.get() + if app._exit: + return + if self._callback is not None: try: await invoke(self._callback) @@ -185,7 +190,6 @@ async def _tick(self, *, next_timer: float, count: int) -> None: # Re-raise CancelledErrors that would be caught by the following exception block in Python 3.7 raise except Exception as error: - app = active_app.get() app._handle_exception(error) else: event = events.Timer( diff --git a/src/textual/widget.py b/src/textual/widget.py index d251d06a62..9f758b64cb 100644 --- a/src/textual/widget.py +++ b/src/textual/widget.py @@ -9,6 +9,7 @@ from collections import Counter from contextlib import asynccontextmanager from fractions import Fraction +from time import monotonic from types import TracebackType from typing import ( TYPE_CHECKING, @@ -491,6 +492,8 @@ def __init__( """Used to cache :last-of-type pseudoclass state.""" self._odd: tuple[int, bool] = (-1, False) """Used to cache :odd pseudoclass state.""" + self._last_scroll_time = monotonic() + """Time of last scroll.""" @property def is_mounted(self) -> bool: @@ -616,6 +619,17 @@ def is_maximized(self) -> bool: except NoScreen: return False + @property + def is_in_maximized_view(self) -> bool: + """Is this widget, or a parent maximized?""" + maximized = self.screen.maximized + if not maximized: + return False + for node in self.ancestors_with_self: + if maximized is node: + return True + return False + @property def _render_widget(self) -> Widget: """The widget the compositor should render.""" @@ -1239,11 +1253,18 @@ def mount( parent, *widgets, before=insert_before, after=insert_after ) - def update_styles(children: Iterable[DOMNode]) -> None: + def update_styles(children: list[DOMNode]) -> None: """Update order related CSS""" - for child in children: - if child._has_order_style: - child._update_styles() + if before is not None or after is not None: + # If the new children aren't at the end. + # we need to update both odd/even and first-of-type/last-of-type + for child in children: + if child._has_order_style or child._has_odd_or_even: + child._update_styles() + else: + for child in children: + if child._has_order_style: + child._update_styles() self.call_later(update_styles, list(self.children)) await_mount = AwaitMount(self, mounted) @@ -2197,6 +2218,23 @@ def is_scrollable(self) -> bool: """Can this widget be scrolled?""" return self.styles.layout is not None or bool(self._nodes) + @property + def is_scrolling(self) -> bool: + """Is this widget currently scrolling?""" + current_time = monotonic() + for node in self.ancestors: + if not isinstance(node, Widget): + break + if ( + node.scroll_x != node.scroll_target_x + or node.scroll_y != node.scroll_target_y + ): + return True + if current_time - node._last_scroll_time < 0.1: + # Scroll ended very recently + return True + return False + @property def layer(self) -> str: """Get the name of this widgets layer. @@ -2336,6 +2374,12 @@ def _scroll_to( animator.force_stop_animation(self, "scroll_x") animator.force_stop_animation(self, "scroll_y") + def _animate_on_complete() -> None: + """set last scroll time, and invoke callback.""" + self._last_scroll_time = monotonic() + if on_complete is not None: + self.call_next(on_complete) + if animate: # TODO: configure animation speed if duration is None and speed is None: @@ -2354,7 +2398,7 @@ def _scroll_to( speed=speed, duration=duration, easing=easing, - on_complete=on_complete, + on_complete=_animate_on_complete, level=level, ) scrolled_x = True @@ -2368,7 +2412,7 @@ def _scroll_to( speed=speed, duration=duration, easing=easing, - on_complete=on_complete, + on_complete=_animate_on_complete, level=level, ) scrolled_y = True @@ -2385,12 +2429,10 @@ def _scroll_to( self.scroll_target_y = self.scroll_y = y scrolled_y = scroll_y != self.scroll_y + self._last_scroll_time = monotonic() if on_complete is not None: self.call_after_refresh(on_complete) - if scrolled_x or scrolled_y: - self.app._pause_hover_effects() - return scrolled_x or scrolled_y def pre_layout(self, layout: Layout) -> None: @@ -2871,6 +2913,7 @@ def scroll_up( force=force, on_complete=on_complete, level=level, + immediate=immediate, ) def _scroll_up_for_pointer( diff --git a/src/textual/widgets/_button.py b/src/textual/widgets/_button.py index 49b436cf82..20b4572207 100644 --- a/src/textual/widgets/_button.py +++ b/src/textual/widgets/_button.py @@ -59,8 +59,8 @@ class Button(Widget, can_focus=True): content-align: center middle; text-style: bold; - &:disabled { - text-style: not bold; + &:disabled { + text-opacity: 0.6; } &:focus { diff --git a/src/textual/widgets/_list_item.py b/src/textual/widgets/_list_item.py index 8e889d6427..0df949c1be 100644 --- a/src/textual/widgets/_list_item.py +++ b/src/textual/widgets/_list_item.py @@ -2,7 +2,7 @@ from __future__ import annotations -from textual import events +from textual import events, on from textual.message import Message from textual.reactive import reactive from textual.widget import Widget @@ -30,5 +30,10 @@ def _on_click(self, _: events.Click) -> None: self.post_message(self._ChildClicked(self)) def watch_highlighted(self, value: bool) -> None: - print("highlighted", value) - self.set_class(value, "--highlight") + self.set_class(value, "-highlight") + + @on(events.Enter) + @on(events.Leave) + def on_enter_or_leave(self, event: events.Enter | events.Leave) -> None: + event.stop() + self.set_class(self.is_mouse_over, "-hovered") diff --git a/src/textual/widgets/_list_view.py b/src/textual/widgets/_list_view.py index bc81a0936f..e2363a80f8 100644 --- a/src/textual/widgets/_list_view.py +++ b/src/textual/widgets/_list_view.py @@ -25,6 +25,8 @@ class ListView(VerticalScroll, can_focus=True, can_focus_children=False): index: The index in the list that's currently highlighted. """ + ALLOW_MAXIMIZE = True + DEFAULT_CSS = """ ListView { background: $surface; @@ -37,19 +39,23 @@ class ListView(VerticalScroll, can_focus=True, can_focus_children=False): height: auto; overflow: hidden hidden; width: 1fr; + + &.-hovered { + background: $block-hover-background; + } - &.--highlight > Widget { + &.-highlight { color: $block-cursor-blurred-foreground; background: $block-cursor-blurred-background; text-style: $block-cursor-blurred-text-style; } } - &:focus > ListItem.--highlight > Widget { + &:focus > ListItem.-highlight > Widget { width: 1fr; color: $block-cursor-foreground; background: $block-cursor-background; - text-style: $block-cursor-text-style; + text-style: $block-cursor-text-style; } } """ diff --git a/src/textual/widgets/_radio_set.py b/src/textual/widgets/_radio_set.py index cdfa80b7ad..eb47af19fc 100644 --- a/src/textual/widgets/_radio_set.py +++ b/src/textual/widgets/_radio_set.py @@ -25,6 +25,8 @@ class RadioSet(VerticalScroll, can_focus=True, can_focus_children=False): turned off. """ + ALLOW_MAXIMIZE = True + DEFAULT_CSS = """ RadioSet { border: tall $border-blurred; diff --git a/src/textual/widgets/_sparkline.py b/src/textual/widgets/_sparkline.py index 103cb08d94..aeb55382a1 100644 --- a/src/textual/widgets/_sparkline.py +++ b/src/textual/widgets/_sparkline.py @@ -87,8 +87,7 @@ def __init__( def render(self) -> RenderResult: """Renders the sparkline when there is data available.""" - if not self.data: - return "" + data = self.data or [] _, base = self.background_colors min_color = base + ( self.get_component_styles("sparkline--min-color").color @@ -101,7 +100,7 @@ def render(self) -> RenderResult: else self.max_color ) return SparklineRenderable( - self.data, + data, width=self.size.width, min_color=min_color.rich_color, max_color=max_color.rich_color, diff --git a/src/textual/widgets/_switch.py b/src/textual/widgets/_switch.py index a443d757fe..72602b395a 100644 --- a/src/textual/widgets/_switch.py +++ b/src/textual/widgets/_switch.py @@ -50,6 +50,7 @@ class Switch(Widget, can_focus=True): background: $surface; height: auto; width: auto; + padding: 0 2; &.-on .switch--slider { color: $success; diff --git a/src/textual/widgets/_tabbed_content.py b/src/textual/widgets/_tabbed_content.py index 2355c82275..37630b05dc 100644 --- a/src/textual/widgets/_tabbed_content.py +++ b/src/textual/widgets/_tabbed_content.py @@ -236,6 +236,7 @@ def _on_descendant_focus(self, event: events.DescendantFocus): class TabbedContent(Widget): """A container with associated tabs to toggle content visibility.""" + ALLOW_MAXIMIZE = True DEFAULT_CSS = """ TabbedContent { height: auto; diff --git a/src/textual/widgets/_tree.py b/src/textual/widgets/_tree.py index afda7a707c..79c64be224 100644 --- a/src/textual/widgets/_tree.py +++ b/src/textual/widgets/_tree.py @@ -785,6 +785,52 @@ def __init__( super().__init__(name=name, id=id, classes=classes, disabled=disabled) + def add_json(self, json_data: object, node: TreeNode | None = None) -> None: + """Adds JSON data to a node. + + Args: + json_data: An object decoded from JSON. + node: Node to add data to. + + """ + + if node is None: + node = self.root + + from rich.highlighter import ReprHighlighter + + highlighter = ReprHighlighter() + + def add_node(name: str, node: TreeNode, data: object) -> None: + """Adds a node to the tree. + + Args: + name: Name of the node. + node: Parent node. + data: Data associated with the node. + """ + if isinstance(data, dict): + node.set_label(Text(f"{{}} {name}")) + for key, value in data.items(): + new_node = node.add("") + add_node(key, new_node, value) + elif isinstance(data, list): + node.set_label(Text(f"[] {name}")) + for index, value in enumerate(data): + new_node = node.add("") + add_node(str(index), new_node, value) + else: + node.allow_expand = False + if name: + label = Text.assemble( + Text.from_markup(f"[b]{name}[/b]="), highlighter(repr(data)) + ) + else: + label = Text(repr(data)) + node.set_label(label) + + add_node("", node, json_data) + @property def cursor_node(self) -> TreeNode[TreeDataType] | None: """The currently selected node, or ``None`` if no selection.""" diff --git a/tests/css/test_nested_css.py b/tests/css/test_nested_css.py index e73d67e686..bd6b5714a8 100644 --- a/tests/css/test_nested_css.py +++ b/tests/css/test_nested_css.py @@ -72,10 +72,10 @@ async def test_lists_of_selectors_in_nested_css() -> None: class DeclarationAfterNestedApp(App[None]): CSS = """ Screen { + background: green; Label { background: red; - } - background: green; + } } """ diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_ansi_command_palette.svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_ansi_command_palette.svg index f720ab0264..806fe837e3 100644 --- a/tests/snapshot_tests/__snapshots__/test_snapshots/test_ansi_command_palette.svg +++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_ansi_command_palette.svg @@ -19,143 +19,143 @@ font-weight: 700; } - .terminal-1758253465-matrix { + .terminal-2368587539-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1758253465-title { + .terminal-2368587539-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1758253465-r1 { fill: #8a4346 } -.terminal-1758253465-r2 { fill: #868887 } -.terminal-1758253465-r3 { fill: #6b546f } -.terminal-1758253465-r4 { fill: #e0e0e0 } -.terminal-1758253465-r5 { fill: #292929 } -.terminal-1758253465-r6 { fill: #c5c8c6 } -.terminal-1758253465-r7 { fill: #0178d4 } -.terminal-1758253465-r8 { fill: #00ff00 } -.terminal-1758253465-r9 { fill: #000000 } -.terminal-1758253465-r10 { fill: #8d8d8d } -.terminal-1758253465-r11 { fill: #828482 } -.terminal-1758253465-r12 { fill: #e0e0e0;font-weight: bold } -.terminal-1758253465-r13 { fill: #a5a5a5 } + .terminal-2368587539-r1 { fill: #8a4346 } +.terminal-2368587539-r2 { fill: #868887 } +.terminal-2368587539-r3 { fill: #6b546f } +.terminal-2368587539-r4 { fill: #e0e0e0 } +.terminal-2368587539-r5 { fill: #292929 } +.terminal-2368587539-r6 { fill: #c5c8c6 } +.terminal-2368587539-r7 { fill: #0178d4 } +.terminal-2368587539-r8 { fill: #00ff00 } +.terminal-2368587539-r9 { fill: #000000 } +.terminal-2368587539-r10 { fill: #8d8d8d } +.terminal-2368587539-r11 { fill: #7e8486 } +.terminal-2368587539-r12 { fill: #e0e0e0;font-weight: bold } +.terminal-2368587539-r13 { fill: #a1a5a8 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - CommandPaletteApp + CommandPaletteApp - - - - RedMagentaRedMagentaRedMagentaRedMagentaRedMagentaRedMagentaRed -MagentaRedMagentaRedMagentaRedMagentaRedMagentaRedMagentaRed -MagentaRedMagentaRedMagentaRedMagentaRedMagentaRedMagentaRed -▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - -🔎Search for commands… - - -  Quit the application                                                           -Quit the application as soon as possible -  Save screenshot                                                                -Save an SVG 'screenshot' of the current screen -  Show keys and help panel                                                       -Show help for the focused widget and a summary of available keys -▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ -MagentaRedMagentaRedMagentaRedMagentaRedMagentaRedMagentaRed -MagentaRedMagentaRedMagentaRedMagentaRedMagentaRedMagentaRed -MagentaRedMagentaRedMagentaRedMagentaRedMagentaRedMagentaRed -MagentaRedMagentaRedMagentaRedMagentaRedMagentaRedMagentaRed -MagentaRedMagentaRedMagentaRedMagentaRedMagentaRedMagentaRed -MagentaRedMagentaRedMagentaRedMagentaRedMagentaRedMagentaRed -MagentaRedMagentaRedMagentaRedMagentaRedMagentaRedMagentaRed -MagentaRedMagentaRedMagentaRedMagentaRedMagentaRedMagentaRed -MagentaRedMagentaRedMagentaRedMagentaRedMagentaRedMagentaRed + + + + RedMagentaRedMagentaRedMagentaRedMagentaRedMagentaRedMagentaRed +MagentaRedMagentaRedMagentaRedMagentaRedMagentaRedMagentaRed +MagentaRedMagentaRedMagentaRedMagentaRedMagentaRedMagentaRed +▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + +🔎Search for commands… + + +  Quit the application                                                           +Quit the application as soon as possible +  Save screenshot                                                                +Save an SVG 'screenshot' of the current screen +  Show keys and help panel                                                       +Show help for the focused widget and a summary of available keys +▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ +MagentaRedMagentaRedMagentaRedMagentaRedMagentaRedMagentaRed +MagentaRedMagentaRedMagentaRedMagentaRedMagentaRedMagentaRed +MagentaRedMagentaRedMagentaRedMagentaRedMagentaRedMagentaRed +MagentaRedMagentaRedMagentaRedMagentaRedMagentaRedMagentaRed +MagentaRedMagentaRedMagentaRedMagentaRedMagentaRedMagentaRed +MagentaRedMagentaRedMagentaRedMagentaRedMagentaRedMagentaRed +MagentaRedMagentaRedMagentaRedMagentaRedMagentaRedMagentaRed +MagentaRedMagentaRedMagentaRedMagentaRedMagentaRedMagentaRed +MagentaRedMagentaRedMagentaRedMagentaRedMagentaRedMagentaRed diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_app_search_commands_opens_and_displays_search_list.svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_app_search_commands_opens_and_displays_search_list.svg index eb86226537..89e9f90784 100644 --- a/tests/snapshot_tests/__snapshots__/test_snapshots/test_app_search_commands_opens_and_displays_search_list.svg +++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_app_search_commands_opens_and_displays_search_list.svg @@ -19,138 +19,138 @@ font-weight: 700; } - .terminal-4097857637-matrix { + .terminal-1636454365-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-4097857637-title { + .terminal-1636454365-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-4097857637-r1 { fill: #646464 } -.terminal-4097857637-r2 { fill: #c5c8c6 } -.terminal-4097857637-r3 { fill: #0178d4 } -.terminal-4097857637-r4 { fill: #e0e0e0 } -.terminal-4097857637-r5 { fill: #00ff00 } -.terminal-4097857637-r6 { fill: #000000 } -.terminal-4097857637-r7 { fill: #121212 } -.terminal-4097857637-r8 { fill: #e0e0e0;font-weight: bold } -.terminal-4097857637-r9 { fill: #e0e0e0;font-weight: bold;text-decoration: underline; } + .terminal-1636454365-r1 { fill: #646464 } +.terminal-1636454365-r2 { fill: #c5c8c6 } +.terminal-1636454365-r3 { fill: #0178d4 } +.terminal-1636454365-r4 { fill: #e0e0e0 } +.terminal-1636454365-r5 { fill: #00ff00 } +.terminal-1636454365-r6 { fill: #000000 } +.terminal-1636454365-r7 { fill: #121212 } +.terminal-1636454365-r8 { fill: #e0e0e0;font-weight: bold } +.terminal-1636454365-r9 { fill: #e0e0e0;font-weight: bold;text-decoration: underline; } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - SearchApp + SearchApp - - - - Search Commands                                                                  - - -▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - -🔎b - - -bar                                                                            -baz                                                                            -▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - - - - - - - - - - - + + + + Search Commands                                                                  + + +▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + +🔎b + + +bar                                                                            +baz                                                                            +▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + + + + + + + + + diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_button_with_console_markup.svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_button_with_console_markup.svg index bf38bc9d0e..d264449edf 100644 --- a/tests/snapshot_tests/__snapshots__/test_snapshots/test_button_with_console_markup.svg +++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_button_with_console_markup.svg @@ -19,141 +19,141 @@ font-weight: 700; } - .terminal-3330596708-matrix { + .terminal-1193991823-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3330596708-title { + .terminal-1193991823-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3330596708-r1 { fill: #2d2d2d } -.terminal-3330596708-r2 { fill: #e0e0e0 } -.terminal-3330596708-r3 { fill: #c5c8c6 } -.terminal-3330596708-r4 { fill: #272727;font-weight: bold } -.terminal-3330596708-r5 { fill: #272727;font-weight: bold;font-style: italic; } -.terminal-3330596708-r6 { fill: #0d0d0d } -.terminal-3330596708-r7 { fill: #e0e0e0;font-weight: bold } -.terminal-3330596708-r8 { fill: #f4005f;font-weight: bold;font-style: italic; } -.terminal-3330596708-r9 { fill: #1e1e1e } -.terminal-3330596708-r10 { fill: #a2a2a2 } -.terminal-3330596708-r11 { fill: #5f0505;font-style: italic; } -.terminal-3330596708-r12 { fill: #0f0f0f } + .terminal-1193991823-r1 { fill: #2d2d2d } +.terminal-1193991823-r2 { fill: #e0e0e0 } +.terminal-1193991823-r3 { fill: #c5c8c6 } +.terminal-1193991823-r4 { fill: #272727;font-weight: bold } +.terminal-1193991823-r5 { fill: #272727;font-weight: bold;font-style: italic; } +.terminal-1193991823-r6 { fill: #0d0d0d } +.terminal-1193991823-r7 { fill: #e0e0e0;font-weight: bold } +.terminal-1193991823-r8 { fill: #f4005f;font-weight: bold;font-style: italic; } +.terminal-1193991823-r9 { fill: #1e1e1e } +.terminal-1193991823-r10 { fill: #6a6a6a;font-weight: bold } +.terminal-1193991823-r11 { fill: #5f0505;font-weight: bold;font-style: italic; } +.terminal-1193991823-r12 { fill: #0f0f0f } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - ButtonsWithMarkupApp + ButtonsWithMarkupApp - + - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ -Focused Button  -▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ -▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ -Blurred Button  -▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ -▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ -Disabled Button  -▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - - - - - - - - - - - - - + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +Focused Button  +▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ +▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +Blurred Button  +▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ +▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +Disabled Button  +▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + + + + + + + + + + + diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_buttons_render.svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_buttons_render.svg index b6cfb0fa78..3585fe3987 100644 --- a/tests/snapshot_tests/__snapshots__/test_snapshots/test_buttons_render.svg +++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_buttons_render.svg @@ -19,162 +19,162 @@ font-weight: 700; } - .terminal-3544116530-matrix { + .terminal-622193266-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3544116530-title { + .terminal-622193266-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3544116530-r1 { fill: #e0e0e0 } -.terminal-3544116530-r2 { fill: #c5c8c6 } -.terminal-3544116530-r3 { fill: #e0e0e0;font-weight: bold } -.terminal-3544116530-r4 { fill: #2d2d2d } -.terminal-3544116530-r5 { fill: #1e1e1e } -.terminal-3544116530-r6 { fill: #272727;font-weight: bold } -.terminal-3544116530-r7 { fill: #a2a2a2 } -.terminal-3544116530-r8 { fill: #0d0d0d } -.terminal-3544116530-r9 { fill: #0f0f0f } -.terminal-3544116530-r10 { fill: #6db2ff } -.terminal-3544116530-r11 { fill: #3e6085 } -.terminal-3544116530-r12 { fill: #ddedf9;font-weight: bold } -.terminal-3544116530-r13 { fill: #a0a8ae } -.terminal-3544116530-r14 { fill: #004295 } -.terminal-3544116530-r15 { fill: #082951 } -.terminal-3544116530-r16 { fill: #7ae998 } -.terminal-3544116530-r17 { fill: #447b53 } -.terminal-3544116530-r18 { fill: #0a180e;font-weight: bold } -.terminal-3544116530-r19 { fill: #0a120c } -.terminal-3544116530-r20 { fill: #008139 } -.terminal-3544116530-r21 { fill: #084724 } -.terminal-3544116530-r22 { fill: #ffcf56 } -.terminal-3544116530-r23 { fill: #856e32 } -.terminal-3544116530-r24 { fill: #211505;font-weight: bold } -.terminal-3544116530-r25 { fill: #150f08 } -.terminal-3544116530-r26 { fill: #b86b00 } -.terminal-3544116530-r27 { fill: #633d08 } -.terminal-3544116530-r28 { fill: #e76580 } -.terminal-3544116530-r29 { fill: #7a3a47 } -.terminal-3544116530-r30 { fill: #f5e5e9;font-weight: bold } -.terminal-3544116530-r31 { fill: #aca4a6 } -.terminal-3544116530-r32 { fill: #780028 } -.terminal-3544116530-r33 { fill: #43081c } + .terminal-622193266-r1 { fill: #e0e0e0 } +.terminal-622193266-r2 { fill: #c5c8c6 } +.terminal-622193266-r3 { fill: #e0e0e0;font-weight: bold } +.terminal-622193266-r4 { fill: #2d2d2d } +.terminal-622193266-r5 { fill: #1e1e1e } +.terminal-622193266-r6 { fill: #272727;font-weight: bold } +.terminal-622193266-r7 { fill: #6a6a6a;font-weight: bold } +.terminal-622193266-r8 { fill: #0d0d0d } +.terminal-622193266-r9 { fill: #0f0f0f } +.terminal-622193266-r10 { fill: #6db2ff } +.terminal-622193266-r11 { fill: #3e6085 } +.terminal-622193266-r12 { fill: #ddedf9;font-weight: bold } +.terminal-622193266-r13 { fill: #637f94;font-weight: bold } +.terminal-622193266-r14 { fill: #004295 } +.terminal-622193266-r15 { fill: #082951 } +.terminal-622193266-r16 { fill: #7ae998 } +.terminal-622193266-r17 { fill: #447b53 } +.terminal-622193266-r18 { fill: #0a180e;font-weight: bold } +.terminal-622193266-r19 { fill: #193320;font-weight: bold } +.terminal-622193266-r20 { fill: #008139 } +.terminal-622193266-r21 { fill: #084724 } +.terminal-622193266-r22 { fill: #ffcf56 } +.terminal-622193266-r23 { fill: #856e32 } +.terminal-622193266-r24 { fill: #211505;font-weight: bold } +.terminal-622193266-r25 { fill: #422d10;font-weight: bold } +.terminal-622193266-r26 { fill: #b86b00 } +.terminal-622193266-r27 { fill: #633d08 } +.terminal-622193266-r28 { fill: #e76580 } +.terminal-622193266-r29 { fill: #7a3a47 } +.terminal-622193266-r30 { fill: #f5e5e9;font-weight: bold } +.terminal-622193266-r31 { fill: #8f7178;font-weight: bold } +.terminal-622193266-r32 { fill: #780028 } +.terminal-622193266-r33 { fill: #43081c } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - ButtonsApp + ButtonsApp - + - - -Standard ButtonsDisabled Buttons - -▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - Default  Default  -▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - -▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - Primary!  Primary!  -▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - -▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - Success!  Success!  -▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - -▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - Warning!  Warning!  -▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - -▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - Error!  Error!  -▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - + + +Standard ButtonsDisabled Buttons + +▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + Default  Default  +▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + +▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + Primary!  Primary!  +▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + +▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + Success!  Success!  +▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + +▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + Warning!  Warning!  +▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + +▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + Error!  Error!  +▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_command_palette.svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_command_palette.svg index 03936e6c98..cba485475e 100644 --- a/tests/snapshot_tests/__snapshots__/test_snapshots/test_command_palette.svg +++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_command_palette.svg @@ -19,138 +19,138 @@ font-weight: 700; } - .terminal-621274872-matrix { + .terminal-566941328-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-621274872-title { + .terminal-566941328-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-621274872-r1 { fill: #646464 } -.terminal-621274872-r2 { fill: #c5c8c6 } -.terminal-621274872-r3 { fill: #0178d4 } -.terminal-621274872-r4 { fill: #e0e0e0 } -.terminal-621274872-r5 { fill: #00ff00 } -.terminal-621274872-r6 { fill: #000000 } -.terminal-621274872-r7 { fill: #121212 } -.terminal-621274872-r8 { fill: #e0e0e0;font-weight: bold } -.terminal-621274872-r9 { fill: #e0e0e0;font-weight: bold;text-decoration: underline; } + .terminal-566941328-r1 { fill: #646464 } +.terminal-566941328-r2 { fill: #c5c8c6 } +.terminal-566941328-r3 { fill: #0178d4 } +.terminal-566941328-r4 { fill: #e0e0e0 } +.terminal-566941328-r5 { fill: #00ff00 } +.terminal-566941328-r6 { fill: #000000 } +.terminal-566941328-r7 { fill: #121212 } +.terminal-566941328-r8 { fill: #e0e0e0;font-weight: bold } +.terminal-566941328-r9 { fill: #e0e0e0;font-weight: bold;text-decoration: underline; } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - CommandPaletteApp + CommandPaletteApp - - - - - - -▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - -🔎A - - -  This is a test of this code 9                                                  -  This is a test of this code 8                                                  -  This is a test of this code 7                                                  -  This is a test of this code 6                                                  -  This is a test of this code 5                                                  -  This is a test of this code 4                                                  -  This is a test of this code 3                                                  -  This is a test of this code 2                                                  -  This is a test of this code 1                                                  -  This is a test of this code 0                                                  -▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - - - + + + + + + +▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + +🔎A + + +  This is a test of this code 9                                                  +  This is a test of this code 8                                                  +  This is a test of this code 7                                                  +  This is a test of this code 6                                                  +  This is a test of this code 5                                                  +  This is a test of this code 4                                                  +  This is a test of this code 3                                                  +  This is a test of this code 2                                                  +  This is a test of this code 1                                                  +  This is a test of this code 0                                                  +▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_command_palette_discovery.svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_command_palette_discovery.svg index 94dc22828b..60c08e5cee 100644 --- a/tests/snapshot_tests/__snapshots__/test_snapshots/test_command_palette_discovery.svg +++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_command_palette_discovery.svg @@ -19,138 +19,138 @@ font-weight: 700; } - .terminal-2694158592-matrix { + .terminal-1531744096-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2694158592-title { + .terminal-1531744096-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2694158592-r1 { fill: #646464 } -.terminal-2694158592-r2 { fill: #c5c8c6 } -.terminal-2694158592-r3 { fill: #0178d4 } -.terminal-2694158592-r4 { fill: #e0e0e0 } -.terminal-2694158592-r5 { fill: #00ff00 } -.terminal-2694158592-r6 { fill: #000000 } -.terminal-2694158592-r7 { fill: #121212 } -.terminal-2694158592-r8 { fill: #737373 } -.terminal-2694158592-r9 { fill: #e0e0e0;font-weight: bold } + .terminal-1531744096-r1 { fill: #646464 } +.terminal-1531744096-r2 { fill: #c5c8c6 } +.terminal-1531744096-r3 { fill: #0178d4 } +.terminal-1531744096-r4 { fill: #e0e0e0 } +.terminal-1531744096-r5 { fill: #00ff00 } +.terminal-1531744096-r6 { fill: #000000 } +.terminal-1531744096-r7 { fill: #121212 } +.terminal-1531744096-r8 { fill: #6d7479 } +.terminal-1531744096-r9 { fill: #e0e0e0;font-weight: bold } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - CommandPaletteApp + CommandPaletteApp - - - - - - -▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - -🔎Search for commands… - - -  This is a test of this code 0                                                  -  This is a test of this code 1                                                  -  This is a test of this code 2                                                  -  This is a test of this code 3                                                  -  This is a test of this code 4                                                  -  This is a test of this code 5                                                  -  This is a test of this code 6                                                  -  This is a test of this code 7                                                  -  This is a test of this code 8                                                  -  This is a test of this code 9                                                  -▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - - - + + + + + + +▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + +🔎Search for commands… + + +  This is a test of this code 0                                                  +  This is a test of this code 1                                                  +  This is a test of this code 2                                                  +  This is a test of this code 3                                                  +  This is a test of this code 4                                                  +  This is a test of this code 5                                                  +  This is a test of this code 6                                                  +  This is a test of this code 7                                                  +  This is a test of this code 8                                                  +  This is a test of this code 9                                                  +▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_digits.svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_digits.svg index 1adf5d0eb8..d1065cf748 100644 --- a/tests/snapshot_tests/__snapshots__/test_snapshots/test_digits.svg +++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_digits.svg @@ -19,133 +19,133 @@ font-weight: 700; } - .terminal-3814412032-matrix { + .terminal-2360037657-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3814412032-title { + .terminal-2360037657-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3814412032-r1 { fill: #e0e0e0 } -.terminal-3814412032-r2 { fill: #c5c8c6 } -.terminal-3814412032-r3 { fill: #e0e0e0;font-weight: bold } + .terminal-2360037657-r1 { fill: #e0e0e0 } +.terminal-2360037657-r2 { fill: #c5c8c6 } +.terminal-2360037657-r3 { fill: #e0e0e0;font-weight: bold } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - DigitApp + DigitApp - + - - ╶─╮ ╶╮ ╷ ╷╶╮ ╭─╴╭─╮╶─╮╭─╴╭─╴╶─╮╭─╴╭─╮                                            - ─┤  │ ╰─┤ │ ╰─╮╰─┤┌─┘├─╮╰─╮ ─┤╰─╮╰─┤                                            -╶─╯•╶┴╴  ╵╶┴╴╶─╯╶─╯╰─╴╰─╯╶─╯╶─╯╶─╯╶─╯                                            -             ╭─╮╶╮ ╶─╮╶─╮╷ ╷╭─╴╭─╴╶─┐╭─╮╭─╮        ╭─╮┌─╮╭─╮┌─╮╭─╴╭─╴            -             │ │ │ ┌─┘ ─┤╰─┤╰─╮├─╮  │├─┤╰─┤╶┼╴╶─╴  ├─┤├─┤│  │ │├─ ├─             -             ╰─╯╶┴╴╰─╴╶─╯  ╵╶─╯╰─╯  ╵╰─╯╶─╯      •,╵ ╵└─╯╰─╯└─╯╰─╴╵              -             ┏━┓ ┓ ╺━┓╺━┓╻ ╻┏━╸┏━╸╺━┓┏━┓┏━┓        ╭─╮┌─╮╭─╮┌─╮╭─╴╭─╴            -             ┃ ┃ ┃ ┏━┛ ━┫┗━┫┗━┓┣━┓  ┃┣━┫┗━┫╺╋╸╺━╸  ├─┤├─┤│  │ │├─ ├─             -             ┗━┛╺┻╸┗━╸╺━┛  ╹╺━┛┗━┛  ╹┗━┛╺━┛      •,╵ ╵└─╯╰─╯└─╯╰─╴╵              -                                                              ╶─╮   ╶╮ ╭─╮ ^ ╷ ╷ -                                                               ─┤ ×  │ │ │   ╰─┤ -                                                              ╶─╯   ╶┴╴╰─╯     ╵ -                                                              ╶─╮   ╶╮ ╭─╮ ^ ╷ ╷ -                                                               ─┤ ×  │ │ │   ╰─┤ -                                                              ╶─╯   ╶┴╴╰─╯     ╵ -╭╴ ╭╫╮╶╮ ╶─╮╶─╮ ╷ ╷╭─╴ ╶╮                                                        -│  ╰╫╮ │ ┌─┘ ─┤ ╰─┤╰─╮  │                                                        -╰╴ ╰╫╯╶┴╴╰─╴╶─╯•  ╵╶─╯ ╶╯                                                        -╭─╮╶╮ ╶─╮╶─╮ ╷ ╷╭─╴                                                              -╪═  │ ┌─┘ ─┤ ╰─┤╰─╮                                                              -┴─╴╶┴╴╰─╴╶─╯•  ╵╶─╯                                                              -╭─╮╶╮ ╶─╮╶─╮ ╷ ╷╭─╴                                                              -╪═  │ ┌─┘ ─┤ ╰─┤╰─╮                                                              -╰─╯╶┴╴╰─╴╶─╯•  ╵╶─╯                                                              + + ╶─╮ ╶╮ ╷ ╷╶╮ ╭─╴╭─╮╶─╮╭─╴╭─╴╶─╮╭─╴╭─╮                                            + ─┤  │ ╰─┤ │ ╰─╮╰─┤┌─┘├─╮╰─╮ ─┤╰─╮╰─┤                                            +╶─╯•╶┴╴  ╵╶┴╴╶─╯╶─╯╰─╴╰─╯╶─╯╶─╯╶─╯╶─╯                                            +             ╭─╮╶╮ ╶─╮╶─╮╷ ╷╭─╴╭─╴╶─┐╭─╮╭─╮        ╭─╮┌─╮╭─╮┌─╮╭─╴╭─╴            +             │ │ │ ┌─┘ ─┤╰─┤╰─╮├─╮  │├─┤╰─┤╶┼╴╶─╴  ├─┤├─┤│  │ │├─ ├─             +             ╰─╯╶┴╴╰─╴╶─╯  ╵╶─╯╰─╯  ╵╰─╯╶─╯      •,╵ ╵└─╯╰─╯└─╯╰─╴╵              +             ┏━┓╺┓ ╺━┓╺━┓╻ ╻┏━╸┏━╸╺━┓┏━┓┏━┓        ╭─╮┌─╮╭─╮┌─╮╭─╴╭─╴            +             ┃ ┃ ┃ ┏━┛ ━┫┗━┫┗━┓┣━┓  ┃┣━┫┗━┫╺╋╸╺━╸  ├─┤├─┤│  │ │├─ ├─             +             ┗━┛╺┻╸┗━╸╺━┛  ╹╺━┛┗━┛  ╹┗━┛╺━┛      •,╵ ╵└─╯╰─╯└─╯╰─╴╵              +                                                              ╶─╮   ╶╮ ╭─╮ ^ ╷ ╷ +                                                               ─┤ ×  │ │ │   ╰─┤ +                                                              ╶─╯   ╶┴╴╰─╯     ╵ +                                                              ╶─╮   ╶╮ ╭─╮ ^ ╷ ╷ +                                                               ─┤ ×  │ │ │   ╰─┤ +                                                              ╶─╯   ╶┴╴╰─╯     ╵ +╭╴ ╭╫╮╶╮ ╶─╮╶─╮ ╷ ╷╭─╴ ╶╮                                                        +│  ╰╫╮ │ ┌─┘ ─┤ ╰─┤╰─╮  │                                                        +╰╴ ╰╫╯╶┴╴╰─╴╶─╯•  ╵╶─╯ ╶╯                                                        +╭─╮╶╮ ╶─╮╶─╮ ╷ ╷╭─╴                                                              +╪═  │ ┌─┘ ─┤ ╰─┤╰─╮                                                              +┷━╸╶┴╴╰─╴╶─╯•  ╵╶─╯                                                              +╭─╮╶╮ ╶─╮╶─╮ ╷ ╷╭─╴                                                              +╪═  │ ┌─┘ ─┤ ╰─┤╰─╮                                                              +╰─╯╶┴╴╰─╴╶─╯•  ╵╶─╯                                                              diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_disabled_widgets.svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_disabled_widgets.svg index af81a1c33a..127e3a4dcc 100644 --- a/tests/snapshot_tests/__snapshots__/test_snapshots/test_disabled_widgets.svg +++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_disabled_widgets.svg @@ -19,162 +19,162 @@ font-weight: 700; } - .terminal-1595776013-matrix { + .terminal-1418563853-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1595776013-title { + .terminal-1418563853-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1595776013-r1 { fill: #2d2d2d } -.terminal-1595776013-r2 { fill: #6db2ff } -.terminal-1595776013-r3 { fill: #7ae998 } -.terminal-1595776013-r4 { fill: #ffcf56 } -.terminal-1595776013-r5 { fill: #e76580 } -.terminal-1595776013-r6 { fill: #c5c8c6 } -.terminal-1595776013-r7 { fill: #272727;font-weight: bold } -.terminal-1595776013-r8 { fill: #ddedf9;font-weight: bold } -.terminal-1595776013-r9 { fill: #0a180e;font-weight: bold } -.terminal-1595776013-r10 { fill: #211505;font-weight: bold } -.terminal-1595776013-r11 { fill: #f5e5e9;font-weight: bold } -.terminal-1595776013-r12 { fill: #0d0d0d } -.terminal-1595776013-r13 { fill: #004295 } -.terminal-1595776013-r14 { fill: #008139 } -.terminal-1595776013-r15 { fill: #b86b00 } -.terminal-1595776013-r16 { fill: #780028 } -.terminal-1595776013-r17 { fill: #1e1e1e } -.terminal-1595776013-r18 { fill: #3e6085 } -.terminal-1595776013-r19 { fill: #447b53 } -.terminal-1595776013-r20 { fill: #856e32 } -.terminal-1595776013-r21 { fill: #7a3a47 } -.terminal-1595776013-r22 { fill: #a2a2a2 } -.terminal-1595776013-r23 { fill: #a0a8ae } -.terminal-1595776013-r24 { fill: #0a120c } -.terminal-1595776013-r25 { fill: #150f08 } -.terminal-1595776013-r26 { fill: #aca4a6 } -.terminal-1595776013-r27 { fill: #0f0f0f } -.terminal-1595776013-r28 { fill: #082951 } -.terminal-1595776013-r29 { fill: #084724 } -.terminal-1595776013-r30 { fill: #633d08 } -.terminal-1595776013-r31 { fill: #43081c } -.terminal-1595776013-r32 { fill: #e0e0e0;font-weight: bold } + .terminal-1418563853-r1 { fill: #2d2d2d } +.terminal-1418563853-r2 { fill: #6db2ff } +.terminal-1418563853-r3 { fill: #7ae998 } +.terminal-1418563853-r4 { fill: #ffcf56 } +.terminal-1418563853-r5 { fill: #e76580 } +.terminal-1418563853-r6 { fill: #c5c8c6 } +.terminal-1418563853-r7 { fill: #272727;font-weight: bold } +.terminal-1418563853-r8 { fill: #ddedf9;font-weight: bold } +.terminal-1418563853-r9 { fill: #0a180e;font-weight: bold } +.terminal-1418563853-r10 { fill: #211505;font-weight: bold } +.terminal-1418563853-r11 { fill: #f5e5e9;font-weight: bold } +.terminal-1418563853-r12 { fill: #0d0d0d } +.terminal-1418563853-r13 { fill: #004295 } +.terminal-1418563853-r14 { fill: #008139 } +.terminal-1418563853-r15 { fill: #b86b00 } +.terminal-1418563853-r16 { fill: #780028 } +.terminal-1418563853-r17 { fill: #1e1e1e } +.terminal-1418563853-r18 { fill: #3e6085 } +.terminal-1418563853-r19 { fill: #447b53 } +.terminal-1418563853-r20 { fill: #856e32 } +.terminal-1418563853-r21 { fill: #7a3a47 } +.terminal-1418563853-r22 { fill: #6a6a6a;font-weight: bold } +.terminal-1418563853-r23 { fill: #637f94;font-weight: bold } +.terminal-1418563853-r24 { fill: #193320;font-weight: bold } +.terminal-1418563853-r25 { fill: #422d10;font-weight: bold } +.terminal-1418563853-r26 { fill: #8f7178;font-weight: bold } +.terminal-1418563853-r27 { fill: #0f0f0f } +.terminal-1418563853-r28 { fill: #082951 } +.terminal-1418563853-r29 { fill: #084724 } +.terminal-1418563853-r30 { fill: #633d08 } +.terminal-1418563853-r31 { fill: #43081c } +.terminal-1418563853-r32 { fill: #e0e0e0;font-weight: bold } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - WidgetDisableTestApp + WidgetDisableTestApp - + - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - Button  Button  Button  Button  Button  -▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ -▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - Button  Button  Button  Button  Button  -▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ -▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - Button  Button  Button  Button  Button  -▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ -▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - Button  Button  Button  Button  Button  -▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ -▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - Button  Button  Button  Button  Button  -▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ -▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - Button  Button  Button  Button  Button  -▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ -▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - Button  Button  Button  Button  Button  -▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ -▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - Button  Button  Button  Button  Button  -▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + Button  Button  Button  Button  Button  +▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ +▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + Button  Button  Button  Button  Button  +▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ +▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + Button  Button  Button  Button  Button  +▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ +▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + Button  Button  Button  Button  Button  +▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ +▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + Button  Button  Button  Button  Button  +▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ +▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + Button  Button  Button  Button  Button  +▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ +▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + Button  Button  Button  Button  Button  +▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ +▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + Button  Button  Button  Button  Button  +▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_programmatic_disable_button.svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_programmatic_disable_button.svg index 20d0e52bcd..af58d14524 100644 --- a/tests/snapshot_tests/__snapshots__/test_snapshots/test_programmatic_disable_button.svg +++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_programmatic_disable_button.svg @@ -19,137 +19,137 @@ font-weight: 700; } - .terminal-2160157952-matrix { + .terminal-2528666811-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2160157952-title { + .terminal-2528666811-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2160157952-r1 { fill: #e0e0e0 } -.terminal-2160157952-r2 { fill: #c5c8c6 } -.terminal-2160157952-r3 { fill: #1e1e1e } -.terminal-2160157952-r4 { fill: #a2a2a2 } -.terminal-2160157952-r5 { fill: #0f0f0f } -.terminal-2160157952-r6 { fill: #ffa62b;font-weight: bold } -.terminal-2160157952-r7 { fill: #495259 } + .terminal-2528666811-r1 { fill: #e0e0e0 } +.terminal-2528666811-r2 { fill: #c5c8c6 } +.terminal-2528666811-r3 { fill: #1e1e1e } +.terminal-2528666811-r4 { fill: #6a6a6a;font-weight: bold } +.terminal-2528666811-r5 { fill: #0f0f0f } +.terminal-2528666811-r6 { fill: #ffa62b;font-weight: bold } +.terminal-2528666811-r7 { fill: #495259 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - ExampleApp + ExampleApp - + - - - - - - - - - - -                        Hover the button then hit space                          -▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - Disabled  -▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - - - - - - - - - - space Toggle Button                                                ^p palette + + + + + + + + + + +                        Hover the button then hit space                          +▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + Disabled  +▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + + + + + + + + space Toggle Button                                                ^p palette diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_system_commands.svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_system_commands.svg index 3f88acaab0..dbb425da5e 100644 --- a/tests/snapshot_tests/__snapshots__/test_snapshots/test_system_commands.svg +++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_system_commands.svg @@ -19,164 +19,164 @@ font-weight: 700; } - .terminal-4115527248-matrix { + .terminal-3076581726-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-4115527248-title { + .terminal-3076581726-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-4115527248-r1 { fill: #121212 } -.terminal-4115527248-r2 { fill: #0b3a5f } -.terminal-4115527248-r3 { fill: #c5c8c6 } -.terminal-4115527248-r4 { fill: #e0e0e0 } -.terminal-4115527248-r5 { fill: #0178d4 } -.terminal-4115527248-r6 { fill: #00ff00 } -.terminal-4115527248-r7 { fill: #000000 } -.terminal-4115527248-r8 { fill: #737373 } -.terminal-4115527248-r9 { fill: #e0e0e0;font-weight: bold } -.terminal-4115527248-r10 { fill: #a5a5a5 } -.terminal-4115527248-r11 { fill: #646464 } + .terminal-3076581726-r1 { fill: #121212 } +.terminal-3076581726-r2 { fill: #0b3a5f } +.terminal-3076581726-r3 { fill: #c5c8c6 } +.terminal-3076581726-r4 { fill: #e0e0e0 } +.terminal-3076581726-r5 { fill: #0178d4 } +.terminal-3076581726-r6 { fill: #00ff00 } +.terminal-3076581726-r7 { fill: #000000 } +.terminal-3076581726-r8 { fill: #6d7479 } +.terminal-3076581726-r9 { fill: #e0e0e0;font-weight: bold } +.terminal-3076581726-r10 { fill: #a1a5a8 } +.terminal-3076581726-r11 { fill: #646464 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - SimpleApp + SimpleApp - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - -▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ -▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - -🔎Search for commands… - - -  Change theme                                                                                       -Change the current theme -  Maximize                                                                                           -Maximize the focused widget -  Quit the application                                                                               -Quit the application as soon as possible -  Save screenshot                                                                                    -Save an SVG 'screenshot' of the current screen -  Show keys and help panel                                                                           -Show help for the focused widget and a summary of available keys -▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - - - - - - - - - + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + +▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ +▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + +🔎Search for commands… + + +  Change theme                                                                                       +Change the current theme +  Maximize                                                                                           +Maximize the focused widget +  Quit the application                                                                               +Quit the application as soon as possible +  Save screenshot                                                                                    +Save an SVG 'screenshot' of the current screen +  Show keys and help panel                                                                           +Show help for the focused widget and a summary of available keys +▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + + + + + + + diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_tabbed_content_with_modified_tabs.svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_tabbed_content_with_modified_tabs.svg index 6331c437bd..14eb0e779b 100644 --- a/tests/snapshot_tests/__snapshots__/test_snapshots/test_tabbed_content_with_modified_tabs.svg +++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_tabbed_content_with_modified_tabs.svg @@ -19,139 +19,139 @@ font-weight: 700; } - .terminal-3843185498-matrix { + .terminal-3443677784-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3843185498-title { + .terminal-3443677784-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3843185498-r1 { fill: #c5c8c6 } -.terminal-3843185498-r2 { fill: #ddedf9;font-weight: bold } -.terminal-3843185498-r3 { fill: #454545 } -.terminal-3843185498-r4 { fill: #797979 } -.terminal-3843185498-r5 { fill: #e0e0e0 } -.terminal-3843185498-r6 { fill: #4f4f4f } -.terminal-3843185498-r7 { fill: #0178d4 } -.terminal-3843185498-r8 { fill: #981515 } -.terminal-3843185498-r9 { fill: #e99c9c } -.terminal-3843185498-r10 { fill: #880606 } + .terminal-3443677784-r1 { fill: #c5c8c6 } +.terminal-3443677784-r2 { fill: #ddedf9;font-weight: bold } +.terminal-3443677784-r3 { fill: #454545 } +.terminal-3443677784-r4 { fill: #797979 } +.terminal-3443677784-r5 { fill: #e0e0e0 } +.terminal-3443677784-r6 { fill: #4f4f4f } +.terminal-3443677784-r7 { fill: #0178d4 } +.terminal-3443677784-r8 { fill: #981515 } +.terminal-3443677784-r9 { fill: #c56363;font-weight: bold } +.terminal-3443677784-r10 { fill: #880606 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - FiddleWithTabsApp + FiddleWithTabsApp - + - - Tab 1Tab 2Tab 4Tab 5 -━━━━━╺━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - Button  -▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - - - - - - - - - - - - - - - - - + + Tab 1Tab 2Tab 4Tab 5 +━━━━━╺━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + Button  +▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + + + + + + + + + + + + + + + diff --git a/tests/snapshot_tests/snapshot_apps/dock_scroll_off_by_one.py b/tests/snapshot_tests/snapshot_apps/dock_scroll_off_by_one.py index 563cd0aba9..afbbf0966c 100644 --- a/tests/snapshot_tests/snapshot_apps/dock_scroll_off_by_one.py +++ b/tests/snapshot_tests/snapshot_apps/dock_scroll_off_by_one.py @@ -4,7 +4,6 @@ class ScrollOffByOne(App): AUTO_FOCUS = None - HOVER_EFFECTS_SCROLL_PAUSE = 0.0 def compose(self) -> ComposeResult: for number in range(1, 100): diff --git a/tests/snapshot_tests/snapshot_apps/scroll_to.py b/tests/snapshot_tests/snapshot_apps/scroll_to.py index bded6e5ff4..986af6701f 100644 --- a/tests/snapshot_tests/snapshot_apps/scroll_to.py +++ b/tests/snapshot_tests/snapshot_apps/scroll_to.py @@ -6,7 +6,6 @@ class ScrollOffByOne(App): """Scroll to item 50.""" AUTO_FOCUS = None - HOVER_EFFECTS_SCROLL_PAUSE = 0 def compose(self) -> ComposeResult: for number in range(1, 100): diff --git a/tests/test_driver.py b/tests/test_driver.py index 0c37f6c86f..764c9930da 100644 --- a/tests/test_driver.py +++ b/tests/test_driver.py @@ -18,8 +18,8 @@ def handle(self, event): app = MyApp() async with app.run_test() as pilot: - app._driver.process_message(MouseDown(0, 0, 0, 0, 1, False, False, False)) - app._driver.process_message(MouseUp(0, 0, 0, 0, 1, False, False, False)) + app._driver.process_message(MouseDown(None, 0, 0, 0, 0, 1, False, False, False)) + app._driver.process_message(MouseUp(None, 0, 0, 0, 0, 1, False, False, False)) await pilot.pause() assert len(app.messages) == 3 assert isinstance(app.messages[0], MouseDown) @@ -41,8 +41,8 @@ def on_button_pressed(self, event): app = MyApp() async with app.run_test() as pilot: - app._driver.process_message(MouseDown(0, 0, 0, 0, 1, False, False, False)) - app._driver.process_message(MouseUp(0, 0, 0, 0, 1, False, False, False)) + app._driver.process_message(MouseDown(None, 0, 0, 0, 0, 1, False, False, False)) + app._driver.process_message(MouseUp(None, 0, 0, 0, 0, 1, False, False, False)) await pilot.pause() assert len(app.messages) == 1 @@ -69,9 +69,10 @@ def on_button_pressed(self, event): assert (width, height) == (button_width, button_height) # Mouse down on the button, then move the mouse inside the button, then mouse up. - app._driver.process_message(MouseDown(0, 0, 0, 0, 1, False, False, False)) + app._driver.process_message(MouseDown(None, 0, 0, 0, 0, 1, False, False, False)) app._driver.process_message( MouseUp( + None, button_width - 1, button_height - 1, button_width - 1, @@ -108,9 +109,10 @@ def on_button_pressed(self, event): assert (width, height) == (button_width, button_height) # Mouse down on the button, then move the mouse outside the button, then mouse up. - app._driver.process_message(MouseDown(0, 0, 0, 0, 1, False, False, False)) + app._driver.process_message(MouseDown(None, 0, 0, 0, 0, 1, False, False, False)) app._driver.process_message( MouseUp( + None, button_width + 1, button_height + 1, button_width + 1, diff --git a/tests/test_lazy.py b/tests/test_lazy.py index 5beb591855..e862d0ce1f 100644 --- a/tests/test_lazy.py +++ b/tests/test_lazy.py @@ -39,10 +39,10 @@ async def test_lazy_reveal(): async with app.run_test() as pilot: # No #foo on initial mount - # Only first child should be visible initially + # Only first child should be available initially assert app.query_one("#foo").display - assert not app.query_one("#bar").display - assert not app.query_one("#baz").display + # Next two aren't mounted yet + assert not app.query("#baz") # All children should be visible after a pause await pilot.pause()