mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
game tidy, snapshots
This commit is contained in:
@@ -11,16 +11,16 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
### Added
|
||||
|
||||
- Added Styles.has_any_rules https://github.com/Textualize/textual/pull/5264
|
||||
- Added `position` CSS rule.
|
||||
- Added `Widget.set_scroll`
|
||||
- Added `Select.selection`
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed offset applied to docked widgets https://github.com/Textualize/textual/pull/5264
|
||||
- Fixed loading widgets responding to input https://github.com/Textualize/textual/pull/5267
|
||||
|
||||
### Changed
|
||||
|
||||
- Added `position` CSS rule.
|
||||
- Added `Widget.set_scroll`
|
||||
|
||||
## [0.86.3] - 2024-11-19
|
||||
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
"""
|
||||
An implementation of the "Sliding Tile" puzzle.
|
||||
|
||||
Textual isn't a game engine exactly, but it wasn't hard to build this.
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from asyncio import sleep
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
from itertools import product
|
||||
from random import choice
|
||||
@@ -10,6 +18,7 @@ from rich.console import ConsoleRenderable
|
||||
from rich.syntax import Syntax
|
||||
|
||||
from textual import containers, events, on, work
|
||||
from textual._loop import loop_last
|
||||
from textual.app import ComposeResult
|
||||
from textual.binding import Binding
|
||||
from textual.demo.page import PageScreen
|
||||
@@ -22,6 +31,8 @@ from textual.widgets import Button, Digits, Footer, Select, Static
|
||||
|
||||
@dataclass
|
||||
class NewGame:
|
||||
"""A dataclass to report the desired game type."""
|
||||
|
||||
language: str
|
||||
code: str
|
||||
size: tuple[int, int]
|
||||
@@ -160,29 +171,25 @@ LEVELS = {"Python": PYTHON_CODE, "XML": XML_CODE, "BF": BF_CODE}
|
||||
|
||||
|
||||
class Tile(containers.Vertical):
|
||||
"""An individual tile in the puzzle.
|
||||
|
||||
A Tile is a container with a static inside it.
|
||||
The static contains the code (as a Rich Syntax object), scrolled so the
|
||||
relevant portion is visible.
|
||||
"""
|
||||
|
||||
DEFAULT_CSS = """
|
||||
Tile {
|
||||
width: 16;
|
||||
height: 8;
|
||||
position: absolute;
|
||||
|
||||
transition: opacity 0.1s;
|
||||
position: absolute;
|
||||
Static {
|
||||
width: auto;
|
||||
height: auto;
|
||||
|
||||
&:hover {
|
||||
|
||||
tint: $primary 30%;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&#blank {
|
||||
visibility: hidden;
|
||||
}
|
||||
&:hover { tint: $primary 30%; }
|
||||
}
|
||||
&#blank { visibility: hidden; }
|
||||
}
|
||||
|
||||
"""
|
||||
|
||||
position: reactive[Offset] = reactive(Offset)
|
||||
@@ -208,6 +215,7 @@ class Tile(containers.Vertical):
|
||||
classes="tile",
|
||||
name="blank" if self.tile is None else str(self.tile),
|
||||
)
|
||||
assert self.parent is not None
|
||||
static.styles.width = self.parent.styles.width
|
||||
static.styles.height = self.parent.styles.height
|
||||
yield static
|
||||
@@ -219,14 +227,17 @@ class Tile(containers.Vertical):
|
||||
self.styles.height = height
|
||||
column, row = self.position
|
||||
self.set_scroll(column * width, row * height)
|
||||
|
||||
self.offset = self.position * self.tile_size
|
||||
|
||||
def watch_position(self, position: Offset) -> None:
|
||||
"""The 'position' is in tile coordinate.
|
||||
When it changes we animate it to the cell coordinates."""
|
||||
self.animate("offset", position * self.tile_size, duration=0.2)
|
||||
|
||||
|
||||
class GameDialog(containers.VerticalGroup):
|
||||
"""A dialog to ask the user for the initial game parameters."""
|
||||
|
||||
DEFAULT_CSS = """
|
||||
GameDialog {
|
||||
background: $boost;
|
||||
@@ -264,25 +275,23 @@ class GameDialog(containers.VerticalGroup):
|
||||
id="level",
|
||||
allow_blank=False,
|
||||
)
|
||||
|
||||
yield Button("Start", variant="primary")
|
||||
|
||||
@on(Button.Pressed)
|
||||
def on_button_pressed(self) -> None:
|
||||
language = self.query_one("#language", Select).value
|
||||
|
||||
level = self.query_one("#level", Select).value
|
||||
language = self.query_one("#language", Select).selection
|
||||
level = self.query_one("#level", Select).selection
|
||||
assert language is not None and level is not None
|
||||
self.screen.dismiss(NewGame(language, LEVELS[language], level))
|
||||
|
||||
|
||||
class GameDialogScreen(ModalScreen):
|
||||
CSS = """
|
||||
GameDialogScreen {
|
||||
|
||||
align: center middle;
|
||||
|
||||
}
|
||||
"""Modal screen containing the dialog."""
|
||||
|
||||
CSS = """
|
||||
GameDialogScreen {
|
||||
align: center middle;
|
||||
}
|
||||
"""
|
||||
|
||||
BINDINGS = [("escape", "dismiss")]
|
||||
@@ -292,6 +301,8 @@ class GameDialogScreen(ModalScreen):
|
||||
|
||||
|
||||
class Game(containers.Vertical, can_focus=True):
|
||||
"""Widget for the game board."""
|
||||
|
||||
ALLOW_MAXIMIZE = False
|
||||
DEFAULT_CSS = """
|
||||
Game {
|
||||
@@ -312,7 +323,6 @@ class Game(containers.Vertical, can_focus=True):
|
||||
color: $foreground;
|
||||
}
|
||||
}
|
||||
|
||||
"""
|
||||
|
||||
BINDINGS = [
|
||||
@@ -323,7 +333,7 @@ class Game(containers.Vertical, can_focus=True):
|
||||
]
|
||||
|
||||
state = reactive("waiting")
|
||||
play_start_time = reactive(monotonic)
|
||||
play_start_time: reactive[float] = reactive(monotonic)
|
||||
play_time = reactive(0.0, init=False)
|
||||
code = reactive("")
|
||||
dimensions = reactive(Size(3, 3))
|
||||
@@ -339,32 +349,21 @@ class Game(containers.Vertical, can_focus=True):
|
||||
) -> None:
|
||||
self.set_reactive(Game.code, code)
|
||||
self.set_reactive(Game.language, language)
|
||||
self.set_reactive(Game.dimensions, Size(*dimensions))
|
||||
self.tile_size = Size(*tile_size)
|
||||
|
||||
tile_width, tile_height = dimensions
|
||||
tile_no = -1
|
||||
self.locations: list[list[int | None]] = [
|
||||
[(tile_no := tile_no + 1) for _ in range(tile_width)]
|
||||
for _ in range(tile_height)
|
||||
]
|
||||
self.locations[tile_height - 1][tile_width - 1] = None
|
||||
|
||||
self.locations: defaultdict[Offset, int | None] = defaultdict(None)
|
||||
super().__init__()
|
||||
|
||||
self.dimensions = Size(*dimensions)
|
||||
self.tile_size = Size(*tile_size)
|
||||
self.play_timer: Timer | None = None
|
||||
|
||||
def check_win(self) -> bool:
|
||||
return all(tile.start_position == tile.position for tile in self.query(Tile))
|
||||
|
||||
def watch_dimensions(self, dimensions: Size) -> None:
|
||||
self.locations.clear()
|
||||
tile_width, tile_height = dimensions
|
||||
tile_no = 0
|
||||
self.locations: list[list[int | None]] = [
|
||||
[(tile_no := tile_no + 1) for _ in range(tile_width)]
|
||||
for _ in range(tile_height)
|
||||
]
|
||||
self.locations[tile_height - 1][tile_width - 1] = None
|
||||
for last, tile_no in loop_last(range(0, tile_width * tile_height)):
|
||||
position = Offset(*divmod(tile_no, tile_width))
|
||||
self.locations[position] = None if last else tile_no
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
syntax = Syntax(
|
||||
@@ -381,13 +380,9 @@ class Game(containers.Vertical, can_focus=True):
|
||||
grid.styles.width = tile_width * self.tile_size[0]
|
||||
grid.styles.height = tile_height * self.tile_size[1]
|
||||
for row, column in product(range(tile_width), range(tile_height)):
|
||||
tile_no = self.locations[column][row]
|
||||
yield Tile(
|
||||
syntax,
|
||||
tile_no,
|
||||
self.tile_size,
|
||||
Offset(row, column),
|
||||
)
|
||||
position = Offset(row, column)
|
||||
tile_no = self.locations[position]
|
||||
yield Tile(syntax, tile_no, self.tile_size, position)
|
||||
if self.language:
|
||||
self.call_later(self.shuffle)
|
||||
|
||||
@@ -409,31 +404,36 @@ class Game(containers.Vertical, can_focus=True):
|
||||
self.play_start_time = monotonic()
|
||||
self.play_timer = self.set_interval(1 / 10, self.update_clock)
|
||||
|
||||
def get_tile_location(self, tile: int | None) -> Offset:
|
||||
return self.get_tile(tile).position
|
||||
|
||||
def get_tile(self, tile: int | None) -> Tile:
|
||||
"""Get a tile (int) or the blank (None)."""
|
||||
return self.query_one("#blank" if tile is None else f"#tile{tile}", Tile)
|
||||
|
||||
def get_tile_at(self, position: Offset) -> Tile:
|
||||
if position.x < 0 or position.y < 0:
|
||||
"""Get a tile at the given position, or raise an IndexError."""
|
||||
if position not in self.locations:
|
||||
raise IndexError("No tile")
|
||||
tile_no = self.locations[position.y][position.x]
|
||||
return self.get_tile(tile_no)
|
||||
return self.get_tile(self.locations[position])
|
||||
|
||||
def move_tile(self, tile_no: int | None, position: Offset) -> None:
|
||||
def move_tile(self, tile_no: int | None) -> None:
|
||||
"""Move a tile to the blank.
|
||||
Note: this doesn't do any validation of legal moves.
|
||||
"""
|
||||
tile = self.get_tile(tile_no)
|
||||
blank = self.get_tile(None)
|
||||
self.locations[tile.position.y][tile.position.x] = None
|
||||
blank_position = blank.position
|
||||
|
||||
self.locations[tile.position] = None
|
||||
blank.position = tile.position
|
||||
self.locations[position.y][position.x] = tile_no
|
||||
tile.position = position
|
||||
|
||||
self.locations[blank_position] = tile_no
|
||||
tile.position = blank_position
|
||||
|
||||
if self.check_win():
|
||||
self.state = "won"
|
||||
self.notify("You won!")
|
||||
|
||||
def can_move(self, tile: int) -> bool:
|
||||
"""Check if a tile may move."""
|
||||
blank_position = self.get_tile(None).position
|
||||
tile_position = self.get_tile(tile).position
|
||||
return blank_position in (
|
||||
@@ -460,11 +460,11 @@ class Game(containers.Vertical, can_focus=True):
|
||||
tile = self.get_tile_at(position)
|
||||
except IndexError:
|
||||
return
|
||||
|
||||
self.move_tile(tile.tile, blank)
|
||||
self.move_tile(tile.tile)
|
||||
|
||||
@work(exclusive=True)
|
||||
async def shuffle(self, shuffles: int = 150) -> None:
|
||||
"""A worker to do the shuffling."""
|
||||
self.visible = True
|
||||
if self.play_timer is not None:
|
||||
self.play_timer.stop()
|
||||
@@ -483,10 +483,10 @@ class Game(containers.Vertical, can_focus=True):
|
||||
moves.append(blank + (0, -1))
|
||||
if blank.y < tile_height - 1:
|
||||
moves.append(blank + (0, +1))
|
||||
# Avoid moving back to the previous position
|
||||
move_position = choice([move for move in moves if move != previous_move])
|
||||
|
||||
move_tile = self.get_tile_at(move_position)
|
||||
self.move_tile(move_tile.tile, blank)
|
||||
self.move_tile(move_tile.tile)
|
||||
await sleep(0.05)
|
||||
previous_move = blank
|
||||
self.query_one("#grid").border_title = ""
|
||||
@@ -494,15 +494,17 @@ class Game(containers.Vertical, can_focus=True):
|
||||
|
||||
@on(events.Click, ".tile")
|
||||
def on_tile_clicked(self, event: events.Click) -> None:
|
||||
assert event.widget is not None
|
||||
tile = int(event.widget.name or 0)
|
||||
if self.state != "playing" or not self.can_move(tile):
|
||||
self.app.bell()
|
||||
return
|
||||
blank = self.get_tile_location(None)
|
||||
self.move_tile(tile, blank)
|
||||
self.move_tile(tile)
|
||||
|
||||
|
||||
class GameScreen(PageScreen):
|
||||
"""The screen containing the game."""
|
||||
|
||||
AUTO_FOCUS = "Game"
|
||||
BINDINGS = [
|
||||
("s", "shuffle", "Shuffle"),
|
||||
|
||||
@@ -101,6 +101,7 @@ class HorizontalLayout(Layout):
|
||||
(next_x - x.__floor__()).__floor__(),
|
||||
content_height.__floor__(),
|
||||
)
|
||||
absolute = styles.has_rule("position") and styles.position == "absolute"
|
||||
add_placement(
|
||||
_WidgetPlacement(
|
||||
region,
|
||||
@@ -110,10 +111,10 @@ class HorizontalLayout(Layout):
|
||||
0,
|
||||
False,
|
||||
overlay,
|
||||
styles.has_rule("position") and styles.position == "absolute",
|
||||
absolute,
|
||||
)
|
||||
)
|
||||
if not overlay:
|
||||
if not overlay and not absolute:
|
||||
x = next_x + margin
|
||||
|
||||
return placements
|
||||
|
||||
@@ -105,6 +105,7 @@ class VerticalLayout(Layout):
|
||||
next_y.__floor__() - y.__floor__(),
|
||||
)
|
||||
|
||||
absolute = styles.has_rule("position") and styles.position == "absolute"
|
||||
add_placement(
|
||||
_WidgetPlacement(
|
||||
region,
|
||||
@@ -114,10 +115,10 @@ class VerticalLayout(Layout):
|
||||
0,
|
||||
False,
|
||||
overlay,
|
||||
styles.has_rule("position") and styles.position == "absolute",
|
||||
absolute,
|
||||
)
|
||||
)
|
||||
if not overlay:
|
||||
if not overlay and not absolute:
|
||||
y = next_y + margin
|
||||
|
||||
return placements
|
||||
|
||||
@@ -378,6 +378,18 @@ class Select(Generic[SelectType], Vertical, can_focus=True):
|
||||
disabled=disabled,
|
||||
)
|
||||
|
||||
@property
|
||||
def selection(self) -> SelectType | None:
|
||||
"""The currently selected item.
|
||||
|
||||
Unlike [value][textual.widgets.Select.value], this will not return Blanks.
|
||||
If nothing is selected, this will return `None`.
|
||||
|
||||
"""
|
||||
value = self.value
|
||||
assert not isinstance(value, NoSelection)
|
||||
return value
|
||||
|
||||
def _setup_variables_for_options(
|
||||
self,
|
||||
options: Iterable[tuple[RenderableType, SelectType]],
|
||||
|
||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 16 KiB |
@@ -0,0 +1,150 @@
|
||||
<svg class="rich-terminal" viewBox="0 0 994 635.5999999999999" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- Generated with Rich https://www.textualize.io -->
|
||||
<style>
|
||||
|
||||
@font-face {
|
||||
font-family: "Fira Code";
|
||||
src: local("FiraCode-Regular"),
|
||||
url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff2/FiraCode-Regular.woff2") format("woff2"),
|
||||
url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff/FiraCode-Regular.woff") format("woff");
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Fira Code";
|
||||
src: local("FiraCode-Bold"),
|
||||
url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff2/FiraCode-Bold.woff2") format("woff2"),
|
||||
url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff/FiraCode-Bold.woff") format("woff");
|
||||
font-style: bold;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.terminal-1334573531-matrix {
|
||||
font-family: Fira Code, monospace;
|
||||
font-size: 20px;
|
||||
line-height: 24.4px;
|
||||
font-variant-east-asian: full-width;
|
||||
}
|
||||
|
||||
.terminal-1334573531-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
font-family: arial;
|
||||
}
|
||||
|
||||
.terminal-1334573531-r1 { fill: #e0e0e0 }
|
||||
.terminal-1334573531-r2 { fill: #c5c8c6 }
|
||||
</style>
|
||||
|
||||
<defs>
|
||||
<clipPath id="terminal-1334573531-clip-terminal">
|
||||
<rect x="0" y="0" width="975.0" height="584.5999999999999" />
|
||||
</clipPath>
|
||||
<clipPath id="terminal-1334573531-line-0">
|
||||
<rect x="0" y="1.5" width="976" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-1334573531-line-1">
|
||||
<rect x="0" y="25.9" width="976" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-1334573531-line-2">
|
||||
<rect x="0" y="50.3" width="976" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-1334573531-line-3">
|
||||
<rect x="0" y="74.7" width="976" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-1334573531-line-4">
|
||||
<rect x="0" y="99.1" width="976" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-1334573531-line-5">
|
||||
<rect x="0" y="123.5" width="976" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-1334573531-line-6">
|
||||
<rect x="0" y="147.9" width="976" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-1334573531-line-7">
|
||||
<rect x="0" y="172.3" width="976" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-1334573531-line-8">
|
||||
<rect x="0" y="196.7" width="976" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-1334573531-line-9">
|
||||
<rect x="0" y="221.1" width="976" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-1334573531-line-10">
|
||||
<rect x="0" y="245.5" width="976" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-1334573531-line-11">
|
||||
<rect x="0" y="269.9" width="976" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-1334573531-line-12">
|
||||
<rect x="0" y="294.3" width="976" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-1334573531-line-13">
|
||||
<rect x="0" y="318.7" width="976" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-1334573531-line-14">
|
||||
<rect x="0" y="343.1" width="976" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-1334573531-line-15">
|
||||
<rect x="0" y="367.5" width="976" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-1334573531-line-16">
|
||||
<rect x="0" y="391.9" width="976" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-1334573531-line-17">
|
||||
<rect x="0" y="416.3" width="976" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-1334573531-line-18">
|
||||
<rect x="0" y="440.7" width="976" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-1334573531-line-19">
|
||||
<rect x="0" y="465.1" width="976" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-1334573531-line-20">
|
||||
<rect x="0" y="489.5" width="976" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-1334573531-line-21">
|
||||
<rect x="0" y="513.9" width="976" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-1334573531-line-22">
|
||||
<rect x="0" y="538.3" width="976" height="24.65"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
|
||||
<rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" x="1" y="1" width="992" height="633.6" rx="8"/><text class="terminal-1334573531-title" fill="#c5c8c6" text-anchor="middle" x="496" y="27">AbsoluteApp</text>
|
||||
<g transform="translate(26,22)">
|
||||
<circle cx="0" cy="0" r="7" fill="#ff5f57"/>
|
||||
<circle cx="22" cy="0" r="7" fill="#febc2e"/>
|
||||
<circle cx="44" cy="0" r="7" fill="#28c840"/>
|
||||
</g>
|
||||
|
||||
<g transform="translate(9, 41)" clip-path="url(#terminal-1334573531-clip-terminal)">
|
||||
<rect fill="#121212" x="0" y="1.5" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="25.9" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="50.3" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="74.7" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="99.1" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="123.5" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="147.9" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="172.3" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="196.7" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="221.1" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="245.5" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="269.9" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="294.3" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="318.7" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="343.1" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="367.5" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="391.9" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="416.3" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="440.7" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="465.1" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="489.5" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="513.9" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="538.3" width="976" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="562.7" width="976" height="24.65" shape-rendering="crispEdges"/>
|
||||
<g class="terminal-1334573531-matrix">
|
||||
<text class="terminal-1334573531-r2" x="976" y="20" textLength="12.2" clip-path="url(#terminal-1334573531-line-0)">
|
||||
</text><text class="terminal-1334573531-r1" x="0" y="44.4" textLength="976" clip-path="url(#terminal-1334573531-line-1)"> Absolute 1                                                                     </text><text class="terminal-1334573531-r2" x="976" y="44.4" textLength="12.2" clip-path="url(#terminal-1334573531-line-1)">
|
||||
</text><text class="terminal-1334573531-r1" x="0" y="68.8" textLength="976" clip-path="url(#terminal-1334573531-line-2)">  Absolute 2                                                                    </text><text class="terminal-1334573531-r2" x="976" y="68.8" textLength="12.2" clip-path="url(#terminal-1334573531-line-2)">
|
||||
</text><text class="terminal-1334573531-r1" x="0" y="93.2" textLength="976" clip-path="url(#terminal-1334573531-line-3)">   Absolute 3                                                                   </text><text class="terminal-1334573531-r2" x="976" y="93.2" textLength="12.2" clip-path="url(#terminal-1334573531-line-3)">
|
||||
</text><text class="terminal-1334573531-r2" x="976" y="117.6" textLength="12.2" clip-path="url(#terminal-1334573531-line-4)">
|
||||
</text><text class="terminal-1334573531-r2" x="976" y="142" textLength="12.2" clip-path="url(#terminal-1334573531-line-5)">
|
||||
</text><text class="terminal-1334573531-r2" x="976" y="166.4" textLength="12.2" clip-path="url(#terminal-1334573531-line-6)">
|
||||
</text><text class="terminal-1334573531-r2" x="976" y="190.8" textLength="12.2" clip-path="url(#terminal-1334573531-line-7)">
|
||||
</text><text class="terminal-1334573531-r2" x="976" y="215.2" textLength="12.2" clip-path="url(#terminal-1334573531-line-8)">
|
||||
</text><text class="terminal-1334573531-r2" x="976" y="239.6" textLength="12.2" clip-path="url(#terminal-1334573531-line-9)">
|
||||
</text><text class="terminal-1334573531-r2" x="976" y="264" textLength="12.2" clip-path="url(#terminal-1334573531-line-10)">
|
||||
</text><text class="terminal-1334573531-r1" x="0" y="288.4" textLength="976" clip-path="url(#terminal-1334573531-line-11)">                                    Relative 1                                  </text><text class="terminal-1334573531-r2" x="976" y="288.4" textLength="12.2" clip-path="url(#terminal-1334573531-line-11)">
|
||||
</text><text class="terminal-1334573531-r2" x="976" y="312.8" textLength="12.2" clip-path="url(#terminal-1334573531-line-12)">
|
||||
</text><text class="terminal-1334573531-r1" x="0" y="337.2" textLength="976" clip-path="url(#terminal-1334573531-line-13)">                                     Relative 2                                 </text><text class="terminal-1334573531-r2" x="976" y="337.2" textLength="12.2" clip-path="url(#terminal-1334573531-line-13)">
|
||||
</text><text class="terminal-1334573531-r2" x="976" y="361.6" textLength="12.2" clip-path="url(#terminal-1334573531-line-14)">
|
||||
</text><text class="terminal-1334573531-r1" x="0" y="386" textLength="976" clip-path="url(#terminal-1334573531-line-15)">                                      Relative 3                                </text><text class="terminal-1334573531-r2" x="976" y="386" textLength="12.2" clip-path="url(#terminal-1334573531-line-15)">
|
||||
</text><text class="terminal-1334573531-r2" x="976" y="410.4" textLength="12.2" clip-path="url(#terminal-1334573531-line-16)">
|
||||
</text><text class="terminal-1334573531-r2" x="976" y="434.8" textLength="12.2" clip-path="url(#terminal-1334573531-line-17)">
|
||||
</text><text class="terminal-1334573531-r2" x="976" y="459.2" textLength="12.2" clip-path="url(#terminal-1334573531-line-18)">
|
||||
</text><text class="terminal-1334573531-r2" x="976" y="483.6" textLength="12.2" clip-path="url(#terminal-1334573531-line-19)">
|
||||
</text><text class="terminal-1334573531-r2" x="976" y="508" textLength="12.2" clip-path="url(#terminal-1334573531-line-20)">
|
||||
</text><text class="terminal-1334573531-r2" x="976" y="532.4" textLength="12.2" clip-path="url(#terminal-1334573531-line-21)">
|
||||
</text><text class="terminal-1334573531-r2" x="976" y="556.8" textLength="12.2" clip-path="url(#terminal-1334573531-line-22)">
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 13 KiB |
Reference in New Issue
Block a user