From ed4d811451226cac847af0d7302f71876df39595 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Tue, 9 May 2023 14:55:18 +0100 Subject: [PATCH 01/30] Add tests for Screen auto focus. Related issues: #2457. --- tests/test_screens.py | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/tests/test_screens.py b/tests/test_screens.py index 6825c101c..7e20348a9 100644 --- a/tests/test_screens.py +++ b/tests/test_screens.py @@ -6,6 +6,7 @@ import pytest from textual.app import App, ScreenStackError from textual.screen import Screen +from textual.widgets import Button, Input skip_py310 = pytest.mark.skipif( sys.version_info.minor == 10 and sys.version_info.major == 3, @@ -150,3 +151,44 @@ async def test_screens(): screen2.remove() screen3.remove() await app._shutdown() + + +async def test_auto_focus(): + class MyScreen(Screen[None]): + def compose(self) -> None: + print("composing") + yield Button() + yield Input(id="one") + yield Input(id="two") + + class MyApp(App[None]): + pass + + app = MyApp() + async with app.run_test(): + await app.push_screen(MyScreen()) + assert isinstance(app.focused, Button) + app.pop_screen() + + MyScreen.auto_focus = None + await app.push_screen(MyScreen()) + assert app.focused is None + app.pop_screen() + + MyScreen.auto_focus = "Input" + await app.push_screen(MyScreen()) + assert isinstance(app.focused, Input) + assert app.focused.id == "one" + app.pop_screen() + + MyScreen.auto_focus = "#two" + await app.push_screen(MyScreen()) + assert isinstance(app.focused, Input) + assert app.focused.id == "two" + + # If we push and pop another screen, focus should be preserved for #two. + MyScreen.auto_focus = None + await app.push_screen(MyScreen()) + assert app.focused is None + app.pop_screen() + assert app.focused.id == "two" From 8d3f69a04d49d1e4e83db1c573d1232f7201aede Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Tue, 9 May 2023 14:57:50 +0100 Subject: [PATCH 02/30] Add auto_focus attribute to screens. --- CHANGELOG.md | 7 +++++++ src/textual/screen.py | 15 ++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aa2923fac..d5f95b549 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ 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 + +### Added + +- Attribute `auto_focus` to screens https://github.com/Textualize/textual/issues/2457 + ## [0.24.1] - 2023-05-08 ### Fixed diff --git a/src/textual/screen.py b/src/textual/screen.py index 34db473ef..8bc9c0532 100644 --- a/src/textual/screen.py +++ b/src/textual/screen.py @@ -30,7 +30,7 @@ from ._types import CallbackType from .binding import Binding from .css.match import match from .css.parse import parse_selectors -from .css.query import QueryType +from .css.query import NoMatches, QueryType from .dom import DOMNode from .geometry import Offset, Region, Size from .reactive import Reactive @@ -101,6 +101,12 @@ class Screen(Generic[ScreenResultType], Widget): } """ + auto_focus: str | None = "*" + """A selector to determine what to focus automatically when the screen is activated. + + The widget focused is the first that matches the given [CSS selector](/guide/queries/#query-selectors). + Set to `None` to disable auto focus. + """ focused: Reactive[Widget | None] = Reactive(None) """The focused [widget][textual.widget.Widget] or `None` for no focus.""" stack_updates: Reactive[int] = Reactive(0, repaint=False) @@ -659,6 +665,13 @@ class Screen(Generic[ScreenResultType], Widget): """Screen has resumed.""" self.stack_updates += 1 size = self.app.size + if self.auto_focus is not None and self.focused is None: + try: + to_focus = self.query(self.auto_focus).first() + except NoMatches: + pass + else: + self.set_focus(to_focus) self._refresh_layout(size, full=True) self.refresh() From eafe6b1786490679b4acb85dfbf3978e54a2cc4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Tue, 9 May 2023 15:56:28 +0100 Subject: [PATCH 03/30] Moving child before/after self is a no-op. Related issues: #1743. --- CHANGELOG.md | 7 +++++++ src/textual/widget.py | 15 +++++++-------- tests/test_widget_child_moving.py | 8 ++------ 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aa2923fac..df66182ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ 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 + +### Changed + +- Using `Widget.move_child` where the target and the child being moved are the same is now a no-op https://github.com/Textualize/textual/issues/1743 + ## [0.24.1] - 2023-05-08 ### Fixed diff --git a/src/textual/widget.py b/src/textual/widget.py index 4fc364ebf..7103c630c 100644 --- a/src/textual/widget.py +++ b/src/textual/widget.py @@ -795,19 +795,15 @@ class Widget(DOMNode): Args: child: The child widget to move. - before: Optional location to move before. An `int` is the index - of the child to move before, a `str` is a `query_one` query to - find the widget to move before. - after: Optional location to move after. An `int` is the index - of the child to move after, a `str` is a `query_one` query to - find the widget to move after. + before: Child widget or location index to move before. + after: Child widget or location index to move after. Raises: WidgetError: If there is a problem with the child or target. Note: - Only one of ``before`` or ``after`` can be provided. If neither - or both are provided a ``WidgetError`` will be raised. + Only one of `before` or `after` can be provided. If neither + or both are provided a `WidgetError` will be raised. """ # One or the other of before or after are required. Can't do @@ -817,6 +813,9 @@ class Widget(DOMNode): elif before is not None and after is not None: raise WidgetError("Only one of `before` or `after` can be handled.") + if child is before or child is after: + return + def _to_widget(child: int | Widget, called: str) -> Widget: """Ensure a given child reference is a Widget.""" if isinstance(child, int): diff --git a/tests/test_widget_child_moving.py b/tests/test_widget_child_moving.py index 520ef7810..f2d40a4aa 100644 --- a/tests/test_widget_child_moving.py +++ b/tests/test_widget_child_moving.py @@ -42,22 +42,18 @@ async def test_move_child_to_outside() -> None: pilot.app.screen.move_child(child, before=Widget()) -@pytest.mark.xfail( - strict=True, reason="https://github.com/Textualize/textual/issues/1743" -) async def test_move_child_before_itself() -> None: """Test moving a widget before itself.""" + async with App().run_test() as pilot: child = Widget(Widget()) await pilot.app.mount(child) pilot.app.screen.move_child(child, before=child) -@pytest.mark.xfail( - strict=True, reason="https://github.com/Textualize/textual/issues/1743" -) async def test_move_child_after_itself() -> None: """Test moving a widget after itself.""" + # Regression test for https://github.com/Textualize/textual/issues/1743 async with App().run_test() as pilot: child = Widget(Widget()) await pilot.app.mount(child) From 3245eb38bb5f6a4a95fbe282f30b88288c49505f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Tue, 9 May 2023 16:44:37 +0100 Subject: [PATCH 04/30] Make auto-focus a class var. Related comments: https://github.com/Textualize/textual/pull/2527\#discussion_r1188776849 --- src/textual/screen.py | 19 ++++++++++--------- tests/test_screens.py | 8 ++++---- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/textual/screen.py b/src/textual/screen.py index 8bc9c0532..af0b006be 100644 --- a/src/textual/screen.py +++ b/src/textual/screen.py @@ -9,6 +9,7 @@ from typing import ( TYPE_CHECKING, Awaitable, Callable, + ClassVar, Generic, Iterable, Iterator, @@ -93,6 +94,13 @@ class ResultCallback(Generic[ScreenResultType]): class Screen(Generic[ScreenResultType], Widget): """The base class for screens.""" + AUTO_FOCUS: ClassVar[str | None] = "*" + """A selector to determine what to focus automatically when the screen is activated. + + The widget focused is the first that matches the given [CSS selector](/guide/queries/#query-selectors). + Set to `None` to disable auto focus. + """ + DEFAULT_CSS = """ Screen { layout: vertical; @@ -100,13 +108,6 @@ class Screen(Generic[ScreenResultType], Widget): background: $surface; } """ - - auto_focus: str | None = "*" - """A selector to determine what to focus automatically when the screen is activated. - - The widget focused is the first that matches the given [CSS selector](/guide/queries/#query-selectors). - Set to `None` to disable auto focus. - """ focused: Reactive[Widget | None] = Reactive(None) """The focused [widget][textual.widget.Widget] or `None` for no focus.""" stack_updates: Reactive[int] = Reactive(0, repaint=False) @@ -665,9 +666,9 @@ class Screen(Generic[ScreenResultType], Widget): """Screen has resumed.""" self.stack_updates += 1 size = self.app.size - if self.auto_focus is not None and self.focused is None: + if self.AUTO_FOCUS is not None and self.focused is None: try: - to_focus = self.query(self.auto_focus).first() + to_focus = self.query(self.AUTO_FOCUS).first() except NoMatches: pass else: diff --git a/tests/test_screens.py b/tests/test_screens.py index 7e20348a9..2e3dbfcbe 100644 --- a/tests/test_screens.py +++ b/tests/test_screens.py @@ -170,24 +170,24 @@ async def test_auto_focus(): assert isinstance(app.focused, Button) app.pop_screen() - MyScreen.auto_focus = None + MyScreen.AUTO_FOCUS = None await app.push_screen(MyScreen()) assert app.focused is None app.pop_screen() - MyScreen.auto_focus = "Input" + MyScreen.AUTO_FOCUS = "Input" await app.push_screen(MyScreen()) assert isinstance(app.focused, Input) assert app.focused.id == "one" app.pop_screen() - MyScreen.auto_focus = "#two" + MyScreen.AUTO_FOCUS = "#two" await app.push_screen(MyScreen()) assert isinstance(app.focused, Input) assert app.focused.id == "two" # If we push and pop another screen, focus should be preserved for #two. - MyScreen.auto_focus = None + MyScreen.AUTO_FOCUS = None await app.push_screen(MyScreen()) assert app.focused is None app.pop_screen() From 17af4735588a657f465cea01cfdd136b980ba677 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Tue, 9 May 2023 16:58:56 +0100 Subject: [PATCH 05/30] Annotate no-op. --- src/textual/widget.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/textual/widget.py b/src/textual/widget.py index 7103c630c..f2f99ba42 100644 --- a/src/textual/widget.py +++ b/src/textual/widget.py @@ -813,7 +813,7 @@ class Widget(DOMNode): elif before is not None and after is not None: raise WidgetError("Only one of `before` or `after` can be handled.") - if child is before or child is after: + if child is before or child is after: # no-op return def _to_widget(child: int | Widget, called: str) -> Widget: From fcf9806f6b79b549633fa49e263415e794febcde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Tue, 9 May 2023 17:00:07 +0100 Subject: [PATCH 06/30] Add clarifying comment. --- src/textual/widget.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/textual/widget.py b/src/textual/widget.py index f2f99ba42..b116f5393 100644 --- a/src/textual/widget.py +++ b/src/textual/widget.py @@ -813,7 +813,8 @@ class Widget(DOMNode): elif before is not None and after is not None: raise WidgetError("Only one of `before` or `after` can be handled.") - if child is before or child is after: # no-op + # We short-circuit the no-op, otherwise it will error later down the road. + if child is before or child is after: return def _to_widget(child: int | Widget, called: str) -> Widget: From a065ff572e752c9e3290c422339ae977a98edfb2 Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Fri, 12 May 2023 08:34:27 +0100 Subject: [PATCH 07/30] Correct spelling in a docstring (#2552) --- src/textual/dom.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/textual/dom.py b/src/textual/dom.py index e136a5588..c6efd1c4f 100644 --- a/src/textual/dom.py +++ b/src/textual/dom.py @@ -887,7 +887,7 @@ class DOMNode(MessagePump): Example: - Here's how you could detect when the app changes from dark to light mode (and visa versa). + Here's how you could detect when the app changes from dark to light mode (and vice versa). ```python def on_dark_change(old_value:bool, new_value:bool): From 4434b599825a20e26d85fbebede3b1847a411dae Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Wed, 10 May 2023 22:40:15 +0200 Subject: [PATCH 08/30] spacing diagram --- reference/spacing.monopic | Bin 0 -> 1716 bytes src/textual/geometry.py | 16 ++++++++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 reference/spacing.monopic diff --git a/reference/spacing.monopic b/reference/spacing.monopic new file mode 100644 index 0000000000000000000000000000000000000000..474d7d3ad7d7bcbd883f1f744b0de9dec5dd82ba GIT binary patch literal 1716 zcmV;l221(>O;1iwP)S1pABzY8000000u$|9-E*5b5dT*?JkuHU4SDVBJ>6?3!(dys zxnjrxX`9RVe?LiRF%Xj)h~hNqOG{+5+FkAb_Cx5=D^~x=bzONkkNWYmK>F#fsM0zu z^0<^+@22@7@O*DmtUE(UkECTKYwsrTy}e8}as6;7y&L3v`XNp?>Nz<2=JgP3(x>t8 z9KAaIDKo=OEq~VWy2xvpEBaq={buB@NOR5Z*V`k|4fN~JvQ{b)V-QL$9d-G`{z=|# zmh2R9WJ!x?GHHG(X)&2JzL2!hv(;R@%4EKgYDQh&OW!*b8=1vglXlIJs6`KVRlF+7 zjVzVMv!e8FC>9}#f*@3URQqI8{D{rgs9Ea$q^^_oK2fw=x|+iJ+wR@Fe}#Iw0PSjtK>P0+NYb1%-a{% z^}a0bcl&l=l_<8=P!7h6dt)-!Vz&CLlbzX{b|dgfLB)1!w$2ND?JSLaRr;Tz=?P!+ zQr)lAyK1Ilv1*Lk&TW?gHtqIxXc}pHIw-3%imB~ju2xr1ncUVbGwY(vwf50z0dTm+ z81xcFneI}hFz+)7JAw?PgZXFnyT5ss9V=(5XDPFNFecE&8Lf|#0azEY524rkW9##` z?LwQR+Mg7t(97)5z4^CL{~m`L%6e+k(_6ha+tU~V)f-lBleN@dIJAVmlt8j$Oec5I zTM^4mT6Z?s>f;ycYc~cz*=Fk$Pn~OEOBNKk)=ZY>a-c5D&cKb@7f19taHEe%V-oT&77qx?*MLKg=o%(}rTxJGEg01_Bn z69Y(K0uS^RLKAvBp$YRjp{a`u_?MaRDeXmiXHs1eco_MJc|e3tPN&(W=`?bdsUz;J zbRmH|g%D>|5y8$BqTs_x`H4a|bg?NAs~=J}ppoWE1F=cx^`9pV1m_lP-mZ&g?ip83 zvUHap3=@G4z~5#2M>AIe?Y{jx-N-gbHW)LE45!c|fWoS%>*CNja1+|WTPbp*GeRBh zMdSW8G`bLL&{C{Hi?C);5k3bs4M}LWO{qO-^u{`36#wanyE58-ra9zs4+FBXT>o3R4=ECTv00uWGKsTq6XWO6fByh1ZpOlrpR$PF-vdAYHe zo0p4DZY*9R!4BVFNHENUc#Z;sa2F87%z(g=k0OxUn8pBBuK}B=51af+KYM_ZUy}TL zQkfq45GQY65b_3CW&Xe6$pvAD`%7kRslu#YNdx=LK_axyWl^2mK$<>8dL8h$MM3#Qs10c#@w8w7BJ zMAxyu6HDP#`=e;K{n1P9j|P(t+0m3n7kX=rF7*31y0DLjjV?kAP%r3ov89LX_~LdK z5?4QDN053T$UP8*Ed;>={i4~043we?&8}vT-{=`T3YVU-yE|pVGwUv*>Mqe9-@7|c z!q{1Df*c*k5ado9)ZGC)cXT)v%xWs2==V53pHRw!x8U#vnxquJKw(YTyX%)|cEE(R za7lKj(j8fMs`pC%8|U|jl`M@VBa=9A^8z(VvQjNRcGlCMDvNXpJ*{M(oi=a+AcfTpOW_Sw|3)!y~`U9eEIqAtgP31^rZcr_%)Fq^ky4*Jh46pq;KY%O-kR@h7a7k|6U>HHD|x8Ddj$_T_*X(JYtHpk{i4Wp0VcU4y&kN*Qj KFnbEGKL7whc2Il( literal 0 HcmV?d00001 diff --git a/src/textual/geometry.py b/src/textual/geometry.py index 32dbd0a7d..4b337a4d9 100644 --- a/src/textual/geometry.py +++ b/src/textual/geometry.py @@ -853,7 +853,7 @@ class Region(NamedTuple): Args: cut: An offset from self.y where the cut should be made. May be negative, - for the offset to start from the bottom edge. + for the offset to start from the lower edge. Returns: Two regions, which add up to the original (self). @@ -909,7 +909,19 @@ class Region(NamedTuple): class Spacing(NamedTuple): """The spacing around a renderable, such as padding and border - Spacing is defined by four integers for the space at the top, right, bottom, and left of a region, + Spacing is defined by four integers for the space at the top, right, bottom, and left of a region. + + ``` + ┌ ─ ─ ─ ─ ─ ─ ─▲─ ─ ─ ─ ─ ─ ─ ─ ┐ + │ top + │ ┏━━━━━▼━━━━━━┓ │ + ◀──────▶┃ ┃◀───────▶ + │ left ┃ ┃ right │ + ┃ ┃ + │ ┗━━━━━▲━━━━━━┛ │ + │ bottom + └ ─ ─ ─ ─ ─ ─ ─▼─ ─ ─ ─ ─ ─ ─ ─ ┘ + ``` Example: ```python From 9a134b49203f306167306ebc61b4cd9e8d3f19c0 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Fri, 12 May 2023 15:36:49 +0200 Subject: [PATCH 09/30] virtual size and docks --- src/textual/_arrange.py | 4 +++- src/textual/_compositor.py | 11 ++++++++++- src/textual/_layout.py | 6 +++--- src/textual/_partition.py | 5 +++-- src/textual/layouts/grid.py | 2 +- src/textual/screen.py | 5 ++++- tests/test_arrange.py | 24 ++++++++---------------- 7 files changed, 32 insertions(+), 25 deletions(-) diff --git a/src/textual/_arrange.py b/src/textual/_arrange.py index b415d8983..1484b120f 100644 --- a/src/textual/_arrange.py +++ b/src/textual/_arrange.py @@ -94,7 +94,9 @@ def arrange( ) dock_region = dock_region.shrink(margin).translate(align_offset) add_placement( - _WidgetPlacement(dock_region, null_spacing, dock_widget, top_z, True) + _WidgetPlacement( + dock_region, null_spacing, dock_widget, top_z, True, False + ) ) dock_spacing = Spacing(top, right, bottom, left) diff --git a/src/textual/_compositor.py b/src/textual/_compositor.py index 61e4f3de8..115dafa9f 100644 --- a/src/textual/_compositor.py +++ b/src/textual/_compositor.py @@ -592,6 +592,7 @@ class Compositor: get_layer_index = layers_to_index.get scroll_spacing = arrange_result.scroll_spacing + total_region = total_region.shrink(scroll_spacing) # Add all the widgets for sub_region, margin, sub_widget, z, fixed, overlay in reversed( @@ -621,13 +622,21 @@ class Compositor: constrain in ("y", "both"), ) + if overlay: + clip_region = no_clip + else: + if fixed: + clip_region = sub_clip + else: + clip_region = sub_clip.shrink(scroll_spacing) + add_widget( sub_widget, sub_region, widget_region, ((1, 0, 0),) if overlay else widget_order, layer_order, - no_clip if overlay else sub_clip, + clip_region, visible, ) diff --git a/src/textual/_layout.py b/src/textual/_layout.py index e0061c148..40716b7f7 100644 --- a/src/textual/_layout.py +++ b/src/textual/_layout.py @@ -72,9 +72,9 @@ class WidgetPlacement(NamedTuple): region: Region margin: Spacing widget: Widget - order: int = 0 - fixed: bool = False - overlay: bool = False + order: int + fixed: bool + overlay: bool class Layout(ABC): diff --git a/src/textual/_partition.py b/src/textual/_partition.py index 858846f54..7bb46794d 100644 --- a/src/textual/_partition.py +++ b/src/textual/_partition.py @@ -21,8 +21,9 @@ def partition( """ result: tuple[list[T], list[T]] = ([], []) - appends = (result[0].append, result[1].append) + append0 = result[0].append + append1 = result[1].append for value in iterable: - appends[1 if pred(value) else 0](value) + (append1 if pred(value) else append0)(value) return result diff --git a/src/textual/layouts/grid.py b/src/textual/layouts/grid.py index 3ade70ab2..d26752f75 100644 --- a/src/textual/layouts/grid.py +++ b/src/textual/layouts/grid.py @@ -153,7 +153,7 @@ class GridLayout(Layout): .shrink(margin) .clip_size(cell_size) ) - add_placement(WidgetPlacement(region, margin, widget)) + add_placement(WidgetPlacement(region, margin, widget, 0, False, False)) add_widget(widget) return (placements, set(widgets)) diff --git a/src/textual/screen.py b/src/textual/screen.py index 34db473ef..d58a69031 100644 --- a/src/textual/screen.py +++ b/src/textual/screen.py @@ -580,7 +580,10 @@ class Screen(Generic[ScreenResultType], Widget): ) in layers: if widget in exposed_widgets: if widget._size_updated( - region.size, virtual_size, container_size, layout=False + region.size, + virtual_size, + container_size, + layout=False, ): widget.post_message( ResizeEvent( diff --git a/tests/test_arrange.py b/tests/test_arrange.py index 38f5a191d..8014d0d31 100644 --- a/tests/test_arrange.py +++ b/tests/test_arrange.py @@ -24,10 +24,8 @@ def test_arrange_dock_top(): result = arrange(container, [child, header], Size(80, 24), Size(80, 24)) assert result.placements == [ - WidgetPlacement( - Region(0, 0, 80, 1), Spacing(), header, order=TOP_Z, fixed=True - ), - WidgetPlacement(Region(0, 1, 80, 23), Spacing(), child, order=0, fixed=False), + WidgetPlacement(Region(0, 0, 80, 1), Spacing(), header, TOP_Z, True, False), + WidgetPlacement(Region(0, 1, 80, 23), Spacing(), child, 0, False, False), ] assert result.widgets == {child, header} @@ -41,10 +39,8 @@ def test_arrange_dock_left(): result = arrange(container, [child, header], Size(80, 24), Size(80, 24)) assert result.placements == [ - WidgetPlacement( - Region(0, 0, 10, 24), Spacing(), header, order=TOP_Z, fixed=True - ), - WidgetPlacement(Region(10, 0, 70, 24), Spacing(), child, order=0, fixed=False), + WidgetPlacement(Region(0, 0, 10, 24), Spacing(), header, TOP_Z, True, False), + WidgetPlacement(Region(10, 0, 70, 24), Spacing(), child, 0, False, False), ] assert result.widgets == {child, header} @@ -58,10 +54,8 @@ def test_arrange_dock_right(): result = arrange(container, [child, header], Size(80, 24), Size(80, 24)) assert result.placements == [ - WidgetPlacement( - Region(70, 0, 10, 24), Spacing(), header, order=TOP_Z, fixed=True - ), - WidgetPlacement(Region(0, 0, 70, 24), Spacing(), child, order=0, fixed=False), + WidgetPlacement(Region(70, 0, 10, 24), Spacing(), header, TOP_Z, True, False), + WidgetPlacement(Region(0, 0, 70, 24), Spacing(), child, 0, False, False), ] assert result.widgets == {child, header} @@ -75,10 +69,8 @@ def test_arrange_dock_bottom(): result = arrange(container, [child, header], Size(80, 24), Size(80, 24)) assert result.placements == [ - WidgetPlacement( - Region(0, 23, 80, 1), Spacing(), header, order=TOP_Z, fixed=True - ), - WidgetPlacement(Region(0, 0, 80, 23), Spacing(), child, order=0, fixed=False), + WidgetPlacement(Region(0, 23, 80, 1), Spacing(), header, TOP_Z, True, False), + WidgetPlacement(Region(0, 0, 80, 23), Spacing(), child, 0, False, False), ] assert result.widgets == {child, header} From 47970dd6229f252e744d4226c19f887a6f8bf6e6 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Fri, 12 May 2023 15:45:25 +0200 Subject: [PATCH 10/30] remove total region update --- src/textual/_compositor.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/textual/_compositor.py b/src/textual/_compositor.py index 115dafa9f..fb1a642d5 100644 --- a/src/textual/_compositor.py +++ b/src/textual/_compositor.py @@ -603,11 +603,11 @@ class Compositor: if fixed: widget_region = sub_region + placement_offset else: - total_region = total_region.union( - sub_region.grow( - margin if layer_index else margin + scroll_spacing - ) - ) + # total_region = total_region.union( + # sub_region.grow( + # margin if layer_index else margin + scroll_spacing + # ) + # ) widget_region = sub_region + placement_scroll_offset widget_order = order + ((layer_index, z, layer_order),) From 4e069abd2e79f8c7caa7683f85af98a27a07a38d Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Fri, 12 May 2023 15:49:39 +0200 Subject: [PATCH 11/30] Revert "remove total region update" This reverts commit 47970dd6229f252e744d4226c19f887a6f8bf6e6. --- src/textual/_compositor.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/textual/_compositor.py b/src/textual/_compositor.py index fb1a642d5..115dafa9f 100644 --- a/src/textual/_compositor.py +++ b/src/textual/_compositor.py @@ -603,11 +603,11 @@ class Compositor: if fixed: widget_region = sub_region + placement_offset else: - # total_region = total_region.union( - # sub_region.grow( - # margin if layer_index else margin + scroll_spacing - # ) - # ) + total_region = total_region.union( + sub_region.grow( + margin if layer_index else margin + scroll_spacing + ) + ) widget_region = sub_region + placement_scroll_offset widget_order = order + ((layer_index, z, layer_order),) From c2e7b619f19de3423c33e7caf4bb209a1f25417a Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Fri, 12 May 2023 15:53:47 +0200 Subject: [PATCH 12/30] Revert "virtual size and docks" This reverts commit 9a134b49203f306167306ebc61b4cd9e8d3f19c0. --- src/textual/_arrange.py | 4 +--- src/textual/_compositor.py | 11 +---------- src/textual/_layout.py | 6 +++--- src/textual/_partition.py | 5 ++--- src/textual/layouts/grid.py | 2 +- src/textual/screen.py | 5 +---- tests/test_arrange.py | 24 ++++++++++++++++-------- 7 files changed, 25 insertions(+), 32 deletions(-) diff --git a/src/textual/_arrange.py b/src/textual/_arrange.py index 1484b120f..b415d8983 100644 --- a/src/textual/_arrange.py +++ b/src/textual/_arrange.py @@ -94,9 +94,7 @@ def arrange( ) dock_region = dock_region.shrink(margin).translate(align_offset) add_placement( - _WidgetPlacement( - dock_region, null_spacing, dock_widget, top_z, True, False - ) + _WidgetPlacement(dock_region, null_spacing, dock_widget, top_z, True) ) dock_spacing = Spacing(top, right, bottom, left) diff --git a/src/textual/_compositor.py b/src/textual/_compositor.py index 115dafa9f..61e4f3de8 100644 --- a/src/textual/_compositor.py +++ b/src/textual/_compositor.py @@ -592,7 +592,6 @@ class Compositor: get_layer_index = layers_to_index.get scroll_spacing = arrange_result.scroll_spacing - total_region = total_region.shrink(scroll_spacing) # Add all the widgets for sub_region, margin, sub_widget, z, fixed, overlay in reversed( @@ -622,21 +621,13 @@ class Compositor: constrain in ("y", "both"), ) - if overlay: - clip_region = no_clip - else: - if fixed: - clip_region = sub_clip - else: - clip_region = sub_clip.shrink(scroll_spacing) - add_widget( sub_widget, sub_region, widget_region, ((1, 0, 0),) if overlay else widget_order, layer_order, - clip_region, + no_clip if overlay else sub_clip, visible, ) diff --git a/src/textual/_layout.py b/src/textual/_layout.py index 40716b7f7..e0061c148 100644 --- a/src/textual/_layout.py +++ b/src/textual/_layout.py @@ -72,9 +72,9 @@ class WidgetPlacement(NamedTuple): region: Region margin: Spacing widget: Widget - order: int - fixed: bool - overlay: bool + order: int = 0 + fixed: bool = False + overlay: bool = False class Layout(ABC): diff --git a/src/textual/_partition.py b/src/textual/_partition.py index 7bb46794d..858846f54 100644 --- a/src/textual/_partition.py +++ b/src/textual/_partition.py @@ -21,9 +21,8 @@ def partition( """ result: tuple[list[T], list[T]] = ([], []) - append0 = result[0].append - append1 = result[1].append + appends = (result[0].append, result[1].append) for value in iterable: - (append1 if pred(value) else append0)(value) + appends[1 if pred(value) else 0](value) return result diff --git a/src/textual/layouts/grid.py b/src/textual/layouts/grid.py index d26752f75..3ade70ab2 100644 --- a/src/textual/layouts/grid.py +++ b/src/textual/layouts/grid.py @@ -153,7 +153,7 @@ class GridLayout(Layout): .shrink(margin) .clip_size(cell_size) ) - add_placement(WidgetPlacement(region, margin, widget, 0, False, False)) + add_placement(WidgetPlacement(region, margin, widget)) add_widget(widget) return (placements, set(widgets)) diff --git a/src/textual/screen.py b/src/textual/screen.py index d58a69031..34db473ef 100644 --- a/src/textual/screen.py +++ b/src/textual/screen.py @@ -580,10 +580,7 @@ class Screen(Generic[ScreenResultType], Widget): ) in layers: if widget in exposed_widgets: if widget._size_updated( - region.size, - virtual_size, - container_size, - layout=False, + region.size, virtual_size, container_size, layout=False ): widget.post_message( ResizeEvent( diff --git a/tests/test_arrange.py b/tests/test_arrange.py index 8014d0d31..38f5a191d 100644 --- a/tests/test_arrange.py +++ b/tests/test_arrange.py @@ -24,8 +24,10 @@ def test_arrange_dock_top(): result = arrange(container, [child, header], Size(80, 24), Size(80, 24)) assert result.placements == [ - WidgetPlacement(Region(0, 0, 80, 1), Spacing(), header, TOP_Z, True, False), - WidgetPlacement(Region(0, 1, 80, 23), Spacing(), child, 0, False, False), + WidgetPlacement( + Region(0, 0, 80, 1), Spacing(), header, order=TOP_Z, fixed=True + ), + WidgetPlacement(Region(0, 1, 80, 23), Spacing(), child, order=0, fixed=False), ] assert result.widgets == {child, header} @@ -39,8 +41,10 @@ def test_arrange_dock_left(): result = arrange(container, [child, header], Size(80, 24), Size(80, 24)) assert result.placements == [ - WidgetPlacement(Region(0, 0, 10, 24), Spacing(), header, TOP_Z, True, False), - WidgetPlacement(Region(10, 0, 70, 24), Spacing(), child, 0, False, False), + WidgetPlacement( + Region(0, 0, 10, 24), Spacing(), header, order=TOP_Z, fixed=True + ), + WidgetPlacement(Region(10, 0, 70, 24), Spacing(), child, order=0, fixed=False), ] assert result.widgets == {child, header} @@ -54,8 +58,10 @@ def test_arrange_dock_right(): result = arrange(container, [child, header], Size(80, 24), Size(80, 24)) assert result.placements == [ - WidgetPlacement(Region(70, 0, 10, 24), Spacing(), header, TOP_Z, True, False), - WidgetPlacement(Region(0, 0, 70, 24), Spacing(), child, 0, False, False), + WidgetPlacement( + Region(70, 0, 10, 24), Spacing(), header, order=TOP_Z, fixed=True + ), + WidgetPlacement(Region(0, 0, 70, 24), Spacing(), child, order=0, fixed=False), ] assert result.widgets == {child, header} @@ -69,8 +75,10 @@ def test_arrange_dock_bottom(): result = arrange(container, [child, header], Size(80, 24), Size(80, 24)) assert result.placements == [ - WidgetPlacement(Region(0, 23, 80, 1), Spacing(), header, TOP_Z, True, False), - WidgetPlacement(Region(0, 0, 80, 23), Spacing(), child, 0, False, False), + WidgetPlacement( + Region(0, 23, 80, 1), Spacing(), header, order=TOP_Z, fixed=True + ), + WidgetPlacement(Region(0, 0, 80, 23), Spacing(), child, order=0, fixed=False), ] assert result.widgets == {child, header} From d061065dcce4f523a5a74bd3919c833aeafbc25b Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Fri, 12 May 2023 18:29:51 +0200 Subject: [PATCH 13/30] optimization for divide --- src/textual/strip.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/textual/strip.py b/src/textual/strip.py index 554acdecf..c1f9e2587 100644 --- a/src/textual/strip.py +++ b/src/textual/strip.py @@ -379,16 +379,22 @@ class Strip: """ pos = 0 + cell_length = self.cell_length + cuts = [cut for cut in cuts if cut <= cell_length] cache_key = tuple(cuts) cached = self._divide_cache.get(cache_key) if cached is not None: return cached - strips: list[Strip] = [] - add_strip = strips.append - for segments, cut in zip(Segment.divide(self._segments, cuts), cuts): - add_strip(Strip(segments, cut - pos)) - pos = cut + strips: list[Strip] + if cuts == [cell_length]: + strips = [self] + else: + strips = [] + add_strip = strips.append + for segments, cut in zip(Segment.divide(self._segments, cuts), cuts): + add_strip(Strip(segments, cut - pos)) + pos = cut self._divide_cache[cache_key] = strips return strips From d266e3685f37353eb3e201a6538d28e4cc5d7025 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Fri, 12 May 2023 18:57:53 +0200 Subject: [PATCH 14/30] snapshot update (#2555) --- .../__snapshots__/test_snapshots.ambr | 5378 +++++++++-------- 1 file changed, 2695 insertions(+), 2683 deletions(-) diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots.ambr b/tests/snapshot_tests/__snapshots__/test_snapshots.ambr index cbee7ba35..8b03449d9 100644 --- a/tests/snapshot_tests/__snapshots__/test_snapshots.ambr +++ b/tests/snapshot_tests/__snapshots__/test_snapshots.ambr @@ -21,137 +21,138 @@ font-weight: 700; } - .terminal-369237853-matrix { + .terminal-1593336641-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-369237853-title { + .terminal-1593336641-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-369237853-r1 { fill: #c5c8c6 } - .terminal-369237853-r2 { fill: #7ae998 } - .terminal-369237853-r3 { fill: #0a180e;font-weight: bold } - .terminal-369237853-r4 { fill: #008139 } - .terminal-369237853-r5 { fill: #e1e1e1 } - .terminal-369237853-r6 { fill: #e76580 } - .terminal-369237853-r7 { fill: #f5e5e9;font-weight: bold } - .terminal-369237853-r8 { fill: #780028 } + .terminal-1593336641-r1 { fill: #c5c8c6 } + .terminal-1593336641-r2 { fill: #7ae998 } + .terminal-1593336641-r3 { fill: #0a180e;font-weight: bold } + .terminal-1593336641-r4 { fill: #008139 } + .terminal-1593336641-r5 { fill: #e3dbce } + .terminal-1593336641-r6 { fill: #e1e1e1 } + .terminal-1593336641-r7 { fill: #e76580 } + .terminal-1593336641-r8 { fill: #f5e5e9;font-weight: bold } + .terminal-1593336641-r9 { fill: #780028 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - AlignContainersApp + AlignContainersApp - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - center - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - - - - - - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - middle - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - - - - - - - + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + center + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + + + + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + middle + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + + + + + @@ -346,202 +347,202 @@ font-weight: 700; } - .terminal-2978213952-matrix { + .terminal-3056812568-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2978213952-title { + .terminal-3056812568-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2978213952-r1 { fill: #c5c8c6 } - .terminal-2978213952-r2 { fill: #e3e3e3 } - .terminal-2978213952-r3 { fill: #004578 } - .terminal-2978213952-r4 { fill: #e1e1e1 } - .terminal-2978213952-r5 { fill: #632ca6 } - .terminal-2978213952-r6 { fill: #dde6ed;font-weight: bold } - .terminal-2978213952-r7 { fill: #14191f } - .terminal-2978213952-r8 { fill: #23568b } + .terminal-3056812568-r1 { fill: #c5c8c6 } + .terminal-3056812568-r2 { fill: #e3e3e3 } + .terminal-3056812568-r3 { fill: #004578 } + .terminal-3056812568-r4 { fill: #e1e1e1 } + .terminal-3056812568-r5 { fill: #632ca6 } + .terminal-3056812568-r6 { fill: #dde6ed;font-weight: bold } + .terminal-3056812568-r7 { fill: #14191f } + .terminal-3056812568-r8 { fill: #23568b } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - MyApp + MyApp - - - - MyApp - ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── - oktest - ╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍ -  0 ────────────────────────────────────── 1 ────────────────────────────────────── 2 ───── - -  Foo       Bar         Baz               Foo       Bar         Baz               Foo      -  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH -  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH -  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH -  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH -  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH -  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH -  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH -  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH -  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH -  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH -  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH -  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH -  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH -  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH -  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH -  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH -  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH -  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY▁▁ ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY▁▁ ABCDEFGH -  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH -  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH -  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH -  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH -  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH -  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH -  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH -  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH -  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH -  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH -  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH -  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH - ───────────────────────────────────────────────────────────────────────────────────────────── - - ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + + + + MyApp + ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + oktest + ╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍ +  0 ────────────────────────────────────── 1 ────────────────────────────────────── 2 ───── + +  Foo       Bar         Baz               Foo       Bar         Baz               Foo      +  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH +  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH +  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH +  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH +  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH +  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH +  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH +  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH +  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH +  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH +  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH +  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH +  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH +  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH +  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH +  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH +  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH +  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY▁▁ ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY▁▁ ABCDEFGH +  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH +  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH +  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH +  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH +  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH +  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH +  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH +  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH +  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH +  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH +  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH +  ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH  0123456789  IJKLMNOPQRSTUVWXY ABCDEFGH + ───────────────────────────────────────────────────────────────────────────────────────────── + + ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── @@ -571,136 +572,136 @@ font-weight: 700; } - .terminal-3956291897-matrix { + .terminal-1625062503-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3956291897-title { + .terminal-1625062503-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3956291897-r1 { fill: #c5c8c6 } - .terminal-3956291897-r2 { fill: #e3e3e3 } - .terminal-3956291897-r3 { fill: #1e1e1e } - .terminal-3956291897-r4 { fill: #0178d4 } - .terminal-3956291897-r5 { fill: #e1e1e1 } - .terminal-3956291897-r6 { fill: #e2e2e2 } - .terminal-3956291897-r7 { fill: #ddedf9 } + .terminal-1625062503-r1 { fill: #c5c8c6 } + .terminal-1625062503-r2 { fill: #e3e3e3 } + .terminal-1625062503-r3 { fill: #1e1e1e } + .terminal-1625062503-r4 { fill: #0178d4 } + .terminal-1625062503-r5 { fill: #e1e1e1 } + .terminal-1625062503-r6 { fill: #e2e2e2 } + .terminal-1625062503-r7 { fill: #ddedf9 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - InputWidthAutoApp + InputWidthAutoApp - - - - InputWidthAutoApp - ▔▔▔▔▔▔▔▔▔▔ - Hello - ▁▁▁▁▁▁▁▁▁▁ - - - - - - - - - - - - - - - - - - - + + + + InputWidthAutoApp + ▔▔▔▔▔▔▔▔▔▔ + Hello + ▁▁▁▁▁▁▁▁▁▁ + + + + + + + + + + + + + + + + + + + @@ -731,136 +732,137 @@ font-weight: 700; } - .terminal-2059832628-matrix { + .terminal-2470781732-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2059832628-title { + .terminal-2470781732-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2059832628-r1 { fill: #1e1e1e } - .terminal-2059832628-r2 { fill: #c5c8c6 } - .terminal-2059832628-r3 { fill: #183118 } - .terminal-2059832628-r4 { fill: #124512 } - .terminal-2059832628-r5 { fill: #0c580c } - .terminal-2059832628-r6 { fill: #066c06 } - .terminal-2059832628-r7 { fill: #008000 } + .terminal-2470781732-r1 { fill: #1e1e1e } + .terminal-2470781732-r2 { fill: #c5c8c6 } + .terminal-2470781732-r3 { fill: #e1e1e1 } + .terminal-2470781732-r4 { fill: #183118 } + .terminal-2470781732-r5 { fill: #124512 } + .terminal-2470781732-r6 { fill: #0c580c } + .terminal-2470781732-r7 { fill: #066c06 } + .terminal-2470781732-r8 { fill: #008000 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - BorderAlphaApp + BorderAlphaApp - - - - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - - - - + + + + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + + + + @@ -1055,161 +1057,162 @@ font-weight: 700; } - .terminal-3643133712-matrix { + .terminal-619468389-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3643133712-title { + .terminal-619468389-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3643133712-r1 { fill: #c5c8c6 } - .terminal-3643133712-r2 { fill: #e1e1e1;font-weight: bold } - .terminal-3643133712-r3 { fill: #454a50 } - .terminal-3643133712-r4 { fill: #35383c } - .terminal-3643133712-r5 { fill: #24292f;font-weight: bold } - .terminal-3643133712-r6 { fill: #7c7d7e;font-weight: bold } - .terminal-3643133712-r7 { fill: #000000 } - .terminal-3643133712-r8 { fill: #0c0c0c } - .terminal-3643133712-r9 { fill: #507bb3 } - .terminal-3643133712-r10 { fill: #3c5577 } - .terminal-3643133712-r11 { fill: #dde6ed;font-weight: bold } - .terminal-3643133712-r12 { fill: #75828b;font-weight: bold } - .terminal-3643133712-r13 { fill: #001541 } - .terminal-3643133712-r14 { fill: #0c1833 } - .terminal-3643133712-r15 { fill: #7ae998 } - .terminal-3643133712-r16 { fill: #559767 } - .terminal-3643133712-r17 { fill: #0a180e;font-weight: bold } - .terminal-3643133712-r18 { fill: #192e1f;font-weight: bold } - .terminal-3643133712-r19 { fill: #008139 } - .terminal-3643133712-r20 { fill: #0c592e } - .terminal-3643133712-r21 { fill: #ffcf56 } - .terminal-3643133712-r22 { fill: #a5883f } - .terminal-3643133712-r23 { fill: #211505;font-weight: bold } - .terminal-3643133712-r24 { fill: #3a2a13;font-weight: bold } - .terminal-3643133712-r25 { fill: #b86b00 } - .terminal-3643133712-r26 { fill: #7a4c0c } - .terminal-3643133712-r27 { fill: #e76580 } - .terminal-3643133712-r28 { fill: #964858 } - .terminal-3643133712-r29 { fill: #f5e5e9;font-weight: bold } - .terminal-3643133712-r30 { fill: #978186;font-weight: bold } - .terminal-3643133712-r31 { fill: #780028 } - .terminal-3643133712-r32 { fill: #540c24 } + .terminal-619468389-r1 { fill: #e1e1e1 } + .terminal-619468389-r2 { fill: #c5c8c6 } + .terminal-619468389-r3 { fill: #e1e1e1;font-weight: bold } + .terminal-619468389-r4 { fill: #454a50 } + .terminal-619468389-r5 { fill: #35383c } + .terminal-619468389-r6 { fill: #24292f;font-weight: bold } + .terminal-619468389-r7 { fill: #7c7d7e;font-weight: bold } + .terminal-619468389-r8 { fill: #000000 } + .terminal-619468389-r9 { fill: #0c0c0c } + .terminal-619468389-r10 { fill: #507bb3 } + .terminal-619468389-r11 { fill: #3c5577 } + .terminal-619468389-r12 { fill: #dde6ed;font-weight: bold } + .terminal-619468389-r13 { fill: #75828b;font-weight: bold } + .terminal-619468389-r14 { fill: #001541 } + .terminal-619468389-r15 { fill: #0c1833 } + .terminal-619468389-r16 { fill: #7ae998 } + .terminal-619468389-r17 { fill: #559767 } + .terminal-619468389-r18 { fill: #0a180e;font-weight: bold } + .terminal-619468389-r19 { fill: #192e1f;font-weight: bold } + .terminal-619468389-r20 { fill: #008139 } + .terminal-619468389-r21 { fill: #0c592e } + .terminal-619468389-r22 { fill: #ffcf56 } + .terminal-619468389-r23 { fill: #a5883f } + .terminal-619468389-r24 { fill: #211505;font-weight: bold } + .terminal-619468389-r25 { fill: #3a2a13;font-weight: bold } + .terminal-619468389-r26 { fill: #b86b00 } + .terminal-619468389-r27 { fill: #7a4c0c } + .terminal-619468389-r28 { fill: #e76580 } + .terminal-619468389-r29 { fill: #964858 } + .terminal-619468389-r30 { fill: #f5e5e9;font-weight: bold } + .terminal-619468389-r31 { fill: #978186;font-weight: bold } + .terminal-619468389-r32 { fill: #780028 } + .terminal-619468389-r33 { fill: #540c24 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - ButtonsApp + ButtonsApp - - - - - Standard ButtonsDisabled Buttons - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - DefaultDefault - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - Primary!Primary! - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - Success!Success! - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - Warning!Warning! - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - Error!Error! - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - + + + + + Standard ButtonsDisabled Buttons + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + DefaultDefault + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + Primary!Primary! + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + Success!Success! + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + Warning!Warning! + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + Error!Error! + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + @@ -2482,134 +2485,135 @@ font-weight: 700; } - .terminal-2323733830-matrix { + .terminal-1331556511-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2323733830-title { + .terminal-1331556511-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2323733830-r1 { fill: #808080 } - .terminal-2323733830-r2 { fill: #e1e1e1 } - .terminal-2323733830-r3 { fill: #c5c8c6 } - .terminal-2323733830-r4 { fill: #ddedf9 } + .terminal-1331556511-r1 { fill: #808080 } + .terminal-1331556511-r2 { fill: #e1e1e1 } + .terminal-1331556511-r3 { fill: #c5c8c6 } + .terminal-1331556511-r4 { fill: #ddedf9 } + .terminal-1331556511-r5 { fill: #e2e2e2 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - AlignAllApp + AlignAllApp - - - - ──────────────────────────────────────────────────────────────────────── - left topcenter topright top - - - - - ──────────────────────────────────────────────────────────────────────── - - ──────────────────────────────────────────────────────────────────────── - - - left middlecenter middleright middle - - - ──────────────────────────────────────────────────────────────────────── - - ──────────────────────────────────────────────────────────────────────── - - - - - - left bottomcenter bottomright bottom - ──────────────────────────────────────────────────────────────────────── + + + + ──────────────────────────────────────────────────────────────────────── + left topcenter topright top + + + + + ──────────────────────────────────────────────────────────────────────── + + ──────────────────────────────────────────────────────────────────────── + + + left middlecenter middleright middle + + + ──────────────────────────────────────────────────────────────────────── + + ──────────────────────────────────────────────────────────────────────── + + + + + + left bottomcenter bottomright bottom + ──────────────────────────────────────────────────────────────────────── @@ -3273,141 +3277,141 @@ font-weight: 700; } - .terminal-1536397390-matrix { + .terminal-1997861159-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1536397390-title { + .terminal-1997861159-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1536397390-r1 { fill: #c5c8c6 } - .terminal-1536397390-r2 { fill: #fea62b } - .terminal-1536397390-r3 { fill: #fea62b;font-weight: bold } - .terminal-1536397390-r4 { fill: #fea62b;font-weight: bold;font-style: italic; } - .terminal-1536397390-r5 { fill: #cc555a;font-weight: bold } - .terminal-1536397390-r6 { fill: #e1e1e1 } - .terminal-1536397390-r7 { fill: #1e1e1e } - .terminal-1536397390-r8 { fill: #1e1e1e;text-decoration: underline; } - .terminal-1536397390-r9 { fill: #fea62b;text-decoration: underline; } - .terminal-1536397390-r10 { fill: #4b4e55;text-decoration: underline; } - .terminal-1536397390-r11 { fill: #4ebf71 } - .terminal-1536397390-r12 { fill: #b93c5b } + .terminal-1997861159-r1 { fill: #e1e1e1 } + .terminal-1997861159-r2 { fill: #c5c8c6 } + .terminal-1997861159-r3 { fill: #fea62b } + .terminal-1997861159-r4 { fill: #fea62b;font-weight: bold } + .terminal-1997861159-r5 { fill: #fea62b;font-weight: bold;font-style: italic; } + .terminal-1997861159-r6 { fill: #cc555a;font-weight: bold } + .terminal-1997861159-r7 { fill: #1e1e1e } + .terminal-1997861159-r8 { fill: #1e1e1e;text-decoration: underline; } + .terminal-1997861159-r9 { fill: #fea62b;text-decoration: underline; } + .terminal-1997861159-r10 { fill: #4b4e55;text-decoration: underline; } + .terminal-1997861159-r11 { fill: #4ebf71 } + .terminal-1997861159-r12 { fill: #b93c5b } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - BorderSubTitleAlignAll + BorderSubTitleAlignAll - - - - - - Border titleLef…▁▁▁▁Left▁▁▁▁ - This is the story ofa Pythondeveloper that - Border subtitleCen…▔▔▔▔@@@▔▔▔▔▔ - - - - - - +--------------+Title───────────────── - |had to fill up|nine labelsand ended up redoing it - +-Left-------+──────────────Subtitle - - - - - Title, but really looo… - Title, but r…Title, but reall… - because the first tryhad some labelsthat were too long. - Subtitle, bu…Subtitle, but re… - Subtitle, but really l… - + + + + + + Border titleLef…▁▁▁▁Left▁▁▁▁ + This is the story ofa Pythondeveloper that + Border subtitleCen…▔▔▔▔@@@▔▔▔▔▔ + + + + + + +--------------+Title───────────────── + |had to fill up|nine labelsand ended up redoing it + +-Left-------+──────────────Subtitle + + + + + Title, but really looo… + Title, but r…Title, but reall… + because the first tryhad some labelsthat were too long. + Subtitle, bu…Subtitle, but re… + Subtitle, but really l… + @@ -5014,132 +5018,132 @@ font-weight: 700; } - .terminal-1564714526-matrix { + .terminal-1840966081-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1564714526-title { + .terminal-1840966081-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1564714526-r1 { fill: #e1e1e1 } - .terminal-1564714526-r2 { fill: #c5c8c6 } - .terminal-1564714526-r3 { fill: #ffffff } + .terminal-1840966081-r1 { fill: #e1e1e1 } + .terminal-1840966081-r2 { fill: #c5c8c6 } + .terminal-1840966081-r3 { fill: #ffffff } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - DockAllApp + DockAllApp - - - - - - - ────────────────────────────────────────────────────────── - top - - - - - - - leftright - - - - - - - - bottom - ────────────────────────────────────────────────────────── - - + + + + + + + ────────────────────────────────────────────────────────── + top + + + + + + + leftright + + + + + + + + bottom + ────────────────────────────────────────────────────────── + + @@ -6427,132 +6431,134 @@ font-weight: 700; } - .terminal-2726481143-matrix { + .terminal-2838975926-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2726481143-title { + .terminal-2838975926-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2726481143-r1 { fill: #c5c8c6 } - .terminal-2726481143-r2 { fill: #000000 } - .terminal-2726481143-r3 { fill: #e1e1e1 } + .terminal-2838975926-r1 { fill: #efddef } + .terminal-2838975926-r2 { fill: #c5c8c6 } + .terminal-2838975926-r3 { fill: #000000 } + .terminal-2838975926-r4 { fill: #ddefef } + .terminal-2838975926-r5 { fill: #e1e1e1 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - LayoutApp + LayoutApp - - - - - Layout - - Is - - Vertical - - - LayoutIsHorizontal - - - - - - - - - - - - - - + + + + + Layout + + Is + + Vertical + + + LayoutIsHorizontal + + + + + + + + + + + + + + @@ -7839,140 +7845,141 @@ font-weight: 700; } - .terminal-4172255139-matrix { + .terminal-2245771963-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-4172255139-title { + .terminal-2245771963-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-4172255139-r1 { fill: #ffffff } - .terminal-4172255139-r2 { fill: #c5c8c6 } - .terminal-4172255139-r3 { fill: #ece5e5 } - .terminal-4172255139-r4 { fill: #eee8e3 } - .terminal-4172255139-r5 { fill: #e7e0e6 } - .terminal-4172255139-r6 { fill: #eae2e4 } - .terminal-4172255139-r7 { fill: #e3ede7 } - .terminal-4172255139-r8 { fill: #e8ede4 } - .terminal-4172255139-r9 { fill: #e1eceb } - .terminal-4172255139-r10 { fill: #eeeddf } + .terminal-2245771963-r1 { fill: #ffffff } + .terminal-2245771963-r2 { fill: #c5c8c6 } + .terminal-2245771963-r3 { fill: #e0e0e0 } + .terminal-2245771963-r4 { fill: #ece5e5 } + .terminal-2245771963-r5 { fill: #eee8e3 } + .terminal-2245771963-r6 { fill: #e7e0e6 } + .terminal-2245771963-r7 { fill: #eae2e4 } + .terminal-2245771963-r8 { fill: #e3ede7 } + .terminal-2245771963-r9 { fill: #e8ede4 } + .terminal-2245771963-r10 { fill: #e1eceb } + .terminal-2245771963-r11 { fill: #eeeddf } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - MarginAllApp + MarginAllApp - - - - ────────────────────────────────────────────────────────────────── - - - - marginmargin: 1  - no marginmargin: 1: 1 51 2 6 - - - - - ────────────────────────────────────────────────────────────────── - - ────────────────────────────────────────────────────────────────── - - - margin-bottom: 4 - - margin-right: margin-left: 3 - 3 - margin-top: 4 - - - - ────────────────────────────────────────────────────────────────── + + + + ────────────────────────────────────────────────────────────────── + + + + marginmargin: 1  + no marginmargin: 1: 1 51 2 6 + + + + + ────────────────────────────────────────────────────────────────── + + ────────────────────────────────────────────────────────────────── + + + margin-bottom: 4 + + margin-right: margin-left: 3 + 3 + margin-top: 4 + + + + ────────────────────────────────────────────────────────────────── @@ -8160,134 +8167,134 @@ font-weight: 700; } - .terminal-987506037-matrix { + .terminal-1398959741-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-987506037-title { + .terminal-1398959741-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-987506037-r1 { fill: #c5c8c6 } - .terminal-987506037-r2 { fill: #e8e0e7 } - .terminal-987506037-r3 { fill: #eae3e5 } - .terminal-987506037-r4 { fill: #ede6e6 } - .terminal-987506037-r5 { fill: #efe9e4 } + .terminal-1398959741-r1 { fill: #c5c8c6 } + .terminal-1398959741-r2 { fill: #e8e0e7 } + .terminal-1398959741-r3 { fill: #eae3e5 } + .terminal-1398959741-r4 { fill: #ede6e6 } + .terminal-1398959741-r5 { fill: #efe9e4 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - MaxWidthApp + MaxWidthApp - - - - - - max-width:  - 50h - - - - - max-width: 999 - - - - - - max-width: 50% - - - - - - max-width: 30 - - + + + + + + max-width:  + 50h + + + + + max-width: 999 + + + + + + max-width: 50% + + + + + + max-width: 30 + + @@ -8637,134 +8644,134 @@ font-weight: 700; } - .terminal-3520697079-matrix { + .terminal-292160688-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3520697079-title { + .terminal-292160688-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3520697079-r1 { fill: #000000 } - .terminal-3520697079-r2 { fill: #0000ff } - .terminal-3520697079-r3 { fill: #c5c8c6 } - .terminal-3520697079-r4 { fill: #ff0000 } - .terminal-3520697079-r5 { fill: #008000 } + .terminal-292160688-r1 { fill: #000000 } + .terminal-292160688-r2 { fill: #0000ff } + .terminal-292160688-r3 { fill: #c5c8c6 } + .terminal-292160688-r4 { fill: #ff0000 } + .terminal-292160688-r5 { fill: #008000 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - OffsetApp + OffsetApp - - - - - Chani (offset 0  - ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀-3) - - - - Paul (offset 8 2)▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ - - - - ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ - ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ - - - Duncan (offset 4  - 10) - - - - ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ - - - + + + + + Chani (offset 0  + ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀-3) + + + + Paul (offset 8 2)▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ + + + + ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ + ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ + + + Duncan (offset 4  + 10) + + + + ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ + + + @@ -9429,136 +9436,136 @@ font-weight: 700; } - .terminal-3720200886-matrix { + .terminal-2990670852-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3720200886-title { + .terminal-2990670852-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3720200886-r1 { fill: #c5c8c6 } - .terminal-3720200886-r2 { fill: #000000 } - .terminal-3720200886-r3 { fill: #008000 } - .terminal-3720200886-r4 { fill: #e5f0e5 } - .terminal-3720200886-r5 { fill: #036a03 } - .terminal-3720200886-r6 { fill: #14191f } + .terminal-2990670852-r1 { fill: #c5c8c6 } + .terminal-2990670852-r2 { fill: #000000 } + .terminal-2990670852-r3 { fill: #008000 } + .terminal-2990670852-r4 { fill: #e5f0e5 } + .terminal-2990670852-r5 { fill: #036a03 } + .terminal-2990670852-r6 { fill: #14191f } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - OverflowApp + OverflowApp - - - - - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - I must not fear.I must not fear. - Fear is the mind-killer.Fear is the mind-killer. - Fear is the little-death that Fear is the little-death that  - brings total obliteration.brings total obliteration. - I will face my fear.I will face my fear. - I will permit it to pass over meI will permit it to pass over me  - and through me.and through me. - And when it has gone past, I And when it has gone past, I will  - will turn the inner eye to see turn the inner eye to see its  - its path.▁▁path. - Where the fear has gone there Where the fear has gone there will - will be nothing. Only I will be nothing. Only I will remain. - remain.▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁I must not fear. - I must not fear.Fear is the mind-killer. - Fear is the mind-killer.Fear is the little-death that  - Fear is the little-death that brings total obliteration. - brings total obliteration.I will face my fear. - I will face my fear.I will permit it to pass over me  - I will permit it to pass over meand through me. + + + + + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + I must not fear.I must not fear. + Fear is the mind-killer.Fear is the mind-killer. + Fear is the little-death that Fear is the little-death that  + brings total obliteration.brings total obliteration. + I will face my fear.I will face my fear. + I will permit it to pass over meI will permit it to pass over me  + and through me.and through me. + And when it has gone past, I And when it has gone past, I will  + will turn the inner eye to see turn the inner eye to see its  + its path.▁▁path. + Where the fear has gone there Where the fear has gone there will + will be nothing. Only I will be nothing. Only I will remain. + remain.▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁I must not fear. + I must not fear.Fear is the mind-killer. + Fear is the mind-killer.Fear is the little-death that  + Fear is the little-death that brings total obliteration. + brings total obliteration.I will face my fear. + I will face my fear.I will permit it to pass over me  + I will permit it to pass over meand through me. @@ -9743,138 +9750,138 @@ font-weight: 700; } - .terminal-2103878337-matrix { + .terminal-1642992271-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2103878337-title { + .terminal-1642992271-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2103878337-r1 { fill: #e7e0e6 } - .terminal-2103878337-r2 { fill: #c5c8c6 } - .terminal-2103878337-r3 { fill: #eae2e4 } - .terminal-2103878337-r4 { fill: #ece5e5 } - .terminal-2103878337-r5 { fill: #eee8e3 } - .terminal-2103878337-r6 { fill: #e8ede4 } - .terminal-2103878337-r7 { fill: #e3ede7 } - .terminal-2103878337-r8 { fill: #e1eceb } - .terminal-2103878337-r9 { fill: #eeeddf } + .terminal-1642992271-r1 { fill: #c5c8c6 } + .terminal-1642992271-r2 { fill: #e7e0e6 } + .terminal-1642992271-r3 { fill: #eae2e4 } + .terminal-1642992271-r4 { fill: #ece5e5 } + .terminal-1642992271-r5 { fill: #eee8e3 } + .terminal-1642992271-r6 { fill: #e8ede4 } + .terminal-1642992271-r7 { fill: #e3ede7 } + .terminal-1642992271-r8 { fill: #e1eceb } + .terminal-1642992271-r9 { fill: #eeeddf } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - PaddingAllApp + PaddingAllApp - - - - no padding - padding: 1padding:padding: 1 1 - 1 52 6 - - - - - - - - - - padding-right: 3padding-bottom: 4padding-left: 3 - - - - padding-top: 4 - - - - - - + + + + no padding + padding: 1padding:padding: 1 1 + 1 52 6 + + + + + + + + + + padding-right: 3padding-bottom: 4padding-left: 3 + + + + padding-top: 4 + + + + + + @@ -12284,141 +12291,141 @@ font-weight: 700; } - .terminal-1052270191-matrix { + .terminal-1938916138-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1052270191-title { + .terminal-1938916138-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1052270191-r1 { fill: #c5c8c6 } - .terminal-1052270191-r2 { fill: #e8e0e7 } - .terminal-1052270191-r3 { fill: #eae3e5 } - .terminal-1052270191-r4 { fill: #ede6e6 } - .terminal-1052270191-r5 { fill: #efe9e4 } - .terminal-1052270191-r6 { fill: #efeedf } - .terminal-1052270191-r7 { fill: #e9eee5 } - .terminal-1052270191-r8 { fill: #e4eee8 } - .terminal-1052270191-r9 { fill: #e2edeb } - .terminal-1052270191-r10 { fill: #dfebed } - .terminal-1052270191-r11 { fill: #ddedf9 } + .terminal-1938916138-r1 { fill: #c5c8c6 } + .terminal-1938916138-r2 { fill: #e8e0e7 } + .terminal-1938916138-r3 { fill: #eae3e5 } + .terminal-1938916138-r4 { fill: #ede6e6 } + .terminal-1938916138-r5 { fill: #efe9e4 } + .terminal-1938916138-r6 { fill: #efeedf } + .terminal-1938916138-r7 { fill: #e9eee5 } + .terminal-1938916138-r8 { fill: #e4eee8 } + .terminal-1938916138-r9 { fill: #e2edeb } + .terminal-1938916138-r10 { fill: #dfebed } + .terminal-1938916138-r11 { fill: #ddedf9 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - HeightComparisonApp + HeightComparisonApp - - - - - - - - - - - - - - - #cells#percent#w#h#vw#vh#auto#fr1#fr3 - - - - - - - - - - - - ····•····•····•····•····•····•····•····•····•····•····•····•····•····•····•····• + + + + + + + + + + + + + + + #cells#percent#w#h#vw#vh#auto#fr1#fr3 + + + + + + + + + + + + ····•····•····•····•····•····•····•····•····•····•····•····•····•····•····•····• @@ -13562,168 +13569,168 @@ font-weight: 700; } - .terminal-614458704-matrix { + .terminal-4033540874-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-614458704-title { + .terminal-4033540874-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-614458704-r1 { fill: #c5c8c6 } - .terminal-614458704-r2 { fill: #e3e3e3 } - .terminal-614458704-r3 { fill: #e1e1e1 } - .terminal-614458704-r4 { fill: #e2e2e2 } - .terminal-614458704-r5 { fill: #14191f } - .terminal-614458704-r6 { fill: #004578 } - .terminal-614458704-r7 { fill: #262626 } - .terminal-614458704-r8 { fill: #e2e2e2;font-weight: bold;text-decoration: underline; } - .terminal-614458704-r9 { fill: #e2e2e2;font-weight: bold } - .terminal-614458704-r10 { fill: #7ae998 } - .terminal-614458704-r11 { fill: #4ebf71;font-weight: bold } - .terminal-614458704-r12 { fill: #008139 } - .terminal-614458704-r13 { fill: #dde8f3;font-weight: bold } - .terminal-614458704-r14 { fill: #ddedf9 } + .terminal-4033540874-r1 { fill: #c5c8c6 } + .terminal-4033540874-r2 { fill: #e3e3e3 } + .terminal-4033540874-r3 { fill: #e1e1e1 } + .terminal-4033540874-r4 { fill: #e2e2e2 } + .terminal-4033540874-r5 { fill: #14191f } + .terminal-4033540874-r6 { fill: #004578 } + .terminal-4033540874-r7 { fill: #262626 } + .terminal-4033540874-r8 { fill: #e2e2e2;font-weight: bold;text-decoration: underline; } + .terminal-4033540874-r9 { fill: #e2e2e2;font-weight: bold } + .terminal-4033540874-r10 { fill: #7ae998 } + .terminal-4033540874-r11 { fill: #4ebf71;font-weight: bold } + .terminal-4033540874-r12 { fill: #008139 } + .terminal-4033540874-r13 { fill: #dde8f3;font-weight: bold } + .terminal-4033540874-r14 { fill: #ddedf9 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - Textual Demo + Textual Demo - - - - Textual Demo - - - TOP - - ▆▆ - - Widgets - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - - Rich contentTextual Demo - - Welcome! Textual is a framework for creating sophisticated - applications with the terminal. - CSS - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - Start - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - - - - - - - - -  CTRL+C  Quit  CTRL+B  Sidebar  CTRL+T  Toggle Dark mode  CTRL+S  Screenshot  F1  Notes  + + + + Textual Demo + + + TOP + + ▆▆ + + Widgets + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + Rich contentTextual Demo + + Welcome! Textual is a framework for creating sophisticated + applications with the terminal. + CSS + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + Start + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + + + + + + + + +  CTRL+C  Quit  CTRL+B  Sidebar  CTRL+T  Toggle Dark mode  CTRL+S  Screenshot  F1  Notes  @@ -14094,141 +14101,141 @@ font-weight: 700; } - .terminal-2216843056-matrix { + .terminal-2702154472-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2216843056-title { + .terminal-2702154472-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2216843056-r1 { fill: #c5c8c6 } - .terminal-2216843056-r2 { fill: #1e1e1e } - .terminal-2216843056-r3 { fill: #1f1f1f } - .terminal-2216843056-r4 { fill: #ff0000 } - .terminal-2216843056-r5 { fill: #dde8f3;font-weight: bold } - .terminal-2216843056-r6 { fill: #ddedf9 } - .terminal-2216843056-r7 { fill: #c7cdd2 } + .terminal-2702154472-r1 { fill: #c5c8c6 } + .terminal-2702154472-r2 { fill: #1e1e1e } + .terminal-2702154472-r3 { fill: #1f1f1f } + .terminal-2702154472-r4 { fill: #ff0000 } + .terminal-2702154472-r5 { fill: #dde8f3;font-weight: bold } + .terminal-2702154472-r6 { fill: #ddedf9 } + .terminal-2702154472-r7 { fill: #c7cdd2 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - TestApp + TestApp - - - - TestApp - ───────── - this - is - a - sample - sentence - and - here - are - some - wordsthis - is - a - sample - sentence - and - here - are - some - words -  CTRL+Q  Quit  - - - ▇▇ + + + + TestApp + ───────── + this + is + a + sample + sentence + and + here + are + some + wordsthis + is + a + sample + sentence + and + here + are + some + words +  CTRL+Q  Quit  + + + ▇▇ @@ -14428,135 +14435,135 @@ font-weight: 700; } - .terminal-2886576672-matrix { + .terminal-1801121102-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2886576672-title { + .terminal-1801121102-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2886576672-r1 { fill: #c5c8c6 } - .terminal-2886576672-r2 { fill: #e3e3e3 } - .terminal-2886576672-r3 { fill: #ffdddd } - .terminal-2886576672-r4 { fill: #e1e1e1 } - .terminal-2886576672-r5 { fill: #14191f } - .terminal-2886576672-r6 { fill: #ddedf9 } + .terminal-1801121102-r1 { fill: #c5c8c6 } + .terminal-1801121102-r2 { fill: #e3e3e3 } + .terminal-1801121102-r3 { fill: #ffdddd } + .terminal-1801121102-r4 { fill: #e1e1e1 } + .terminal-1801121102-r5 { fill: #14191f } + .terminal-1801121102-r6 { fill: #ddedf9 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - StyleBugApp + StyleBugApp - - - - StyleBugApp - test widget 0 - test widget 1 - test widget 2 - test widget 3 - test widget 4 - test widget 5 - test widget 6 - test widget 7 - test widget 8 - test widget 9 - test widget 10 - test widget 11 - test widget 12▇▇ - test widget 13 - test widget 14 - test widget 15 - test widget 16 - test widget 17 - test widget 18 - test widget 19 - test widget 20 - test widget 21 + + + + StyleBugApp + test widget 0 + test widget 1 + test widget 2 + test widget 3 + test widget 4 + test widget 5 + test widget 6 + test widget 7 + test widget 8 + test widget 9 + test widget 10 + test widget 11 + test widget 12▇▇ + test widget 13 + test widget 14 + test widget 15 + test widget 16 + test widget 17 + test widget 18 + test widget 19 + test widget 20 + test widget 21 @@ -14744,137 +14751,138 @@ font-weight: 700; } - .terminal-1298369243-matrix { + .terminal-1665781252-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1298369243-title { + .terminal-1665781252-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1298369243-r1 { fill: #008000 } - .terminal-1298369243-r2 { fill: #c5c8c6 } - .terminal-1298369243-r3 { fill: #e0e6e0 } + .terminal-1665781252-r1 { fill: #008000 } + .terminal-1665781252-r2 { fill: #c5c8c6 } + .terminal-1665781252-r3 { fill: #e0e4e0 } + .terminal-1665781252-r4 { fill: #e0e6e0 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - TestApp + TestApp - - - - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - - Hello - - - - - - - World - - - - - - - !! - - - - - - - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + + + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + + Hello + + + + + + + World + + + + + + + !! + + + + + + + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ @@ -14904,135 +14912,136 @@ font-weight: 700; } - .terminal-2371169958-matrix { + .terminal-1035580841-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2371169958-title { + .terminal-1035580841-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2371169958-r1 { fill: #c5c8c6 } - .terminal-2371169958-r2 { fill: #e3e3e3 } - .terminal-2371169958-r3 { fill: #e3e4e5 } - .terminal-2371169958-r4 { fill: #e2e3e3 } - .terminal-2371169958-r5 { fill: #14191f } - .terminal-2371169958-r6 { fill: #ddedf9 } + .terminal-1035580841-r1 { fill: #c5c8c6 } + .terminal-1035580841-r2 { fill: #e3e3e3 } + .terminal-1035580841-r3 { fill: #ddddff } + .terminal-1035580841-r4 { fill: #e3e4e5 } + .terminal-1035580841-r5 { fill: #e2e3e3 } + .terminal-1035580841-r6 { fill: #14191f } + .terminal-1035580841-r7 { fill: #ddedf9 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - ScreenSplitApp + ScreenSplitApp - - - - ScreenSplitApp - This is content This is content number 0 - number 0This is content number 1 - This is content ▄▄This is content number 2 - number 1This is content number 3 - This is content This is content number 4▁▁ - number 2This is content number 5 - This is content This is content number 6 - number 3This is content number 7 - This is content This is content number 8 - number 4This is content number 9 - This is content This is content number 10 - number 5This is content number 11 - This is content This is content number 12 - number 6This is content number 13 - This is content This is content number 14 - number 7This is content number 15 - This is content This is content number 16 - number 8This is content number 17 - This is content This is content number 18 - number 9This is content number 19 - This is content This is content number 20 - number 10This is content number 21 + + + + ScreenSplitApp + This is content This is content number 0 + number 0This is content number 1 + This is content ▄▄This is content number 2 + number 1This is content number 3 + This is content This is content number 4▁▁ + number 2This is content number 5 + This is content This is content number 6 + number 3This is content number 7 + This is content This is content number 8 + number 4This is content number 9 + This is content This is content number 10 + number 5This is content number 11 + This is content This is content number 12 + number 6This is content number 13 + This is content This is content number 14 + number 7This is content number 15 + This is content This is content number 16 + number 8This is content number 17 + This is content This is content number 18 + number 9This is content number 19 + This is content This is content number 20 + number 10This is content number 21 @@ -15687,132 +15696,132 @@ font-weight: 700; } - .terminal-2648118808-matrix { + .terminal-4077214022-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2648118808-title { + .terminal-4077214022-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2648118808-r1 { fill: #c5c8c6 } - .terminal-2648118808-r2 { fill: #e3e3e3 } - .terminal-2648118808-r3 { fill: #e1e1e1 } + .terminal-4077214022-r1 { fill: #c5c8c6 } + .terminal-4077214022-r2 { fill: #e3e3e3 } + .terminal-4077214022-r3 { fill: #e1e1e1 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - HeaderApp + HeaderApp - - - - HeaderApp - - - - - - - - - - - - - - - - - - - - - - + + + + HeaderApp + + + + + + + + + + + + + + + + + + + + + + @@ -16473,146 +16482,146 @@ font-weight: 700; } - .terminal-641812469-matrix { + .terminal-2572323619-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-641812469-title { + .terminal-2572323619-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-641812469-r1 { fill: #c5c8c6 } - .terminal-641812469-r2 { fill: #e3e3e3 } - .terminal-641812469-r3 { fill: #e1e1e1 } - .terminal-641812469-r4 { fill: #e1e1e1;text-decoration: underline; } - .terminal-641812469-r5 { fill: #e1e1e1;font-weight: bold } - .terminal-641812469-r6 { fill: #e1e1e1;font-style: italic; } - .terminal-641812469-r7 { fill: #98729f;font-weight: bold } - .terminal-641812469-r8 { fill: #d0b344 } - .terminal-641812469-r9 { fill: #98a84b } - .terminal-641812469-r10 { fill: #00823d;font-style: italic; } - .terminal-641812469-r11 { fill: #ffcf56 } - .terminal-641812469-r12 { fill: #e76580 } - .terminal-641812469-r13 { fill: #211505;font-weight: bold } - .terminal-641812469-r14 { fill: #f5e5e9;font-weight: bold } - .terminal-641812469-r15 { fill: #b86b00 } - .terminal-641812469-r16 { fill: #780028 } + .terminal-2572323619-r1 { fill: #c5c8c6 } + .terminal-2572323619-r2 { fill: #e3e3e3 } + .terminal-2572323619-r3 { fill: #e1e1e1 } + .terminal-2572323619-r4 { fill: #e1e1e1;text-decoration: underline; } + .terminal-2572323619-r5 { fill: #e1e1e1;font-weight: bold } + .terminal-2572323619-r6 { fill: #e1e1e1;font-style: italic; } + .terminal-2572323619-r7 { fill: #98729f;font-weight: bold } + .terminal-2572323619-r8 { fill: #d0b344 } + .terminal-2572323619-r9 { fill: #98a84b } + .terminal-2572323619-r10 { fill: #00823d;font-style: italic; } + .terminal-2572323619-r11 { fill: #ffcf56 } + .terminal-2572323619-r12 { fill: #e76580 } + .terminal-2572323619-r13 { fill: #211505;font-weight: bold } + .terminal-2572323619-r14 { fill: #f5e5e9;font-weight: bold } + .terminal-2572323619-r15 { fill: #b86b00 } + .terminal-2572323619-r16 { fill: #780028 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - Textual Keys + Textual Keys - - - - Textual Keys - ╭────────────────────────────────────────────────────────────────────────────╮ - Press some keys! - - To quit the app press ctrl+ctwice or press the Quit button below. - ╰────────────────────────────────────────────────────────────────────────────╯ - Key(key='a'character='a'name='a'is_printable=True) - Key(key='b'character='b'name='b'is_printable=True) - - - - - - - - - - - - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - ClearQuit - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + Textual Keys + ╭────────────────────────────────────────────────────────────────────────────╮ + Press some keys! + + To quit the app press ctrl+ctwice or press the Quit button below. + ╰────────────────────────────────────────────────────────────────────────────╯ + Key(key='a'character='a'name='a'is_printable=True) + Key(key='b'character='b'name='b'is_printable=True) + + + + + + + + + + + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + ClearQuit + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ @@ -16800,136 +16809,136 @@ font-weight: 700; } - .terminal-513592180-matrix { + .terminal-1675990519-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-513592180-title { + .terminal-1675990519-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-513592180-r1 { fill: #c5c8c6 } - .terminal-513592180-r2 { fill: #e3e3e3 } - .terminal-513592180-r3 { fill: #ff0000 } - .terminal-513592180-r4 { fill: #e1e1e1 } - .terminal-513592180-r5 { fill: #dde8f3;font-weight: bold } - .terminal-513592180-r6 { fill: #ddedf9 } + .terminal-1675990519-r1 { fill: #c5c8c6 } + .terminal-1675990519-r2 { fill: #e3e3e3 } + .terminal-1675990519-r3 { fill: #e1e1e1 } + .terminal-1675990519-r4 { fill: #ff0000 } + .terminal-1675990519-r5 { fill: #dde8f3;font-weight: bold } + .terminal-1675990519-r6 { fill: #ddedf9 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - DialogIssueApp + DialogIssueApp - - - - DialogIssueApp - - - - - - ─────────────────────────────────────── - - - - - - This should not cause a scrollbar to ap - - - - - - ─────────────────────────────────────── - - - - -  D  Toggle the dialog  + + + + DialogIssueApp + + + + + + ─────────────────────────────────────── + + + + + + This should not cause a scrollbar to ap + + + + + + ─────────────────────────────────────── + + + + +  D  Toggle the dialog  @@ -17927,135 +17936,135 @@ font-weight: 700; } - .terminal-2423395429-matrix { + .terminal-543315859-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2423395429-title { + .terminal-543315859-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2423395429-r1 { fill: #c5c8c6 } - .terminal-2423395429-r2 { fill: #e3e3e3 } - .terminal-2423395429-r3 { fill: #e1e1e1 } - .terminal-2423395429-r4 { fill: #dde8f3;font-weight: bold } - .terminal-2423395429-r5 { fill: #ddedf9 } + .terminal-543315859-r1 { fill: #c5c8c6 } + .terminal-543315859-r2 { fill: #e3e3e3 } + .terminal-543315859-r3 { fill: #e1e1e1 } + .terminal-543315859-r4 { fill: #dde8f3;font-weight: bold } + .terminal-543315859-r5 { fill: #ddedf9 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - ModalApp + ModalApp - - - - ModalApp - Hello - - - - - - - - - - - - - - - - - - - - - -  ⏎  Open Dialog  + + + + ModalApp + Hello + + + + + + + + + + + + + + + + + + + + + +  ⏎  Open Dialog  @@ -18722,136 +18731,136 @@ font-weight: 700; } - .terminal-1829927563-matrix { + .terminal-1812315577-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1829927563-title { + .terminal-1812315577-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1829927563-r1 { fill: #c5c8c6 } - .terminal-1829927563-r2 { fill: #e3e3e3 } - .terminal-1829927563-r3 { fill: #e1e1e1 } - .terminal-1829927563-r4 { fill: #004578 } - .terminal-1829927563-r5 { fill: #e0e8ee;font-weight: bold } - .terminal-1829927563-r6 { fill: #e2e3e3 } - .terminal-1829927563-r7 { fill: #ddedf9 } + .terminal-1812315577-r1 { fill: #c5c8c6 } + .terminal-1812315577-r2 { fill: #e3e3e3 } + .terminal-1812315577-r3 { fill: #e1e1e1 } + .terminal-1812315577-r4 { fill: #004578 } + .terminal-1812315577-r5 { fill: #e0e8ee;font-weight: bold } + .terminal-1812315577-r6 { fill: #e2e3e3 } + .terminal-1812315577-r7 { fill: #ddedf9 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - OptionListApp + OptionListApp - - - - OptionListApp - - - - ────────────────────────────────────────────────────── - Aerilon - Aquaria - Canceron - Caprica - Gemenon - Leonis - Libran - Picon - Sagittaron - Scorpia - Tauron - Virgon - - - ────────────────────────────────────────────────────── - - - + + + + OptionListApp + + + + ────────────────────────────────────────────────────── + Aerilon + Aquaria + Canceron + Caprica + Gemenon + Leonis + Libran + Picon + Sagittaron + Scorpia + Tauron + Virgon + + + ────────────────────────────────────────────────────── + + + @@ -18882,139 +18891,139 @@ font-weight: 700; } - .terminal-2055091312-matrix { + .terminal-1041266590-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2055091312-title { + .terminal-1041266590-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2055091312-r1 { fill: #c5c8c6 } - .terminal-2055091312-r2 { fill: #e3e3e3 } - .terminal-2055091312-r3 { fill: #e1e1e1 } - .terminal-2055091312-r4 { fill: #004578 } - .terminal-2055091312-r5 { fill: #e0e8ee;font-weight: bold } - .terminal-2055091312-r6 { fill: #e2e3e3 } - .terminal-2055091312-r7 { fill: #42464b } - .terminal-2055091312-r8 { fill: #777a7e } - .terminal-2055091312-r9 { fill: #14191f } - .terminal-2055091312-r10 { fill: #ddedf9 } + .terminal-1041266590-r1 { fill: #c5c8c6 } + .terminal-1041266590-r2 { fill: #e3e3e3 } + .terminal-1041266590-r3 { fill: #e1e1e1 } + .terminal-1041266590-r4 { fill: #004578 } + .terminal-1041266590-r5 { fill: #e0e8ee;font-weight: bold } + .terminal-1041266590-r6 { fill: #e2e3e3 } + .terminal-1041266590-r7 { fill: #42464b } + .terminal-1041266590-r8 { fill: #777a7e } + .terminal-1041266590-r9 { fill: #14191f } + .terminal-1041266590-r10 { fill: #ddedf9 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - OptionListApp + OptionListApp - - - - OptionListApp - - - - ────────────────────────────────────────────────────── - Aerilon - Aquaria - ──────────────────────────────────────────────────── - Canceron - Caprica - ──────────────────────────────────────────────────── - Gemenon - ──────────────────────────────────────────────────── - Leonis - Libran - ────────────────────────────────────────────────────▅▅ - Picon - ──────────────────────────────────────────────────── - Sagittaron - ────────────────────────────────────────────────────── - - - + + + + OptionListApp + + + + ────────────────────────────────────────────────────── + Aerilon + Aquaria + ──────────────────────────────────────────────────── + Canceron + Caprica + ──────────────────────────────────────────────────── + Gemenon + ──────────────────────────────────────────────────── + Leonis + Libran + ────────────────────────────────────────────────────▅▅ + Picon + ──────────────────────────────────────────────────── + Sagittaron + ────────────────────────────────────────────────────── + + + @@ -19045,140 +19054,140 @@ font-weight: 700; } - .terminal-1395459687-matrix { + .terminal-1620527509-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1395459687-title { + .terminal-1620527509-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1395459687-r1 { fill: #c5c8c6 } - .terminal-1395459687-r2 { fill: #e3e3e3 } - .terminal-1395459687-r3 { fill: #e1e1e1 } - .terminal-1395459687-r4 { fill: #004578 } - .terminal-1395459687-r5 { fill: #e0e8ee;font-weight: bold;font-style: italic; } - .terminal-1395459687-r6 { fill: #e2e3e3 } - .terminal-1395459687-r7 { fill: #e0e8ee;font-weight: bold } - .terminal-1395459687-r8 { fill: #14191f } - .terminal-1395459687-r9 { fill: #e2e3e3;font-style: italic; } - .terminal-1395459687-r10 { fill: #e2e3e3;font-weight: bold } - .terminal-1395459687-r11 { fill: #ddedf9 } + .terminal-1620527509-r1 { fill: #c5c8c6 } + .terminal-1620527509-r2 { fill: #e3e3e3 } + .terminal-1620527509-r3 { fill: #e1e1e1 } + .terminal-1620527509-r4 { fill: #004578 } + .terminal-1620527509-r5 { fill: #e0e8ee;font-weight: bold;font-style: italic; } + .terminal-1620527509-r6 { fill: #e2e3e3 } + .terminal-1620527509-r7 { fill: #e0e8ee;font-weight: bold } + .terminal-1620527509-r8 { fill: #14191f } + .terminal-1620527509-r9 { fill: #e2e3e3;font-style: italic; } + .terminal-1620527509-r10 { fill: #e2e3e3;font-weight: bold } + .terminal-1620527509-r11 { fill: #ddedf9 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - OptionListApp + OptionListApp - - - - OptionListApp - - - - ────────────────────────────────────────────────────── -                   Data for Aerilon                   - ┏━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┓ - Patron God   Population    Capital City   ▂▂ - ┡━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━┩ - Demeter      1.2 Billion   Gaoth           - └───────────────┴────────────────┴─────────────────┘ -                   Data for Aquaria                   - ┏━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┓ - Patron God    Population   Capital City    - ┡━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━┩ - Hermes        75,000       None            - └────────────────┴───────────────┴─────────────────┘ -                  Data for Canceron                   - ┏━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┓ - ────────────────────────────────────────────────────── - - - + + + + OptionListApp + + + + ────────────────────────────────────────────────────── +                   Data for Aerilon                   + ┏━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┓ + Patron God   Population    Capital City   ▂▂ + ┡━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━┩ + Demeter      1.2 Billion   Gaoth           + └───────────────┴────────────────┴─────────────────┘ +                   Data for Aquaria                   + ┏━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┓ + Patron God    Population   Capital City    + ┡━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━┩ + Hermes        75,000       None            + └────────────────┴───────────────┴─────────────────┘ +                  Data for Canceron                   + ┏━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┓ + ────────────────────────────────────────────────────── + + + @@ -19367,136 +19376,136 @@ font-weight: 700; } - .terminal-3980370474-matrix { + .terminal-1392305496-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3980370474-title { + .terminal-1392305496-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3980370474-r1 { fill: #ffff00 } - .terminal-3980370474-r2 { fill: #e3e3e3 } - .terminal-3980370474-r3 { fill: #c5c8c6 } - .terminal-3980370474-r4 { fill: #e1e1e1 } - .terminal-3980370474-r5 { fill: #dde8f3;font-weight: bold } - .terminal-3980370474-r6 { fill: #ddedf9 } + .terminal-1392305496-r1 { fill: #ffff00 } + .terminal-1392305496-r2 { fill: #e3e3e3 } + .terminal-1392305496-r3 { fill: #c5c8c6 } + .terminal-1392305496-r4 { fill: #e1e1e1 } + .terminal-1392305496-r5 { fill: #dde8f3;font-weight: bold } + .terminal-1392305496-r6 { fill: #ddedf9 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - Layers + Layers - - - - ──────────────────────────────────Layers - It's full of stars! My God! It's full of sta - - This should float over the top - - - ────────────────────────────────── - - - - - - - - - - - - - - - - -  T  Toggle Screen  + + + + ──────────────────────────────────Layers + It's full of stars! My God! It's full of sta + + This should float over the top + + + ────────────────────────────────── + + + + + + + + + + + + + + + + +  T  Toggle Screen  @@ -19526,136 +19535,136 @@ font-weight: 700; } - .terminal-1053593998-matrix { + .terminal-3727479996-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1053593998-title { + .terminal-3727479996-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1053593998-r1 { fill: #ffff00 } - .terminal-1053593998-r2 { fill: #e3e3e3 } - .terminal-1053593998-r3 { fill: #c5c8c6 } - .terminal-1053593998-r4 { fill: #ddeedd } - .terminal-1053593998-r5 { fill: #dde8f3;font-weight: bold } - .terminal-1053593998-r6 { fill: #ddedf9 } + .terminal-3727479996-r1 { fill: #ffff00 } + .terminal-3727479996-r2 { fill: #e3e3e3 } + .terminal-3727479996-r3 { fill: #c5c8c6 } + .terminal-3727479996-r4 { fill: #ddeedd } + .terminal-3727479996-r5 { fill: #dde8f3;font-weight: bold } + .terminal-3727479996-r6 { fill: #ddedf9 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - Layers + Layers - - - - ──────────────────────────────────Layers - It's full of stars! My God! It's full of sta - - This should float over the top - - - ────────────────────────────────── - - - - - - - - - - - - - - - - -  T  Toggle Screen  + + + + ──────────────────────────────────Layers + It's full of stars! My God! It's full of sta + + This should float over the top + + + ────────────────────────────────── + + + + + + + + + + + + + + + + +  T  Toggle Screen  @@ -19685,142 +19694,142 @@ font-weight: 700; } - .terminal-700023403-matrix { + .terminal-1570661136-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-700023403-title { + .terminal-1570661136-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-700023403-r1 { fill: #c5c8c6 } - .terminal-700023403-r2 { fill: #eae3e5 } - .terminal-700023403-r3 { fill: #e8e0e7 } - .terminal-700023403-r4 { fill: #efe9e4 } - .terminal-700023403-r5 { fill: #ede6e6 } - .terminal-700023403-r6 { fill: #efeedf } - .terminal-700023403-r7 { fill: #e9eee5 } - .terminal-700023403-r8 { fill: #e2edeb } - .terminal-700023403-r9 { fill: #e4eee8;font-weight: bold } - .terminal-700023403-r10 { fill: #dfebed;font-weight: bold } - .terminal-700023403-r11 { fill: #dfe9ed } - .terminal-700023403-r12 { fill: #e3e6eb;font-weight: bold } - .terminal-700023403-r13 { fill: #e6e3e9 } + .terminal-1570661136-r1 { fill: #c5c8c6 } + .terminal-1570661136-r2 { fill: #eae3e5 } + .terminal-1570661136-r3 { fill: #e8e0e7 } + .terminal-1570661136-r4 { fill: #efe9e4 } + .terminal-1570661136-r5 { fill: #ede6e6 } + .terminal-1570661136-r6 { fill: #efeedf } + .terminal-1570661136-r7 { fill: #e9eee5 } + .terminal-1570661136-r8 { fill: #e2edeb } + .terminal-1570661136-r9 { fill: #e4eee8;font-weight: bold } + .terminal-1570661136-r10 { fill: #dfebed;font-weight: bold } + .terminal-1570661136-r11 { fill: #dfe9ed } + .terminal-1570661136-r12 { fill: #e3e6eb;font-weight: bold } + .terminal-1570661136-r13 { fill: #e6e3e9 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - PlaceholderApp + PlaceholderApp - - - - - Placeholder p2 here! - This is a custom label for p1. - #p4 - #p3#p5Placeholde - r - - Lorem ipsum dolor sit  - 26 x 6amet, consectetur 27 x 6 - adipiscing elit. Etiam  - feugiat ac elit sit amet  - - - Lorem ipsum dolor sit amet,  - consectetur adipiscing elit. Etiam 40 x 6 - feugiat ac elit sit amet accumsan.  - Suspendisse bibendum nec libero quis  - gravida. Phasellus id eleifend ligula. - Nullam imperdiet sem tellus, sed  - vehicula nisl faucibus sit amet. Lorem ipsum dolor sit amet,  - Praesent iaculis tempor ultricies. Sedconsectetur adipiscing elit. Etiam  - lacinia, tellus id rutrum lacinia, feugiat ac elit sit amet accumsan.  - sapien sapien congue mauris, sit amet Suspendisse bibendum nec libero quis  + + + + + Placeholder p2 here! + This is a custom label for p1. + #p4 + #p3#p5Placeholde + r + + Lorem ipsum dolor sit  + 26 x 6amet, consectetur 27 x 6 + adipiscing elit. Etiam  + feugiat ac elit sit amet  + + + Lorem ipsum dolor sit amet,  + consectetur adipiscing elit. Etiam 40 x 6 + feugiat ac elit sit amet accumsan.  + Suspendisse bibendum nec libero quis  + gravida. Phasellus id eleifend ligula. + Nullam imperdiet sem tellus, sed  + vehicula nisl faucibus sit amet. Lorem ipsum dolor sit amet,  + Praesent iaculis tempor ultricies. Sedconsectetur adipiscing elit. Etiam  + lacinia, tellus id rutrum lacinia, feugiat ac elit sit amet accumsan.  + sapien sapien congue mauris, sit amet Suspendisse bibendum nec libero quis  @@ -20006,135 +20015,135 @@ font-weight: 700; } - .terminal-1426024135-matrix { + .terminal-230009450-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1426024135-title { + .terminal-230009450-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1426024135-r1 { fill: #c5c8c6 } - .terminal-1426024135-r2 { fill: #4ebf71 } - .terminal-1426024135-r3 { fill: #e1e1e1 } - .terminal-1426024135-r4 { fill: #dde8f3;font-weight: bold } - .terminal-1426024135-r5 { fill: #ddedf9 } + .terminal-230009450-r1 { fill: #c5c8c6 } + .terminal-230009450-r2 { fill: #e1e1e1 } + .terminal-230009450-r3 { fill: #4ebf71 } + .terminal-230009450-r4 { fill: #dde8f3;font-weight: bold } + .terminal-230009450-r5 { fill: #ddedf9 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - IndeterminateProgressBar + IndeterminateProgressBar - - - - - - - - - - - - - - - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━100%--:--:-- - - - - - - - - - - - -  S  Start  + + + + + + + + + + + + + + + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━100%--:--:-- + + + + + + + + + + + +  S  Start  @@ -20164,136 +20173,137 @@ font-weight: 700; } - .terminal-1998155485-matrix { + .terminal-3162092160-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1998155485-title { + .terminal-3162092160-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1998155485-r1 { fill: #c5c8c6 } - .terminal-1998155485-r2 { fill: #b93c5b } - .terminal-1998155485-r3 { fill: #1e1e1e } - .terminal-1998155485-r4 { fill: #e1e1e1;text-decoration: underline; } - .terminal-1998155485-r5 { fill: #dde8f3;font-weight: bold } - .terminal-1998155485-r6 { fill: #ddedf9 } + .terminal-3162092160-r1 { fill: #c5c8c6 } + .terminal-3162092160-r2 { fill: #e1e1e1 } + .terminal-3162092160-r3 { fill: #b93c5b } + .terminal-3162092160-r4 { fill: #1e1e1e } + .terminal-3162092160-r5 { fill: #e1e1e1;text-decoration: underline; } + .terminal-3162092160-r6 { fill: #dde8f3;font-weight: bold } + .terminal-3162092160-r7 { fill: #ddedf9 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - StyledProgressBar + StyledProgressBar - - - - - - - - - - - - - - - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━100%--:--:-- - - - - - - - - - - - -  S  Start  + + + + + + + + + + + + + + + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━100%--:--:-- + + + + + + + + + + + +  S  Start  @@ -20323,136 +20333,136 @@ font-weight: 700; } - .terminal-836496735-matrix { + .terminal-1630089489-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-836496735-title { + .terminal-1630089489-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-836496735-r1 { fill: #c5c8c6 } - .terminal-836496735-r2 { fill: #fea62b } - .terminal-836496735-r3 { fill: #323232 } - .terminal-836496735-r4 { fill: #e1e1e1 } - .terminal-836496735-r5 { fill: #dde8f3;font-weight: bold } - .terminal-836496735-r6 { fill: #ddedf9 } + .terminal-1630089489-r1 { fill: #c5c8c6 } + .terminal-1630089489-r2 { fill: #e1e1e1 } + .terminal-1630089489-r3 { fill: #fea62b } + .terminal-1630089489-r4 { fill: #323232 } + .terminal-1630089489-r5 { fill: #dde8f3;font-weight: bold } + .terminal-1630089489-r6 { fill: #ddedf9 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - IndeterminateProgressBar + IndeterminateProgressBar - - - - - - - - - - - - - - - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━39%00:00:07 - - - - - - - - - - - -  S  Start  + + + + + + + + + + + + + + + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━39%00:00:07 + + + + + + + + + + + +  S  Start  @@ -20482,137 +20492,138 @@ font-weight: 700; } - .terminal-1783624548-matrix { + .terminal-1532901142-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1783624548-title { + .terminal-1532901142-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1783624548-r1 { fill: #c5c8c6 } - .terminal-1783624548-r2 { fill: #004578 } - .terminal-1783624548-r3 { fill: #152939 } - .terminal-1783624548-r4 { fill: #1e1e1e } - .terminal-1783624548-r5 { fill: #e1e1e1;text-decoration: underline; } - .terminal-1783624548-r6 { fill: #dde8f3;font-weight: bold } - .terminal-1783624548-r7 { fill: #ddedf9 } + .terminal-1532901142-r1 { fill: #c5c8c6 } + .terminal-1532901142-r2 { fill: #e1e1e1 } + .terminal-1532901142-r3 { fill: #004578 } + .terminal-1532901142-r4 { fill: #152939 } + .terminal-1532901142-r5 { fill: #1e1e1e } + .terminal-1532901142-r6 { fill: #e1e1e1;text-decoration: underline; } + .terminal-1532901142-r7 { fill: #dde8f3;font-weight: bold } + .terminal-1532901142-r8 { fill: #ddedf9 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - StyledProgressBar + StyledProgressBar - - - - - - - - - - - - - - - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━39%00:00:07 - - - - - - - - - - - -  S  Start  + + + + + + + + + + + + + + + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━39%00:00:07 + + + + + + + + + + + +  S  Start  @@ -20642,136 +20653,136 @@ font-weight: 700; } - .terminal-2036756687-matrix { + .terminal-3440292978-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2036756687-title { + .terminal-3440292978-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2036756687-r1 { fill: #c5c8c6 } - .terminal-2036756687-r2 { fill: #323232 } - .terminal-2036756687-r3 { fill: #b93c5b } - .terminal-2036756687-r4 { fill: #e1e1e1 } - .terminal-2036756687-r5 { fill: #dde8f3;font-weight: bold } - .terminal-2036756687-r6 { fill: #ddedf9 } + .terminal-3440292978-r1 { fill: #c5c8c6 } + .terminal-3440292978-r2 { fill: #e1e1e1 } + .terminal-3440292978-r3 { fill: #323232 } + .terminal-3440292978-r4 { fill: #b93c5b } + .terminal-3440292978-r5 { fill: #dde8f3;font-weight: bold } + .terminal-3440292978-r6 { fill: #ddedf9 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - IndeterminateProgressBar + IndeterminateProgressBar - - - - - - - - - - - - - - - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━--%--:--:-- - - - - - - - - - - - -  S  Start  + + + + + + + + + + + + + + + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━--%--:--:-- + + + + + + + + + + + +  S  Start  @@ -20801,137 +20812,138 @@ font-weight: 700; } - .terminal-4086988071-matrix { + .terminal-4046569674-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-4086988071-title { + .terminal-4046569674-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-4086988071-r1 { fill: #c5c8c6 } - .terminal-4086988071-r2 { fill: #fea62b } - .terminal-4086988071-r3 { fill: #004578 } - .terminal-4086988071-r4 { fill: #1e1e1e } - .terminal-4086988071-r5 { fill: #e1e1e1;text-decoration: underline; } - .terminal-4086988071-r6 { fill: #dde8f3;font-weight: bold } - .terminal-4086988071-r7 { fill: #ddedf9 } + .terminal-4046569674-r1 { fill: #c5c8c6 } + .terminal-4046569674-r2 { fill: #e1e1e1 } + .terminal-4046569674-r3 { fill: #fea62b } + .terminal-4046569674-r4 { fill: #004578 } + .terminal-4046569674-r5 { fill: #1e1e1e } + .terminal-4046569674-r6 { fill: #e1e1e1;text-decoration: underline; } + .terminal-4046569674-r7 { fill: #dde8f3;font-weight: bold } + .terminal-4046569674-r8 { fill: #ddedf9 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - StyledProgressBar + StyledProgressBar - - - - - - - - - - - - - - - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━--%--:--:-- - - - - - - - - - - - -  S  Start  + + + + + + + + + + + + + + + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━--%--:--:-- + + + + + + + + + + + +  S  Start  @@ -21444,137 +21456,137 @@ font-weight: 700; } - .terminal-2779683141-matrix { + .terminal-1869274227-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2779683141-title { + .terminal-1869274227-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2779683141-r1 { fill: #c5c8c6 } - .terminal-2779683141-r2 { fill: #e3e3e3 } - .terminal-2779683141-r3 { fill: #008000 } - .terminal-2779683141-r4 { fill: #ffff00 } - .terminal-2779683141-r5 { fill: #e1e1e1 } - .terminal-2779683141-r6 { fill: #dde8f3;font-weight: bold } - .terminal-2779683141-r7 { fill: #ddedf9 } + .terminal-1869274227-r1 { fill: #c5c8c6 } + .terminal-1869274227-r2 { fill: #e3e3e3 } + .terminal-1869274227-r3 { fill: #008000 } + .terminal-1869274227-r4 { fill: #ffff00 } + .terminal-1869274227-r5 { fill: #e1e1e1 } + .terminal-1869274227-r6 { fill: #dde8f3;font-weight: bold } + .terminal-1869274227-r7 { fill: #ddedf9 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - VerticalRemoveApp + VerticalRemoveApp - - - - VerticalRemoveApp - ────────────────────────────────────────────────────────────────────────────── - ──────────────────── - This is a test label - ──────────────────── - ────────────────────────────────────────────────────────────────────────────── - - - - - - - - - - - - - - - - - -  A  Add  D  Delete  + + + + VerticalRemoveApp + ────────────────────────────────────────────────────────────────────────────── + ──────────────────── + This is a test label + ──────────────────── + ────────────────────────────────────────────────────────────────────────────── + + + + + + + + + + + + + + + + + +  A  Add  D  Delete  @@ -21604,135 +21616,135 @@ font-weight: 700; } - .terminal-3992644605-matrix { + .terminal-1316892474-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3992644605-title { + .terminal-1316892474-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3992644605-r1 { fill: #c5c8c6 } - .terminal-3992644605-r2 { fill: #e3e3e3 } - .terminal-3992644605-r3 { fill: #e1e1e1 } - .terminal-3992644605-r4 { fill: #dde8f3;font-weight: bold } - .terminal-3992644605-r5 { fill: #ddedf9 } + .terminal-1316892474-r1 { fill: #c5c8c6 } + .terminal-1316892474-r2 { fill: #e3e3e3 } + .terminal-1316892474-r3 { fill: #e1e1e1 } + .terminal-1316892474-r4 { fill: #dde8f3;font-weight: bold } + .terminal-1316892474-r5 { fill: #ddedf9 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - ModalApp + ModalApp - - - - ModalApp - B - - - - - - - - - - - - - - - - - - - - - -  A  Push screen A  + + + + ModalApp + B + + + + + + + + + + + + + + + + + + + + + +  A  Push screen A  @@ -22075,134 +22087,134 @@ font-weight: 700; } - .terminal-2749576739-matrix { + .terminal-1647606097-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2749576739-title { + .terminal-1647606097-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2749576739-r1 { fill: #c5c8c6 } - .terminal-2749576739-r2 { fill: #e3e3e3 } - .terminal-2749576739-r3 { fill: #ff0000 } - .terminal-2749576739-r4 { fill: #dde2e8 } - .terminal-2749576739-r5 { fill: #ddedf9 } + .terminal-1647606097-r1 { fill: #c5c8c6 } + .terminal-1647606097-r2 { fill: #e3e3e3 } + .terminal-1647606097-r3 { fill: #ff0000 } + .terminal-1647606097-r4 { fill: #dde2e8 } + .terminal-1647606097-r5 { fill: #ddedf9 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - ScrollViewTester + ScrollViewTester - - - - ScrollViewTester -  1 ────────────────────────────────────────────────────────────────────────── - Welcome to line 980 - Welcome to line 981 - Welcome to line 982 - Welcome to line 983 - Welcome to line 984 - Welcome to line 985 - Welcome to line 986 - Welcome to line 987 - Welcome to line 988 - Welcome to line 989 - Welcome to line 990 - Welcome to line 991 - Welcome to line 992 - Welcome to line 993 - Welcome to line 994 - Welcome to line 995 - Welcome to line 996 - Welcome to line 997 - Welcome to line 998 - Welcome to line 999 - ────────────────────────────────────────────────────────────────────────────── + + + + ScrollViewTester +  1 ────────────────────────────────────────────────────────────────────────── + Welcome to line 980 + Welcome to line 981 + Welcome to line 982 + Welcome to line 983 + Welcome to line 984 + Welcome to line 985 + Welcome to line 986 + Welcome to line 987 + Welcome to line 988 + Welcome to line 989 + Welcome to line 990 + Welcome to line 991 + Welcome to line 992 + Welcome to line 993 + Welcome to line 994 + Welcome to line 995 + Welcome to line 996 + Welcome to line 997 + Welcome to line 998 + Welcome to line 999 + ────────────────────────────────────────────────────────────────────────────── @@ -22233,136 +22245,136 @@ font-weight: 700; } - .terminal-4198692207-matrix { + .terminal-1422407852-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-4198692207-title { + .terminal-1422407852-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-4198692207-r1 { fill: #c5c8c6 } - .terminal-4198692207-r2 { fill: #e3e3e3 } - .terminal-4198692207-r3 { fill: #e1e1e1 } - .terminal-4198692207-r4 { fill: #1e1e1e } - .terminal-4198692207-r5 { fill: #121212 } - .terminal-4198692207-r6 { fill: #787878 } - .terminal-4198692207-r7 { fill: #a8a8a8 } + .terminal-1422407852-r1 { fill: #c5c8c6 } + .terminal-1422407852-r2 { fill: #e3e3e3 } + .terminal-1422407852-r3 { fill: #e1e1e1 } + .terminal-1422407852-r4 { fill: #1e1e1e } + .terminal-1422407852-r5 { fill: #121212 } + .terminal-1422407852-r6 { fill: #787878 } + .terminal-1422407852-r7 { fill: #a8a8a8 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - SelectApp + SelectApp - - - - SelectApp - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - Select - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - - - - - - - - - - - - - - - - + + + + SelectApp + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + Select + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + + + + + + + + + + + + + + @@ -22393,140 +22405,140 @@ font-weight: 700; } - .terminal-1874975621-matrix { + .terminal-2035490498-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1874975621-title { + .terminal-2035490498-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1874975621-r1 { fill: #c5c8c6 } - .terminal-1874975621-r2 { fill: #e3e3e3 } - .terminal-1874975621-r3 { fill: #e1e1e1 } - .terminal-1874975621-r4 { fill: #1e1e1e } - .terminal-1874975621-r5 { fill: #0178d4 } - .terminal-1874975621-r6 { fill: #787878 } - .terminal-1874975621-r7 { fill: #a8a8a8 } - .terminal-1874975621-r8 { fill: #121212 } - .terminal-1874975621-r9 { fill: #ddedf9;font-weight: bold } - .terminal-1874975621-r10 { fill: #85beea;font-weight: bold } - .terminal-1874975621-r11 { fill: #e2e3e3 } + .terminal-2035490498-r1 { fill: #c5c8c6 } + .terminal-2035490498-r2 { fill: #e3e3e3 } + .terminal-2035490498-r3 { fill: #e1e1e1 } + .terminal-2035490498-r4 { fill: #1e1e1e } + .terminal-2035490498-r5 { fill: #0178d4 } + .terminal-2035490498-r6 { fill: #787878 } + .terminal-2035490498-r7 { fill: #a8a8a8 } + .terminal-2035490498-r8 { fill: #121212 } + .terminal-2035490498-r9 { fill: #ddedf9;font-weight: bold } + .terminal-2035490498-r10 { fill: #85beea;font-weight: bold } + .terminal-2035490498-r11 { fill: #e2e3e3 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - SelectApp + SelectApp - - - - SelectApp - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - Select - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - Select - I must not fear. - Fear is the mind-killer. - Fear is the little-death that brings total  - obliteration. - I will face my fear. - I will permit it to pass over me and through me. - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - - - - - - - + + + + SelectApp + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + Select + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + Select + I must not fear. + Fear is the mind-killer. + Fear is the little-death that brings total  + obliteration. + I will face my fear. + I will permit it to pass over me and through me. + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + + + + + @@ -22557,136 +22569,136 @@ font-weight: 700; } - .terminal-2181889025-matrix { + .terminal-4010426174-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2181889025-title { + .terminal-4010426174-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2181889025-r1 { fill: #c5c8c6 } - .terminal-2181889025-r2 { fill: #e3e3e3 } - .terminal-2181889025-r3 { fill: #e1e1e1 } - .terminal-2181889025-r4 { fill: #1e1e1e } - .terminal-2181889025-r5 { fill: #0178d4 } - .terminal-2181889025-r6 { fill: #e2e2e2 } - .terminal-2181889025-r7 { fill: #a8a8a8 } + .terminal-4010426174-r1 { fill: #c5c8c6 } + .terminal-4010426174-r2 { fill: #e3e3e3 } + .terminal-4010426174-r3 { fill: #e1e1e1 } + .terminal-4010426174-r4 { fill: #1e1e1e } + .terminal-4010426174-r5 { fill: #0178d4 } + .terminal-4010426174-r6 { fill: #e2e2e2 } + .terminal-4010426174-r7 { fill: #a8a8a8 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - SelectApp + SelectApp - - - - I must not fear. - - - ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ - I must not fear. - ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - - - - - - - - - - - - - - - - - + + + + I must not fear. + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + I must not fear. + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + + + + + + + + + + + + + + @@ -22717,136 +22729,136 @@ font-weight: 700; } - .terminal-932889121-matrix { + .terminal-2914557706-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-932889121-title { + .terminal-2914557706-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-932889121-r1 { fill: #e1e1e1 } - .terminal-932889121-r2 { fill: #c5c8c6 } - .terminal-932889121-r3 { fill: #e1e1e1;font-weight: bold } - .terminal-932889121-r4 { fill: #1e1e1e } - .terminal-932889121-r5 { fill: #0178d4 } - .terminal-932889121-r6 { fill: #e2e3e3 } - .terminal-932889121-r7 { fill: #e3e8e8 } + .terminal-2914557706-r1 { fill: #e1e1e1 } + .terminal-2914557706-r2 { fill: #c5c8c6 } + .terminal-2914557706-r3 { fill: #e1e1e1;font-weight: bold } + .terminal-2914557706-r4 { fill: #1e1e1e } + .terminal-2914557706-r5 { fill: #0178d4 } + .terminal-2914557706-r6 { fill: #e2e3e3 } + .terminal-2914557706-r7 { fill: #e3e8e8 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - SwitchApp + SwitchApp - - - - - - - - Example switches - - - ▔▔▔▔▔▔▔▔ - off:      - ▁▁▁▁▁▁▁▁ - ▔▔▔▔▔▔▔▔ - on:       - ▁▁▁▁▁▁▁▁ - ▔▔▔▔▔▔▔▔ - focused:  - ▁▁▁▁▁▁▁▁ - ▔▔▔▔▔▔▔▔ - custom:   - ▁▁▁▁▁▁▁▁ - - - - + + + + + + + + Example switches + + + ▔▔▔▔▔▔▔▔ + off:      + ▁▁▁▁▁▁▁▁ + ▔▔▔▔▔▔▔▔ + on:       + ▁▁▁▁▁▁▁▁ + ▔▔▔▔▔▔▔▔ + focused:  + ▁▁▁▁▁▁▁▁ + ▔▔▔▔▔▔▔▔ + custom:   + ▁▁▁▁▁▁▁▁ + + + + From aff9bcdf9365d835e02e36a75b16f38b19e6c538 Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Mon, 15 May 2023 11:30:26 +0100 Subject: [PATCH 15/30] Fix clearing an OptionList See #2557, credit to Will: https://github.com/Textualize/textual/issues/2557#issuecomment-1546883815 --- src/textual/widgets/_option_list.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/textual/widgets/_option_list.py b/src/textual/widgets/_option_list.py index 3b9413181..7d9af219b 100644 --- a/src/textual/widgets/_option_list.py +++ b/src/textual/widgets/_option_list.py @@ -631,7 +631,7 @@ class OptionList(ScrollView, can_focus=True): self.highlighted = None self._mouse_hovering_over = None self.virtual_size = Size(self.scrollable_content_region.width, 0) - self.refresh() + self._request_content_tracking_refresh() return self def _set_option_disabled(self, index: int, disabled: bool) -> Self: From fe7812d94d5bb703d01767c657e105bb0f2d2166 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Mon, 15 May 2023 13:12:57 +0100 Subject: [PATCH 16/30] Add regression test for #2563. --- tests/test_paste.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/test_paste.py b/tests/test_paste.py index 774ad5038..45be6d536 100644 --- a/tests/test_paste.py +++ b/tests/test_paste.py @@ -1,5 +1,6 @@ from textual import events from textual.app import App +from textual.widgets import Input async def test_paste_app(): @@ -16,3 +17,28 @@ async def test_paste_app(): assert len(paste_events) == 1 assert paste_events[0].text == "Hello" + + +async def test_empty_paste(): + """Regression test for https://github.com/Textualize/textual/issues/2563.""" + + paste_events = [] + + class MyInput(Input): + def on_paste(self, event): + super()._on_paste(event) + paste_events.append(event) + + class PasteApp(App): + def compose(self): + yield MyInput() + + def key_p(self): + self.query_one(MyInput).post_message(events.Paste("")) + + app = PasteApp() + async with app.run_test() as pilot: + await pilot.press("p") + assert app.query_one(MyInput).value == "" + assert len(paste_events) == 1 + assert paste_events[0].text == "" From 81289c328a5f9cf63ce1a821e602471635164ae1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Mon, 15 May 2023 13:22:44 +0100 Subject: [PATCH 17/30] Fix empty paste. Related issues: #2563. --- CHANGELOG.md | 1 + src/textual/widgets/_input.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e7cdebf4..5991f818d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Fixed `TreeNode.collapse` and `TreeNode.collapse_all` not posting a `Tree.NodeCollapsed` message https://github.com/Textualize/textual/issues/2535 - Fixed `TreeNode.toggle` and `TreeNode.toggle_all` not posting a `Tree.NodeExpanded` or `Tree.NodeCollapsed` message https://github.com/Textualize/textual/issues/2535 - `footer--description` component class was being ignored https://github.com/Textualize/textual/issues/2544 +- Pasting empty selection in `Input` would raise an exception https://github.com/Textualize/textual/issues/2563 ### Added diff --git a/src/textual/widgets/_input.py b/src/textual/widgets/_input.py index 9e5bf2d07..7e764e868 100644 --- a/src/textual/widgets/_input.py +++ b/src/textual/widgets/_input.py @@ -332,7 +332,7 @@ class Input(Widget, can_focus=True): event.prevent_default() def _on_paste(self, event: events.Paste) -> None: - line = event.text.splitlines()[0] + line = event.text.splitlines()[0] if event.text else "" self.insert_text_at_cursor(line) event.stop() From f02e2fcdd9a5a57e26966b72f54eb25abf761189 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Mon, 15 May 2023 14:12:17 +0100 Subject: [PATCH 18/30] Short-circuit paste on empty text. Related comments: https://github.com/Textualize/textual/pull/2568#discussion_r1193790630 --- src/textual/widgets/_input.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/textual/widgets/_input.py b/src/textual/widgets/_input.py index 7e764e868..7ed037890 100644 --- a/src/textual/widgets/_input.py +++ b/src/textual/widgets/_input.py @@ -332,9 +332,10 @@ class Input(Widget, can_focus=True): event.prevent_default() def _on_paste(self, event: events.Paste) -> None: - line = event.text.splitlines()[0] if event.text else "" - self.insert_text_at_cursor(line) - event.stop() + if event.text: + line = event.text.splitlines()[0] + self.insert_text_at_cursor(line) + event.stop() async def _on_click(self, event: events.Click) -> None: offset = event.get_content_offset(self) From 720bd37bba7fa63b3a2bf1e299cb6272a083b3e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Mon, 15 May 2023 14:22:16 +0100 Subject: [PATCH 19/30] Consume event. --- src/textual/widgets/_input.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/textual/widgets/_input.py b/src/textual/widgets/_input.py index 7ed037890..e14dcdf10 100644 --- a/src/textual/widgets/_input.py +++ b/src/textual/widgets/_input.py @@ -335,7 +335,7 @@ class Input(Widget, can_focus=True): if event.text: line = event.text.splitlines()[0] self.insert_text_at_cursor(line) - event.stop() + event.stop() async def _on_click(self, event: events.Click) -> None: offset = event.get_content_offset(self) From 6147c28dbf86c0d09a75b179b68fae1aeae1b26c Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Mon, 15 May 2023 15:53:17 +0200 Subject: [PATCH 20/30] arrange refactor (#2569) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * arrange refactor * Apply suggestions from code review Co-authored-by: Dave Pearson * Apply suggestions from code review Co-authored-by: Rodrigo Girão Serrão <5621605+rodrigogiraoserrao@users.noreply.github.com> --------- Co-authored-by: Dave Pearson Co-authored-by: Rodrigo Girão Serrão <5621605+rodrigogiraoserrao@users.noreply.github.com> --- src/textual/_arrange.py | 194 +++++++++++++++++------------- src/textual/_layout.py | 41 ++++++- src/textual/layouts/grid.py | 2 +- src/textual/layouts/horizontal.py | 4 +- src/textual/layouts/vertical.py | 2 +- 5 files changed, 152 insertions(+), 91 deletions(-) diff --git a/src/textual/_arrange.py b/src/textual/_arrange.py index b415d8983..87879573a 100644 --- a/src/textual/_arrange.py +++ b/src/textual/_arrange.py @@ -3,7 +3,7 @@ from __future__ import annotations from collections import defaultdict from fractions import Fraction from operator import attrgetter -from typing import TYPE_CHECKING, Sequence +from typing import TYPE_CHECKING, Iterable, Mapping, Sequence from ._layout import DockArrangeResult, WidgetPlacement from ._partition import partition @@ -16,6 +16,21 @@ if TYPE_CHECKING: TOP_Z = 2**31 - 1 +def _build_dock_layers(widgets: Iterable[Widget]) -> Mapping[str, Sequence[Widget]]: + """Organize widgets into layers. + + Args: + widgets: The widgets. + + Returns: + A mapping of layer name onto the widgets within the layer. + """ + layers: defaultdict[str, list[Widget]] = defaultdict(list) + for widget in widgets: + layers[widget.layer].append(widget) + return layers + + def arrange( widget: Widget, children: Sequence[Widget], size: Size, viewport: Size ) -> DockArrangeResult: @@ -30,107 +45,120 @@ def arrange( Widget arrangement information. """ - arrange_widgets: set[Widget] = set() - - dock_layers: defaultdict[str, list[Widget]] = defaultdict(list) - for child in children: - if child.display: - dock_layers[child.layer].append(child) - - width, height = size - placements: list[WidgetPlacement] = [] - add_placement = placements.append - - _WidgetPlacement = WidgetPlacement - top_z = TOP_Z scroll_spacing = Spacing() - null_spacing = Spacing() get_dock = attrgetter("styles.dock") styles = widget.styles + # Widgets which will be displayed + display_widgets = [child for child in children if child.styles.display != "none"] + + # Widgets organized into layers + dock_layers = _build_dock_layers(display_widgets) + layer_region = size.region for widgets in dock_layers.values(): region = layer_region + # Partition widgets into "layout" widgets (those that appears in the normal 'flow' of the + # document), and "dock" widgets which are positioned relative to an edge layout_widgets, dock_widgets = partition(get_dock, widgets) - arrange_widgets.update(dock_widgets) - top = right = bottom = left = 0 - - for dock_widget in dock_widgets: - edge = dock_widget.styles.dock - - box_model = dock_widget._get_box_model( - size, viewport, Fraction(size.width), Fraction(size.height) - ) - widget_width_fraction, widget_height_fraction, margin = box_model - - widget_width = int(widget_width_fraction) + margin.width - widget_height = int(widget_height_fraction) + margin.height - - if edge == "bottom": - dock_region = Region( - 0, height - widget_height, widget_width, widget_height - ) - bottom = max(bottom, widget_height) - elif edge == "top": - dock_region = Region(0, 0, widget_width, widget_height) - top = max(top, widget_height) - elif edge == "left": - dock_region = Region(0, 0, widget_width, widget_height) - left = max(left, widget_width) - elif edge == "right": - dock_region = Region( - width - widget_width, 0, widget_width, widget_height - ) - right = max(right, widget_width) - else: - # Should not occur, mainly to keep Mypy happy - raise AssertionError("invalid value for edge") # pragma: no-cover - - align_offset = dock_widget.styles._align_size( - (widget_width, widget_height), size - ) - dock_region = dock_region.shrink(margin).translate(align_offset) - add_placement( - _WidgetPlacement(dock_region, null_spacing, dock_widget, top_z, True) - ) - - dock_spacing = Spacing(top, right, bottom, left) - region = region.shrink(dock_spacing) - layout_placements, arranged_layout_widgets = widget._layout.arrange( - widget, layout_widgets, region.size + # Arrange docked widgets + _dock_placements, dock_spacing = _arrange_dock_widgets( + dock_widgets, size, viewport ) - if arranged_layout_widgets: + placements.extend(_dock_placements) + + # Reduce the region to compensate for docked widgets + region = region.shrink(dock_spacing) + + if layout_widgets: + # Arrange layout widgets (i.e. not docked) + layout_placements = widget._layout.arrange( + widget, + layout_widgets, + region.size, + ) + scroll_spacing = scroll_spacing.grow_maximum(dock_spacing) - arrange_widgets.update(arranged_layout_widgets) placement_offset = region.offset + # Perform any alignment of the widgets. if styles.align_horizontal != "left" or styles.align_vertical != "top": - placement_size = Region.from_union( - [ - placement.region.grow(placement.margin) - for placement in layout_placements - ] - ).size + bounding_region = WidgetPlacement.get_bounds(layout_placements) placement_offset += styles._align_size( - placement_size, region.size + bounding_region.size, region.size ).clamped if placement_offset: - layout_placements = [ - _WidgetPlacement( - _region + placement_offset, - margin, - layout_widget, - order, - fixed, - overlay, - ) - for _region, margin, layout_widget, order, fixed, overlay in layout_placements - ] + # Translate placements if required. + layout_placements = WidgetPlacement.translate( + layout_placements, placement_offset + ) - placements.extend(layout_placements) + placements.extend(layout_placements) - return DockArrangeResult(placements, arrange_widgets, scroll_spacing) + return DockArrangeResult(placements, set(display_widgets), scroll_spacing) + + +def _arrange_dock_widgets( + dock_widgets: Sequence[Widget], size: Size, viewport: Size +) -> tuple[list[WidgetPlacement], Spacing]: + """Arrange widgets which are *docked*. + + Args: + dock_widgets: Widgets with a non-empty dock. + size: Size of the container. + viewport: Size of the viewport. + + Returns: + A tuple of widget placements, and additional spacing around them + """ + _WidgetPlacement = WidgetPlacement + top_z = TOP_Z + width, height = size + null_spacing = Spacing() + + top = right = bottom = left = 0 + + placements: list[WidgetPlacement] = [] + append_placement = placements.append + + for dock_widget in dock_widgets: + edge = dock_widget.styles.dock + + box_model = dock_widget._get_box_model( + size, viewport, Fraction(size.width), Fraction(size.height) + ) + widget_width_fraction, widget_height_fraction, margin = box_model + + widget_width = int(widget_width_fraction) + margin.width + widget_height = int(widget_height_fraction) + margin.height + + if edge == "bottom": + dock_region = Region(0, height - widget_height, widget_width, widget_height) + bottom = max(bottom, widget_height) + elif edge == "top": + dock_region = Region(0, 0, widget_width, widget_height) + top = max(top, widget_height) + elif edge == "left": + dock_region = Region(0, 0, widget_width, widget_height) + left = max(left, widget_width) + elif edge == "right": + dock_region = Region(width - widget_width, 0, widget_width, widget_height) + right = max(right, widget_width) + else: + # Should not occur, mainly to keep Mypy happy + raise AssertionError("invalid value for edge") # pragma: no-cover + + align_offset = dock_widget.styles._align_size( + (widget_width, widget_height), size + ) + dock_region = dock_region.shrink(margin).translate(align_offset) + append_placement( + _WidgetPlacement(dock_region, null_spacing, dock_widget, top_z, True) + ) + dock_spacing = Spacing(top, right, bottom, left) + + return (placements, dock_spacing) diff --git a/src/textual/_layout.py b/src/textual/_layout.py index e0061c148..338b72192 100644 --- a/src/textual/_layout.py +++ b/src/textual/_layout.py @@ -2,17 +2,17 @@ from __future__ import annotations from abc import ABC, abstractmethod from dataclasses import dataclass -from typing import TYPE_CHECKING, ClassVar, NamedTuple +from typing import TYPE_CHECKING, ClassVar, Iterable, NamedTuple from ._spatial_map import SpatialMap -from .geometry import Region, Size, Spacing +from .geometry import Offset, Region, Size, Spacing if TYPE_CHECKING: from typing_extensions import TypeAlias from .widget import Widget -ArrangeResult: TypeAlias = "tuple[list[WidgetPlacement], set[Widget]]" +ArrangeResult: TypeAlias = "list[WidgetPlacement]" @dataclass @@ -76,6 +76,41 @@ class WidgetPlacement(NamedTuple): fixed: bool = False overlay: bool = False + @classmethod + def translate( + cls, placements: list[WidgetPlacement], offset: Offset + ) -> list[WidgetPlacement]: + """Move all placements by a given offset. + + Args: + placements: List of placements. + offset: Offset to add to placements. + + Returns: + Placements with adjusted region, or same instance if offset is null. + """ + if offset: + return [ + cls(region + offset, margin, layout_widget, order, fixed, overlay) + for region, margin, layout_widget, order, fixed, overlay in placements + ] + return placements + + @classmethod + def get_bounds(cls, placements: Iterable[WidgetPlacement]) -> Region: + """Get a bounding region around all placements. + + Args: + placements: A number of placements. + + Returns: + An optimal binding box around all placements. + """ + bounding_region = Region.from_union( + [placement.region.grow(placement.margin) for placement in placements] + ) + return bounding_region + class Layout(ABC): """Responsible for arranging Widgets in a view and rendering them.""" diff --git a/src/textual/layouts/grid.py b/src/textual/layouts/grid.py index 3ade70ab2..3b030f30f 100644 --- a/src/textual/layouts/grid.py +++ b/src/textual/layouts/grid.py @@ -156,4 +156,4 @@ class GridLayout(Layout): add_placement(WidgetPlacement(region, margin, widget)) add_widget(widget) - return (placements, set(widgets)) + return placements diff --git a/src/textual/layouts/horizontal.py b/src/textual/layouts/horizontal.py index d2fb3146e..a1c4eea4a 100644 --- a/src/textual/layouts/horizontal.py +++ b/src/textual/layouts/horizontal.py @@ -65,8 +65,6 @@ class HorizontalLayout(Layout): x = Fraction(box_models[0].margin.left if box_models else 0) - displayed_children = [child for child in children if child.display] - _Region = Region _WidgetPlacement = WidgetPlacement for widget, box_model, margin in zip(children, box_models, margins): @@ -86,4 +84,4 @@ class HorizontalLayout(Layout): if not overlay: x = next_x + margin - return placements, set(displayed_children) + return placements diff --git a/src/textual/layouts/vertical.py b/src/textual/layouts/vertical.py index 0001efdb6..c74332535 100644 --- a/src/textual/layouts/vertical.py +++ b/src/textual/layouts/vertical.py @@ -86,4 +86,4 @@ class VerticalLayout(Layout): if not overlay: y = next_y + margin - return placements, set(children) + return placements From 9b09b19e5f075b363707a2f2bfe8b2276f733930 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Tue, 16 May 2023 10:17:45 +0100 Subject: [PATCH 21/30] Update workflows. This caches the virtual environment so we don't have to download it every time (the cache can be cleared from the repository > Actions > Caches (on the left). We also split black formatting into a separate workflow. This means we can run black ONLY when *.py files are changed. It also means all other testing jobs don't need to _also_ check formatting. --- .github/workflows/black_format.yml | 21 +++++++++++++++++++++ .github/workflows/comment.yml | 5 +++-- .github/workflows/new_issue.yml | 3 ++- .github/workflows/pythonpackage.yml | 25 +++++++++++++------------ 4 files changed, 39 insertions(+), 15 deletions(-) create mode 100644 .github/workflows/black_format.yml diff --git a/.github/workflows/black_format.yml b/.github/workflows/black_format.yml new file mode 100644 index 000000000..aba9ba1f5 --- /dev/null +++ b/.github/workflows/black_format.yml @@ -0,0 +1,21 @@ + +name: Black format check + +on: + pull_request: + paths: + - '**.py' + +jobs: + black-format-check: + runs-on: "ubuntu-latest" + steps: + - uses: actions/checkout@v3.5.2 + - name: Set up Python 3.11 + uses: actions/setup-python@v4.6.0 + with: + python-version: 3.11 + - name: Install black + run: python -m pip install black + - name: Run black + run: black --check src diff --git a/.github/workflows/comment.yml b/.github/workflows/comment.yml index 46cf1677f..d26259360 100644 --- a/.github/workflows/comment.yml +++ b/.github/workflows/comment.yml @@ -1,7 +1,8 @@ -name: issues +name: Closed issue comment on: issues: types: [closed] + jobs: add-comment: runs-on: ubuntu-latest @@ -14,5 +15,5 @@ jobs: issue-number: ${{ github.event.issue.number }} body: | Don't forget to [star](https://github.com/Textualize/textual) the repository! - + Follow [@textualizeio](https://twitter.com/textualizeio) for Textual updates. diff --git a/.github/workflows/new_issue.yml b/.github/workflows/new_issue.yml index 3dd9d9a3a..cb55213ec 100644 --- a/.github/workflows/new_issue.yml +++ b/.github/workflows/new_issue.yml @@ -1,7 +1,8 @@ -name: issues +name: FAQ issue comment on: issues: types: [opened] + jobs: add-comment: if: ${{ !contains( 'willmcgugan,darrenburns,davep,rodrigogiraoserrao', github.actor ) }} diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index 2891b4bcf..79504215b 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -21,27 +21,28 @@ jobs: run: shell: bash steps: - - uses: actions/checkout@v1 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - architecture: x64 - - name: Install and configure Poetry + - uses: actions/checkout@v3.5.2 + - name: Install and configure Poetry # This could be cached, too... uses: snok/install-poetry@v1.3.3 with: version: 1.4.2 virtualenvs-in-project: true + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4.6.0 + with: + python-version: ${{ matrix.python-version }} + architecture: x64 + - name: Load cached venv + id: cached-poetry-dependencies + uses: actions/cache@v3 + with: + path: .venv + key: venv-${{ runner.os }}-${{ matrix.python-version }}-${{ hashFiles('**/poetry.lock') }} - name: Install dependencies run: poetry install --extras "dev" if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true' - - name: Format check with black - run: | - source $VENV - make format-check # - name: Typecheck with mypy # run: | -# source $VENV # make typecheck - name: Test with pytest run: | From 7a8d6920e81a819e44ee3a8d7bba83ba35140e02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Tue, 16 May 2023 11:14:54 +0100 Subject: [PATCH 22/30] Error when dismissing non-active screen. Related issues: #2575. --- CHANGELOG.md | 1 + src/textual/app.py | 2 +- src/textual/screen.py | 9 +++++++++ tests/test_screens.py | 14 ++++++++++++++ 4 files changed, 25 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5991f818d..bce29efda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - App `title` and `sub_title` attributes can be set to any type https://github.com/Textualize/textual/issues/2521 - Using `Widget.move_child` where the target and the child being moved are the same is now a no-op https://github.com/Textualize/textual/issues/1743 +- Calling `dismiss` on a screen that is not at the top of the stack now raises an exception https://github.com/Textualize/textual/issues/2575 ### Fixed diff --git a/src/textual/app.py b/src/textual/app.py index 12f66cf04..c1376cd22 100644 --- a/src/textual/app.py +++ b/src/textual/app.py @@ -156,7 +156,7 @@ class ScreenError(Exception): class ScreenStackError(ScreenError): - """Raised when attempting to pop the last screen from the stack.""" + """Raised when trying to manipulate the screen stack incorrectly.""" class CssPathError(Exception): diff --git a/src/textual/screen.py b/src/textual/screen.py index af0b006be..a16273121 100644 --- a/src/textual/screen.py +++ b/src/textual/screen.py @@ -28,6 +28,7 @@ from ._callback import invoke from ._compositor import Compositor, MapGeometry from ._context import visible_screen_stack from ._types import CallbackType +from .app import ScreenStackError from .binding import Binding from .css.match import match from .css.parse import parse_selectors @@ -771,6 +772,10 @@ class Screen(Generic[ScreenResultType], Widget): Args: result: The optional result to be passed to the result callback. + Raises: + ScreenStackError: If trying to dismiss a screen that is not at the top of + the stack. + Note: If the screen was pushed with a callback, the callback will be called with the given result and then a call to @@ -778,6 +783,10 @@ class Screen(Generic[ScreenResultType], Widget): no callback was provided calling this method is the same as simply calling [`App.pop_screen`][textual.app.App.pop_screen]. """ + if self is not self.app.screen: + raise ScreenStackError( + f"Can't dismiss screen {self} that's not at the top of the stack." + ) if result is not self._NoResult and self._result_callbacks: self._result_callbacks[-1](cast(ScreenResultType, result)) self.app.pop_screen() diff --git a/tests/test_screens.py b/tests/test_screens.py index 2e3dbfcbe..5b29b1dd5 100644 --- a/tests/test_screens.py +++ b/tests/test_screens.py @@ -192,3 +192,17 @@ async def test_auto_focus(): assert app.focused is None app.pop_screen() assert app.focused.id == "two" + + +async def test_dismiss_non_top_screen(): + class MyApp(App[None]): + async def key_p(self) -> None: + self.bottom, top = Screen(), Screen() + await self.push_screen(self.bottom) + await self.push_screen(top) + + app = MyApp() + async with app.run_test() as pilot: + await pilot.press("p") + with pytest.raises(ScreenStackError): + app.bottom.dismiss() From 93f4de918ce5dfa9f29ea693a2f815282def6625 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Tue, 16 May 2023 11:30:14 +0100 Subject: [PATCH 23/30] Fix circular import. --- src/textual/screen.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/textual/screen.py b/src/textual/screen.py index a16273121..09505cc53 100644 --- a/src/textual/screen.py +++ b/src/textual/screen.py @@ -28,7 +28,6 @@ from ._callback import invoke from ._compositor import Compositor, MapGeometry from ._context import visible_screen_stack from ._types import CallbackType -from .app import ScreenStackError from .binding import Binding from .css.match import match from .css.parse import parse_selectors @@ -784,6 +783,8 @@ class Screen(Generic[ScreenResultType], Widget): simply calling [`App.pop_screen`][textual.app.App.pop_screen]. """ if self is not self.app.screen: + from .app import ScreenStackError + raise ScreenStackError( f"Can't dismiss screen {self} that's not at the top of the stack." ) From a9a04dc37a9dad6c1a47cf2a842e109ea2b9ffbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Tue, 16 May 2023 11:43:31 +0100 Subject: [PATCH 24/30] Run workflows when they are changed. Related comments: https://github.com/Textualize/textual/pull/2577\#issuecomment-1549411724 --- .github/workflows/black_format.yml | 1 + .github/workflows/pythonpackage.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/black_format.yml b/.github/workflows/black_format.yml index aba9ba1f5..063068c35 100644 --- a/.github/workflows/black_format.yml +++ b/.github/workflows/black_format.yml @@ -4,6 +4,7 @@ name: Black format check on: pull_request: paths: + - '.github/workflows/black_format.yml' - '**.py' jobs: diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index 79504215b..27f36d406 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -3,6 +3,7 @@ name: Test Textual module on: pull_request: paths: + - '.github/workflows/pythonpackage.yml' - '**.py' - '**.pyi' - '**.css' From dde3ad397f82cbd32c17dfd812dfdb04fdbc0730 Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Tue, 16 May 2023 12:01:45 +0100 Subject: [PATCH 25/30] Remove duplicated setting of height for Select (#2576) --- src/textual/widgets/_select.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/textual/widgets/_select.py b/src/textual/widgets/_select.py index 25564af95..dddd9e157 100644 --- a/src/textual/widgets/_select.py +++ b/src/textual/widgets/_select.py @@ -185,10 +185,6 @@ class Select(Generic[SelectType], Vertical, can_focus=True): border: tall $accent; } - Select { - height: auto; - } - Select > SelectOverlay { width: 1fr; display: none; From f12aeb00d2b9e1835ccaf9b7e194818db25438a0 Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Tue, 16 May 2023 13:33:57 +0100 Subject: [PATCH 26/30] Remove forced content tracking refresh in clear_options While the fix for #2557 likely isn't *the* fix (see #2582 for some context around that), it is a fix that works for now. As such, with the change, there was a double attempt to refresh the content tracking in the clearing of options in the OptionList, which shouldn't be necessary. This removes that. --- src/textual/widgets/_option_list.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/textual/widgets/_option_list.py b/src/textual/widgets/_option_list.py index 7d9af219b..5b4a39b48 100644 --- a/src/textual/widgets/_option_list.py +++ b/src/textual/widgets/_option_list.py @@ -627,7 +627,6 @@ class OptionList(ScrollView, can_focus=True): """ self._contents.clear() self._options.clear() - self._refresh_content_tracking(force=True) self.highlighted = None self._mouse_hovering_over = None self.virtual_size = Size(self.scrollable_content_region.width, 0) From 32fa259c94261dab70337314c2d55f406e919973 Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Tue, 16 May 2023 13:38:08 +0100 Subject: [PATCH 27/30] Add a TODO comment to the effect that this is a temp fix --- src/textual/widgets/_option_list.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/textual/widgets/_option_list.py b/src/textual/widgets/_option_list.py index 5b4a39b48..dfe530543 100644 --- a/src/textual/widgets/_option_list.py +++ b/src/textual/widgets/_option_list.py @@ -630,6 +630,13 @@ class OptionList(ScrollView, can_focus=True): self.highlighted = None self._mouse_hovering_over = None self.virtual_size = Size(self.scrollable_content_region.width, 0) + # TODO: See https://github.com/Textualize/textual/issues/2582 -- it + # should not be necessary to do this like this here; ideally here in + # clear_options it would be a forced refresh, and also in a + # `on_show` it would be the same (which, I think, would actually + # solve the problem we're seeing). But, until such a time as we get + # to the bottom of 2582... this seems to delay the refresh enough + # that things fall into place. self._request_content_tracking_refresh() return self From faa67a82934e2de26471357cfd40fc8ee9b61fa8 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Tue, 16 May 2023 13:44:06 +0100 Subject: [PATCH 28/30] Screen docs (#2579) * screen docs * docstrings * modal example * docstring * docstrings * Apply suggestions from code review Co-authored-by: Dave Pearson --------- Co-authored-by: Dave Pearson --- docs/examples/guide/screens/modal02.py | 1 + docs/examples/guide/screens/modal03.py | 57 ++++++++++++++++++++++++++ docs/guide/screens.md | 37 +++++++++++++++++ src/textual/app.py | 2 +- src/textual/screen.py | 9 ++-- 5 files changed, 99 insertions(+), 7 deletions(-) create mode 100644 docs/examples/guide/screens/modal03.py diff --git a/docs/examples/guide/screens/modal02.py b/docs/examples/guide/screens/modal02.py index bb6c49c59..418030f80 100644 --- a/docs/examples/guide/screens/modal02.py +++ b/docs/examples/guide/screens/modal02.py @@ -42,6 +42,7 @@ class ModalApp(App): yield Footer() def action_request_quit(self) -> None: + """Action to display the quit dialog.""" self.push_screen(QuitScreen()) diff --git a/docs/examples/guide/screens/modal03.py b/docs/examples/guide/screens/modal03.py new file mode 100644 index 000000000..e19fc527b --- /dev/null +++ b/docs/examples/guide/screens/modal03.py @@ -0,0 +1,57 @@ +from textual.app import App, ComposeResult +from textual.containers import Grid +from textual.screen import ModalScreen +from textual.widgets import Button, Footer, Header, Label + +TEXT = """I must not fear. +Fear is the mind-killer. +Fear is the little-death that brings total obliteration. +I will face my fear. +I will permit it to pass over me and through me. +And when it has gone past, I will turn the inner eye to see its path. +Where the fear has gone there will be nothing. Only I will remain.""" + + +class QuitScreen(ModalScreen[bool]): # (1)! + """Screen with a dialog to quit.""" + + def compose(self) -> ComposeResult: + yield Grid( + Label("Are you sure you want to quit?", id="question"), + Button("Quit", variant="error", id="quit"), + Button("Cancel", variant="primary", id="cancel"), + id="dialog", + ) + + def on_button_pressed(self, event: Button.Pressed) -> None: + if event.button.id == "quit": + self.dismiss(True) + else: + self.dismiss(False) + + +class ModalApp(App): + """An app with a modal dialog.""" + + CSS_PATH = "modal01.css" + BINDINGS = [("q", "request_quit", "Quit")] + + def compose(self) -> ComposeResult: + yield Header() + yield Label(TEXT * 8) + yield Footer() + + def action_request_quit(self) -> None: + """Action to display the quit dialog.""" + + def check_quit(quit: bool) -> None: + """Called when QuitScreen is dismissed.""" + if quit: + self.exit() + + self.push_screen(QuitScreen(), check_quit) + + +if __name__ == "__main__": + app = ModalApp() + app.run() diff --git a/docs/guide/screens.md b/docs/guide/screens.md index fe8e3616f..0119ef655 100644 --- a/docs/guide/screens.md +++ b/docs/guide/screens.md @@ -219,3 +219,40 @@ Let's see what happens when we use `ModalScreen`. Now when we press ++q++, the dialog is displayed over the main screen. The main screen is darkened to indicate to the user that it is not active, and only the dialog will respond to input. + +## Returning data from screens + +It is a common requirement for screens to be able to return data. +For instance, you may want a screen to show a dialog and have the result of that dialog processed *after* the screen has been popped. + +To return data from a screen, call [`dismiss()`][textual.screen.dismiss] on the screen with the data you wish to return. +This will pop the screen and invoke a callback set when the screen was pushed (with [`push_screen`][textual.app.push_screen]). + +Let's modify the previous example to use `dismiss` rather than an explicit `pop_screen`. + +=== "modal03.py" + + ```python title="modal03.py" hl_lines="15 27-30 47-50 52" + --8<-- "docs/examples/guide/screens/modal03.py" + ``` + + 1. See below for an explanation of the `[bool]` + +=== "modal01.css" + + ```sass title="modal01.css" + --8<-- "docs/examples/guide/screens/modal01.css" + ``` + +In the `on_button_pressed` message handler we call `dismiss` with a boolean that indicates if the user has chosen to quit the app. +This boolean is passed to the `check_quit` function we provided when `QuitScreen` was pushed. + +Although this example behaves the same as the previous code, it is more flexible because it has removed responsibility for exiting from the modal screen to the caller. +This makes it easier for the app to perform any cleanup actions prior to exiting, for example. + +Returning data in this way can help keep your code manageable by making it easy to re-use your `Screen` classes in other contexts. + +### Typing screen results + +You may have noticed in the previous example that we changed the base class to `ModalScreen[bool]`. +The addition of `[bool]` adds typing information that tells the type checker to expect a boolean in the call to `dismiss`, and that any callback set in `push_screen` should also expect the same type. As always, typing is optional in Textual, but this may help you catch bugs. diff --git a/src/textual/app.py b/src/textual/app.py index c1376cd22..841d854e2 100644 --- a/src/textual/app.py +++ b/src/textual/app.py @@ -1416,7 +1416,7 @@ class App(Generic[ReturnType], DOMNode): Args: screen: A Screen instance or the name of an installed screen. - callback: An optional callback function that is called if the screen is dismissed with a result. + callback: An optional callback function that will be called if the screen is [dismissed][textual.screen.Screen.dismiss] with a result. Returns: An optional awaitable that awaits the mounting of the screen and its children. diff --git a/src/textual/screen.py b/src/textual/screen.py index 09505cc53..535b621ea 100644 --- a/src/textual/screen.py +++ b/src/textual/screen.py @@ -768,6 +768,9 @@ class Screen(Generic[ScreenResultType], Widget): def dismiss(self, result: ScreenResultType | Type[_NoResult] = _NoResult) -> None: """Dismiss the screen, optionally with a result. + If `result` is provided and a callback was set when the screen was [pushed][textual.app.push_screen], then + the callback will be invoked with `result`. + Args: result: The optional result to be passed to the result callback. @@ -775,12 +778,6 @@ class Screen(Generic[ScreenResultType], Widget): ScreenStackError: If trying to dismiss a screen that is not at the top of the stack. - Note: - If the screen was pushed with a callback, the callback will be - called with the given result and then a call to - [`App.pop_screen`][textual.app.App.pop_screen] is performed. If - no callback was provided calling this method is the same as - simply calling [`App.pop_screen`][textual.app.App.pop_screen]. """ if self is not self.app.screen: from .app import ScreenStackError From 3d2e3d909277c02753c9fbb597c3395589e9e97c Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Tue, 16 May 2023 14:34:18 +0100 Subject: [PATCH 29/30] Add a snapshot test for a rebuilt Select This helps test the practical impact of the fix added for #2557. --- .../snapshot_apps/select_rebuild.py | 21 +++++++++++++++++++ tests/snapshot_tests/test_snapshots.py | 9 ++++++++ 2 files changed, 30 insertions(+) create mode 100644 tests/snapshot_tests/snapshot_apps/select_rebuild.py diff --git a/tests/snapshot_tests/snapshot_apps/select_rebuild.py b/tests/snapshot_tests/snapshot_apps/select_rebuild.py new file mode 100644 index 000000000..190db3b4b --- /dev/null +++ b/tests/snapshot_tests/snapshot_apps/select_rebuild.py @@ -0,0 +1,21 @@ +"""Test https://github.com/Textualize/textual/issues/2557""" + +from textual.app import App, ComposeResult +from textual.widgets import Select, Button + + +class SelectRebuildApp(App[None]): + + def compose(self) -> ComposeResult: + yield Select[int]((("1", 1), ("2", 2))) + yield Button("Rebuild") + + def on_button_pressed(self): + self.query_one(Select).set_options(( + ("This", 0), ("Should", 1), ("Be", 2), + ("What", 3), ("Goes", 4), ("Into",5), + ("The", 6), ("Snapshit", 7) + )) + +if __name__ == "__main__": + SelectRebuildApp().run() diff --git a/tests/snapshot_tests/test_snapshots.py b/tests/snapshot_tests/test_snapshots.py index ea5e32153..9bb589f7a 100644 --- a/tests/snapshot_tests/test_snapshots.py +++ b/tests/snapshot_tests/test_snapshots.py @@ -493,3 +493,12 @@ def test_quickly_change_tabs(snap_compare): def test_fr_unit_with_min(snap_compare): # https://github.com/Textualize/textual/issues/2378 assert snap_compare(SNAPSHOT_APPS_DIR / "fr_with_min.py") + + +def test_select_rebuild(snap_compare): + # https://github.com/Textualize/textual/issues/2557 + assert snap_compare( + SNAPSHOT_APPS_DIR / "select_rebuild.py", + press=["tab", "space", "escape", "tab", "enter", "tab", "space"] + ) + From 1ebfe2f418ab0cc58255114345eb912ca345f4be Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Tue, 16 May 2023 14:38:11 +0100 Subject: [PATCH 30/30] Update the snapshits --- .../__snapshots__/test_snapshots.ambr | 163 ++++++++++++++++++ 1 file changed, 163 insertions(+) diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots.ambr b/tests/snapshot_tests/__snapshots__/test_snapshots.ambr index 8b03449d9..018558247 100644 --- a/tests/snapshot_tests/__snapshots__/test_snapshots.ambr +++ b/tests/snapshot_tests/__snapshots__/test_snapshots.ambr @@ -22706,6 +22706,169 @@ ''' # --- +# name: test_select_rebuild + ''' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SelectRebuildApp + + + + + + + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + Select + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + Select + This + Should + Be + What + Goes + Into + The + Snapshit + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + + + + + + + + + + + + ''' +# --- # name: test_switches '''