EN /HU | Belépés

Rajzolás és témák

Hogyan kerül szín és karakter a képernyőre a rubbs-ban: a BBS::Theme (minden preset és minden szerepkulcs), a BBS::Ansi paletta, a BBS::FrameBuffer, amibe az Application rajzol, és amit összehasonlít, a BBS::Renderer a flow-k ERB screenjeihez, a BBS::Banner, és a BBS::Markdown, ami Markdownból ANSI-sorokat készít. Az ezekre épülő widgetek a Widgetek oldalon vannak. Minden a rubbs 1.0.0 forrásával egyeztetve.

Témák

Hogyan működik egy téma

A BBS::Theme (lib/bbs/theme.rb) egy név és egy Hash, ami szerepkulcsokat képez le ANSI SGR-stringekre. A widgetek sosem égetnek be színt: egy szerepet kérnek (:button_focused, :window_title, :listbox_selected…), és azzal az SGR-rel rajzolnak, amit a téma ad. A téma cseréje az egész applicationt átszínezi.

RUBY
BBS::Theme.new(name, styles)
Tag Mit csinál
Theme.new(name, styles) A name bármilyen érték (a presetek Symbolt használnak), a styles egy Hash szerepkulcsból SGR-Stringbe. A Theme::ROLES-on kívüli kulcsra ArgumentError-t dob.
Theme.fetch(name) Az ilyen nevű (Symbol vagy String) preset a Theme::PRESETS-ből; egy BBS::Theme-et változatlanul visszaad. Ismeretlen névre ArgumentError.
Theme::ROLES Az összes szerepkulcs (a synchronet preset kulcsai); a Theme.roles ugyanezt adja.
name A név.
styles A Hash, fagyasztva.
style(key) / [key] Egy szerep SGR-je, vagy nil, ha a témában nincs ilyen.
merge(overrides) Új téma ugyanazzal a névvel, a megadott kulcsokat lecserélve vagy hozzáadva. Az eredetit nem bántja; a Theme::ROLES-on kívüli kulcsra ArgumentError-t dob.

A téma fagyasztott: a merge-dzsel változtatsz rajta, ami újat ad. Ha a Hashből kimarad egy szerep, az nem hiba: a cella nil stílust kap, a FrameBuffer#diff pedig egy sima \e[0m reset után írja ki, így a terminál alapszíneivel jelenik meg.

RUBY
require 'bbs'

t = BBS::Theme.synchronet
p t[:accent]                          # => "\e[1;33;40m"
p t.style(:no_such_role)              # => nil
cyan = t.merge(accent: "\e[1;36;40m")
p [cyan.name, cyan[:accent]]          # => [:synchronet, "\e[1;36;40m"]
p t.frozen?                           # => true
t.merge(check_on: "\e[1;32m")         # ArgumentError: unknown theme roles: [:check_on]

Presetek

A presetek adatok: mindegyik egy Hash a BBS::Theme::PRESETS-ben (lib/bbs/theme/presets.rb), és a BBS::Theme.fetch(:név) épít belőlük témát; az alábbi öt osztálymetódus ennek rövidítése. Ha nincs megadva téma, az application a synchronet-et használja (vagy a BBS.config.theme-et, ha az be van állítva).

Preset Kinézet
BBS::Theme.synchronet Fekete képernyő, szürke szöveg. Kék window-k élénkfehér kerettel és sárga címmel; az aktív window címe fekete világosszürkén. A menüsor és a státuszsor fekete világosszürkén, piros hotkeyekkel, a kijelölt menü fehér zölden. A gombok feketék szürkén, fókuszban fehérek zölden; a beviteli mezők fehérek kéken, fókuszban feketék szürkén.
BBS::Theme.nortoncommander Norton Commander: kék képernyő és kék window-k cián kerettel, a címek feketék ciánon (az aktív fehér ciánon). A menüsor és a státuszsor fekete ciánon, a legördülő menük feketék világosszürkén. A gombok fehérek zölden, fókuszban fehérek ciánon; a beviteli mezők feketék ciánon.
BBS::Theme.dosnavigator DOS Navigator: világosszürke képernyő és window-k fekete szöveggel, a címek ciánon, kék kiemelések. A gombok feketék szürkén, fókuszban fehérek zölden; a lista kijelölése fekete ciánon.
BBS::Theme.neumanntronics Visszafogott 256 színű paletta (38;5;N / 48;5;N): majdnem fekete háttér, kékeszöld szöveg, világos kékeszöld kiemelés, borostyánsárga hangsúlyok és hotkeyek, sötét kékeszöld sávok. 256 színű terminál kell hozzá. A window árnyéka a háttér színe, így a window-knak nincs látható árnyéka.
BBS::Theme.mono Egyáltalán nincs szín, csak félkövér, halvány, aláhúzott és inverz, a szín nélküli terminálokhoz. A screen és a window_bg üres string: a terminál saját háttere.

Szerepkulcsok

A Theme::ROLES: a presetek összes kulcsa (mind az öt mindet definiálja), mi rajzol vele a rubbs 1.0.0-ban, és a synchronet-beli értéke. A „a gem nem használja” jelzésű kulcsok a saját widgetjeidnek és screenjeidnek valók. Az 1.0 kidobta azokat a szerepeket, amelyekkel semmi nem rajzolt: a check_on, a check_off, a listbox_header, a warning és a reset szerepet.

Kulcs Mit színez synchronet
screen Az egész képernyő, minden más előtt kitöltve: az asztal háttere. \e[40m
text A Label alapértéke; a Checkbox; a ScrollView és minden Markdown-sor alapja. \e[0;37;40m
text_dim A TextInput placeholdere; a Markdown dőlt szövege és az idézetjel. \e[0;2;37;40m
text_bright A Markdown első szintű címsorai és a félkövér. \e[1;37;40m
label A Markdown harmadik és mélyebb szintű címsorai, a beágyazott kód, a kódblokkok, a vízszintes vonalak és a táblázatkeretek; az Info window-k elválasztó vonalai. \e[0;36;40m
accent A Markdown második szintű címsorai, a linkek, a listajelek és -számok. \e[1;33;40m
success A gem nem használja. \e[1;32;40m
error A Form window státuszsora (validációs hibák). \e[1;31;40m
window_bg A window belseje. \e[44m
window_border A window kerete. \e[1;37;44m
window_title Az inaktív window címe; a bezáró [■] és a zoom gomb, a window száma, az átméretező sarok. \e[1;33;44m
window_title_focused Az aktív window címe; mozgatás vagy átméretezés közben az egész keret. \e[1;30;47m
window_text A dialógusok és az Info window-k szövege. \e[0;37;44m
window_shadow A window-k és a legördülő menük árnyéka. \e[40m
menubar A menüsor. \e[1;30;47m
menubar_hotkey A hotkeyek betűi a menüsorban. \e[1;31;47m
menubar_selected A nyitott vagy kiemelt menücím. \e[1;37;42m
menubar_sel_hot Ennek a címnek a hotkey-betűje. \e[1;33;42m
menu_bg A legördülő menü kitöltése. \e[1;37;44m
menu_text A menüpontjai. \e[0;37;44m
menu_hotkey A menüpontok hotkey-betűi. \e[1;33;44m
menu_selected A kiemelt menüpont. \e[1;37;46m
menu_disabled A letiltott menüpontok. \e[0;2;37;44m
menu_border A legördülő menü kerete és elválasztó vonalai. \e[1;37;44m
statusbar A státuszsor. \e[1;30;47m
statusbar_key A billentyűnevek a státuszsor tippjeiben. \e[1;31;47m
button A Button. \e[0;30;47m
button_focused A fókuszban lévő Button; a fókuszban lévő Checkbox is. \e[1;37;42m
button_hotkey A Button hotkey-betűje. \e[1;31;47m
button_disabled A letiltott Button. \e[0;2;30;47m
input A TextInput és a TextArea. \e[1;37;44m
input_focused Ugyanezek fókuszban. \e[1;30;47m
input_label A mezőcímkék a Form, Info és MasterDetail window-kban, és a Form tippje. \e[0;36;40m
listbox A ListBox sorai. \e[0;37;44m
listbox_selected A fókusz nélküli ListBox kijelölt sora. \e[1;37;46m
listbox_focused A fókuszban lévő ListBox kijelölt sora. \e[1;30;47m
scrollbar A ScrollView görgetősávjának pályája. \e[0;36;44m
scrollbar_thumb A csúszkája. \e[1;37;46m

A presetek minden SGR-értéke az előtér mellett a hátteret is megadja (37;40, 37;44), és többségük 0;-val kezdődik, így egy stílus sosem örökli az előtte rajzolt cella félkövérségét vagy halványságát. A saját értékeidben is így tégy.

Téma választása az applicationhöz

A BBS::Application.define blokkjában a theme egy téma-példányt vagy egy preset nevét várja, akár egy felülíró blokkal együtt:

RUBY
require 'bbs'

APP = BBS::Application.define do
  theme BBS::Theme.nortoncommander
end
Alak Eredmény
theme BBS::Theme.synchronet Az a téma.
theme :synchronet Az ilyen nevű preset (Theme.fetch; ismeretlen névre ArgumentError).
theme some_theme.merge(accent: "\e[1;36;40m") Bármilyen BBS::Theme példány.
theme(:nortoncommander) { ... } Az ilyen nevű preset, a blokk felülírásaival.
theme(BBS::Theme.mono) { ... } Az a példány, a blokk felülírásaival.
theme { ... } A synchronet, a blokk felülírásaival.
nincs theme hívás synchronet.

A 0.17.0-ban a csupasz Symbol-alak (theme :synchronet) magát a Symbolt tárolta el, és az első újrarajzolás TypeError-ral elszállt, az argumentum nélküli APP.theme pedig visszaállította a tárolt témát; 0.17.1 óta a Symbol a presetet veszi, és a builder argumentum nélküli theme-je az eddig beállított témát adja vissza (nil-t, ha nem volt theme hívás, ami synchronet-et jelent).

Az applicationön belül a theme a használt BBS::Theme-et adja vissza; ezt kapja a BBS::Markdown.to_lines, és ebből színezhetsz saját stringet:

RUBY
helpers do
  def warn_line(text) = "#{theme[:error]}#{text}#{theme[:text]}"
end

Kulcsok felülírása

A theme blokkban minden egyargumentumos hívás beállítja az adott kulcsot; a style(key, value) ennek explicit alakja, az inherit pedig lecseréli az alapot, amire a felülírások kerülnek:

RUBY
require 'bbs'

APP = BBS::Application.define do
  theme :nortoncommander do
    inherit :neumanntronics              # opcionális: másik preset legyen az alap
    window_border "\e[1;36;44m"
    accent        "\e[1;33;44m"
    style :screen, "\e[40m"
  end
end
Hívás Hatás
KULCS érték Beállítja a KULCS-ot, a Theme::ROLES egyik szerepét. Egy elgépelt kulcs NoMethodError-t dob.
style(key, value) Ugyanez, explicit módon.
inherit(base) Másik alap: presetnév (Symbol) vagy BBS::Theme. Az előtte és utána beállított felülírások megmaradnak.

DSL nélkül a merge ugyanezt teszi:

RUBY
require 'bbs'

custom = BBS::Theme.synchronet.merge(
  accent:         "\e[1;36;40m",
  button_focused: "\e[1;37;44m"
)

Saját téma

Egy teljes Hashből építs BBS::Theme-et. A legegyszerűbben úgy kapod meg az összes kulcsot, ha egy preset Hashéből indulsz a Theme::PRESETS-ben:

RUBY
require 'bbs'

AMBER = BBS::Theme.new(:amber, BBS::Theme::PRESETS[:mono].merge(
  screen:           "\e[40m",
  text:             "\e[0;33;40m",
  text_dim:         "\e[0;2;33;40m",
  text_bright:      "\e[1;33;40m",
  label:            "\e[0;33;40m",
  accent:           "\e[1;33;40m",
  window_bg:        "\e[40m",
  window_border:    "\e[0;33;40m",
  window_title:     "\e[1;33;40m",
  window_title_focused: "\e[0;30;43m",
  window_text:      "\e[0;33;40m",
  button:           "\e[0;30;43m",
  button_focused:   "\e[1;37;43m",
  input:            "\e[0;33;40m",
  input_focused:    "\e[0;30;43m",
  listbox:          "\e[0;33;40m",
  listbox_selected: "\e[0;30;43m",
  listbox_focused:  "\e[1;37;43m"
))

APP = BBS::Application.define do
  theme AMBER
end

A paletta: BBS::Ansi

A témán kívül minden szín egyetlen palettából jön, a BBS::Ansi-ból (lib/bbs/ansi.rb): a flow stílusai, egy soralapú board say-e, egy FakeOS, egy logsor, egy gyors door. A BBS::Color (Color.c(:yellow, text)) az 1.0 óta nincs meg; helyette a konstanst és egy resetet írj.

Konstans SGR Konstans SGR
GREY (sima szöveg) \e[0;37m BRIGHT_YELLOW \e[1;33m
DIM_GREY \e[2;37m BLUE \e[0;34m
DARK_GREY \e[1;30m BRIGHT_BLUE \e[1;34m
WHITE \e[1;37m MAGENTA \e[0;35m
BRIGHT_RED \e[1;31m CYAN \e[0;36m
GREEN \e[0;32m BRIGHT_CYAN \e[1;36m
BRIGHT_GREEN \e[1;32m REVERSE / REVERSE_OFF \e[7m / \e[27m
YELLOW \e[0;33m RESET \e[0m
RUBY
require 'bbs'

p "#{BBS::Ansi::CYAN}hello#{BBS::Ansi::RESET}"    # => "\e[0;36mhello\e[0m"

A BBS::Petscii::Colors ugyanezeknek a szekvenciáknak a C64-es nevei (WH YE CY LG GR DG LB BL GN RD PU OR, valamint RV RO az inverz be- és kikapcsolásához; lásd Commodore 64), és egy komponens ezekből a színekből álló szerep-Hasht kap: a BBS::Selector::COLORS-t, a Flow FlowRunner::STYLES-át, egy FakeOS colors-át. A C64 grafikus vásznán ugyanezekből C64-es színkód lesz: BBS::Petscii::Canvas.color(YE) == Canvas::YELLOW.

BBS::FrameBuffer

A BBS::FrameBuffer (lib/bbs/frame_buffer.rb) egy képernyőn kívüli cellarács; minden cella egy karakter és egy SGR-string. Az Application egy egész képkockát rajzol bele, összeveti az előzővel, és csak a különbséget küldi el. A widgetek a render(frame)-ben kapnak egyet.

Dupla pufferelés és delta diff

Az Application minden újrarajzolása (a privát render_screen) ezt csinálja:

  1. Készít egy üres, terminálméretű next képkockát. Megtartja a committed képkockát (amit a terminál most mutat); az első rajzoláskor vagy átméretezés után üresről indul, és letörli a képernyőt.
  2. Kitölti a next-et a téma screen stílusával, kirajzolja az asztalt, a window-kat alulról felfelé, a menüsort és a státuszsort.
  3. delta = next.diff(committed); ha nem üres, kurzorrejtés és -mutatás (\e[?25l … \e[?25h) közé csomagolva kiírja, és a next lesz az új committed képkocka.

Így egy billentyűleütés után csak a megváltozott cellák mennek le a vonalon: egy listakijelölés mozgatása két sorba kerül, egy ketyegő óra néhány karakterbe. Kézzel is ugyanez működik:

RUBY
require 'bbs'

committed = BBS::FrameBuffer.new(20, 2)
nxt = BBS::FrameBuffer.new(20, 2)
nxt.move(1, 1)
nxt.write('Time: 12:00', sgr: "\e[1;33m")
p nxt.diff(committed)     # => "\e[1;1H\e[0m\e[1;33mTime: 12:00\e[0m"
committed = nxt

nxt = BBS::FrameBuffer.new(20, 2)
nxt.move(1, 1)
nxt.write('Time: 12:01', sgr: "\e[1;33m")
p nxt.diff(committed)     # => "\e[1;11H\e[0m\e[1;33m1\e[0m"   csak a megváltozott számjegy
p nxt.diff(nxt)           # => ""

Konstansok

Konstans Érték
Cell Struct.new(:char, :sgr).
BLANK Egy fagyasztott Cell szóközzel, stílus nélkül: ezzel van tele egy új vagy törölt képkocka.
BOX_CHARS A box által használt :single (┌┐└┘─│) és :double (╔╗╚╝═║) vonalkészlet.

Osztálymetódusok ANSI-stringekhez

Ezek SGR-escape-eket tartalmazó stringeken dolgoznak, és csak a látható karaktereket számolják. Escape-szekvencia az, amire a BBS::Ansi::CSI_RE illeszkedik (/\e\[[\d;?]*[A-Za-z~]/: SGR, kurzormozgatás, \e[2~-féle billentyűk, és 1.0 óta a privát módú szekvenciák is, mint a \e[?25l); ez váltja a 0.x FrameBuffer::ANSI_ESC_RE-jét.

Metódus Mit csinál
FrameBuffer.text(value) value.to_s, az érvénytelen bájtokat ?-re cserélve, hogy egy regexp el tudja olvasni. Az összes többi metódus ezen megy át, így a nyers telnet-bájtok nem dönthetik össze a rajzolást.
FrameBuffer.plain(text) A szöveg escape-szekvenciák nélkül.
FrameBuffer.visible_width(text) A hossz escape-szekvenciák nélkül.
FrameBuffer.clip_ansi(text, max_width) max_width látható karakterre vágja a szöveget, a vágás előtti escape-eket megtartva. 0 vagy kisebb szélességre ''.
FrameBuffer.wrap_ansi(text, width) A gem egyetlen tördelője (a dialógusok, a window-k és a Markdown-renderelő is ezt használják). Szavanként tördeli legfeljebb width széles sorok tömbjébe (a sorvégi szóközöket levágja). A tördelési ponton aktív SGR-t a következő sor elején megismétli, így a szín átível. A szélességnél hosszabb szót nem vágja el; az külön sorba kerül, és kilóg. 0 vagy kisebb szélességre [''].
RUBY
require 'bbs'

fb = BBS::FrameBuffer
p fb.plain("\e[?25l\e[1;33mbright\e[0m")           # => "bright"
p fb.visible_width("\e[1;33mbright\e[0m normal")    # => 13
p fb.clip_ansi("\e[1;33mbright\e[0m normal", 4)     # => "\e[1;33mbrig"
p fb.wrap_ansi("\e[1;32mthe quick brown fox jumps\e[0m over", 10)
# => ["\e[1;32mthe quick", "\e[1;32mbrown fox", "\e[1;32mjumps\e[0m over"]
p fb.wrap_ansi('anextremelylongword here', 8)       # => ["anextremelylongword", "here"]
p fb.text("ok\xFFok")                               # => "ok?ok"

Rajzolás

Minden koordináta 1-től számozott oszlop és sor, mint a terminálban. A képkockán kívül eső részt bármelyik szélen levágja, így nyugodtan rajzolhatsz részben a képernyőn kívülre.

Metódus Mit csinál
FrameBuffer.new(cols, rows) Üres képkocka. A cols és a rows olvasható.
clear Minden cellát kiürít, és a kurzort a bal felső sarokba teszi.
move(col, row) Beállítja az írási pozíciót.
write(text, sgr: nil) A pozíciótól kezdve kiírja a text karaktereit, mind egy stílusban, és továbblép. Az escape-eket nem értelmezi, karakterként írná ki őket: stílusos stringhez a write_ansi való.
write_ansi(text, base_sgr: nil) Beágyazott escape-eket tartalmazó stringet ír ki. Lásd lent.
fill(x:, y:, width:, height:, sgr: nil, char: ' ') Kitölt egy téglalapot a char-ral.
hline(x:, y:, length:, char: '─', sgr: nil) Vízszintes vonal.
vline(x:, y:, length:, char: '│', sgr: nil) Függőleges vonal.
box(x:, y:, width:, height:, sgr: nil, fill_sgr: nil, style: :single, title: nil, title_sgr: nil, title_indent: 0) Keret sgr színben; a belseje szóközökkel kitöltve fill_sgr színben, ha meg van adva (különben érintetlen); a style :single vagy :double; a title a felső élben, title_sgr (vagy sgr) színben, title_indent oszloppal jobbra tolva és a helyre vágva. 2×2 alatt semmit nem rajzol.
shadow(x:, y:, width:, height:, sgr: nil) Árnyék a megadott téglalaphoz: két oszlop jobbra, egy sor alul, egy sorral és két oszloppal eltolva. Az sgr alapértéke \e[40m.
diff(from) Azok az ANSI-bájtok, amelyek a from képkockából ezt csinálják (lásd fent).

A write_ansi úgy értelmezi az escape-eket, ahogy egy terminál tenné, a képkockán belül:

  • Egy SGR-szekvencia (\e[...m) hozzáadódik az aktuális stílushoz; egy reset (\e[0m vagy \e[m) a base_sgr-hez tér vissza, nem a „stílus nélkülihez”. Így tartja meg egy kék window-ban egy színes szó utáni szöveg a kék hátteret.
  • A \e[NC (kurzor előre) N szóközt ír az aktuális stílusban, ahogy az ANSI-art használja.
  • Minden más CSI-szekvencia elvész, a kurzormozgatás (\e[SOR;OSZLOPH) is, így egy widgeten belüli szöveg nem ugorhat ki belőle; a 0.x cursor: opciója megszűnt. A vezérlőkarakterek is elvesznek (a \n és a \r is): egy hívás egy sort ír.
RUBY
require 'bbs'

fb = BBS::FrameBuffer.new(20, 4)
fb.move(1, 1)
fb.write('Hello', sgr: "\e[1;33m")
fb.move(18, 1)
fb.write('clipped')                                        # csak a "cli" fér ki
fb.move(1, 2)
fb.write_ansi("a\e[1mb\e[0mc\e[3Cd", base_sgr: "\e[44m")   # stílusok: 44 / 44+bold / 44, három szóköz, d
fb.move(1, 3)
fb.write_ansi("x\e[4;10Hy")                                # a H nem számít: "xy"

Az eredmény:

TEXT
Hello            cli
abc   d
xy

Keretes panel mintás háttéren, árnyékkal:

RUBY
require 'bbs'

fb = BBS::FrameBuffer.new(32, 9)
fb.fill(x: 1, y: 1, width: 32, height: 9, sgr: "\e[44m", char: '░')
fb.box(x: 3, y: 2, width: 24, height: 6, sgr: "\e[1;37;44m", fill_sgr: "\e[44m",
       style: :double, title: 'Inbox', title_sgr: "\e[1;33;44m", title_indent: 4)
fb.shadow(x: 3, y: 2, width: 24, height: 6)
fb.hline(x: 4, y: 4, length: 22)
fb.vline(x: 15, y: 5, length: 2)
fb.move(5, 3)
fb.write('3 new messages', sgr: "\e[0;37;44m")
TEXT
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
░░╔═════ Inbox═══════════╗░░░░░░
░░║ 3 new messages       ║  ░░░░
░░║──────────────────────║  ░░░░
░░║           │          ║  ░░░░
░░║           │          ║  ░░░░
░░╚══════════════════════╝  ░░░░
░░░░                        ░░░░
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░

Karakterszélesség

A framebuffer Ruby-karakterenként egy cellát számol. Ez helyes az ASCII-ra, az előre komponált ékezetes latin betűkre, a keretrajzoló, blokk- és braille-karakterekre, vagyis mindarra, amit maga a gem rajzol. Hibás viszont:

  • a széles karaktereknél (CJK, a legtöbb emoji): egy cellában tárolódnak, de a terminál két oszlopon mutatja őket, így a sor maradéka karakterenként eggyel jobbra csúszik;
  • a kombináló jeleknél (e plusz U+0301): két cellának számítanak, de egy oszlopon látszanak.

A visible_width, a clip_ansi, a wrap_ansi és a Markdown-táblázatok ugyanígy számolnak. Normalizáld a szöveget NFC-re (text.unicode_normalize(:nfc)), és ami pontosan egymás alá kell kerüljön, abba ne tegyél széles karaktert.

BBS::Renderer: ERB screenek

A BBS::Renderer (lib/bbs/renderer.rb) egy ERB-sablonból stringet állít elő egy soros, flow-stílusú screenhez. Ezt használja a Flow screen lépése (lásd Flow). A framebufferhez nincs köze: a dupla pufferelést a fent leírt FrameBuffer#diff végzi.

RUBY
BBS::Renderer.render(name, vars = {}, dir: BBS.config.screens_dir)
Argumentum Jelentés
name A sablon neve: a dir mappa NÉV.erb fájlja.
vars Egy Hash; minden párból példányváltozó lesz a sablonban (a username:-ből @username).
dir: A sablonmappa. RuntimeError-t dob („No screens_dir: …”), ha sem ez, sem a BBS.config.screens_dir nincs beállítva.

Az ERB trim_mode: '-'-dal fut (így a -%> elnyeli a sortörést), és az eredmény minden \n-je \r\n lesz a telnetvonal kedvéért. A sablon ezeket a helpereket hívhatja:

Helper Kimenet
clr \e[2J\e[H: képernyőtörlés, kurzor a sarokba.
reset \e[0m
green, yellow, cyan, white, red Az élénk szín (\e[1;32m, \e[1;33m, \e[1;36m, \e[1;37m, \e[1;31m).
gray, dim_green \e[0;37m, \e[0;32m
banner(text, padding: 2) Zöld dupla vonalas keret a szöveg körül.
big_banner(text, font: 'slant') FIGlet-betűk (az artii gemmel) zölden, két szóközzel beljebb.

screens/welcome.erb:

ERB
<%= clr %><%= banner 'Welcome' %>
   <%= green %>Hello <%= @username %>!<%= reset %>
<% if @new_mail > 0 -%>
   <%= yellow %>You have <%= @new_mail %> new message(s).<%= reset %>
<% end -%>
RUBY
require 'bbs'

out = BBS::Renderer.render('welcome', { username: 'tasi', new_mail: 2 }, dir: 'screens')

A terminálra írva (a keret és az üdvözlés élénkzöld, a levelekről szóló sor élénksárga):

TEXT
╔═══════════╗
║  Welcome  ║
╚═══════════╝
   Hello tasi!
   You have 2 new message(s).

BBS::Banner

A Flow banner és big_banner lépése a BBS::Banner-t (lib/bbs/banner.rb) használja, ami küldésre kész, \r\n sorvégű, két szóközzel behúzott stringeket ad:

Metódus Kimenet
Banner.render(text, color: "\e[1;32m", padding: 2) Dupla vonalas keret a szöveg körül, mindkét oldalon padding szóközzel.
Banner.render_big(text, color: "\e[1;32m", font: 'slant') FIGlet-betűk az artii-vel; bármely Artii-fontnév jó (slant, big, small…).
RUBY
require 'bbs'

print BBS::Banner.render_big('BBS', font: 'small').gsub("\r\n", "\n")
TEXT
    ___ ___ ___
   | _ ) _ ) __|
   | _ \ _ \__ \
   |___/___/___/

BBS::Markdown

A BBS::Markdown (lib/bbs/markdown.rb) Markdown-szövegből ANSI-val színezett sorok tömbjét készíti, adott szélességre. Az eredményt egy BBS::Widgets::ScrollView-nak, egy görgethető Info window-nak adhatod, vagy magad írhatod ki a sorokat.

RUBY
BBS::Markdown.to_lines(text, width:, theme:)
Argumentum Jelentés
text A Markdown. Az érvénytelen bájtokból ? lesz.
width: A tördelési szélesség. Ha a sorok ScrollView-ba kerülnek, adj meg eggyel kevesebbet a szélességénél, hogy maradjon hely a görgetősávnak.
theme: Egy BBS::Theme (vagy bármi, ami válaszol a [key]-re); a színek a szerepeiből jönnek.

Applicationön belül a szokásos hívás:

RUBY
lines = BBS::Markdown.to_lines(text, width: width - 1, theme: ctx[:app].theme)

Minden stílusos szakasz a téma text SGR-jével zárul, így az utána következő szöveg visszakapja a hátterét: színezett window-háttéren ez számít. A sima bekezdéssorok nem kezdődnek saját SGR-rel; a ScrollView text alapstílussal rajzolja őket. Ha a sorokat közvetlenül egy sessionre írod, mindegyik elé tedd oda a theme[:text]-et.

Amit ért

Markdown Így jelenik meg Szerep
# Címsor A címsor szövege # nélkül (a záró #-ek is eltűnnek), tördelve. text_bright
## Címsor Ugyanígy. accent
###-tól ######-ig Ugyanígy, minden szint egyformán. label
Bekezdés Az egymást követő sorok egy-egy szóközzel összefűzve, a whitespace összevonva, szavanként tördelve. text
Üres sor Lezárja a bekezdést, és egy üres sort ad.
**félkövér** A szöveg. text_bright
*dőlt*, _dőlt_ A szöveg. Szavon belül nem: a snake_case_name és az a*b*c változatlan marad. text_dim
`kód` | A szöveg. | label
[szöveg](url) Csak a szöveg; az URL elmarad. accent
> idézet Minden sor │ jellel, a szélesség mínusz 2-re tördelve. jel: text_dim
- elem, * elem, + elem • elem, függő behúzással tördelve. A jel előtti szóközök megmaradnak, így készülnek a beágyazott listák. jel: accent
1. elem 1. elem, a szám úgy, ahogy le van írva, függő behúzással. szám: accent
Behúzott sor egy listaelem alatt Az elem szövegét folytatja.
---, ***, ___ (legalább három) Teljes szélességű ─ vonal. label
Kódblokk (három backtick, bármilyen info stringgel) A sorok változatlanul, a szélességre vágva (nem tördelve); a beágyazott ANSI átmegy. label
GFM-táblázat (fejléc, --- elválasztó sor, törzssorok) Keretrajzoló karakterekkel rajzolt táblázat; a fejléccellák félkövérek; :--, :-:, --: igazítás; a hiányzó cellák kitöltve; ha túl széles, a legszélesebb oszloptól kezdve szűkül, aztán a cellák vágódnak. keret: label, fejléc: text_bright
\*, \_, \`, \#, \[… A karakter backslash nélkül (lásd a korlátokat).

Egy minta 36-os szélességen, synchronet-tel:

RUBY
require 'bbs'

md = <<~MD
  # Welcome
  ## Rules
  ### Small print
  Be **nice**, *kind* and _brief_. See [the wiki](https://wiki.example) or run `help`.
  This line joins the paragraph above and wraps at the width.

  > Quoted text gets a gutter
  > on every line.

  - first item
  - second item that is long enough to wrap onto a second line
    continued here
    - nested
  1. one
  2. two

  ---

  | Name | Calls | Note |
  |:-----|------:|:----:|
  | ra   | 12    | ok   |
  | **zed** | 3 |  |
MD
lines = BBS::Markdown.to_lines(md, width: 36, theme: BBS::Theme.synchronet)
puts lines.map { |l| BBS::FrameBuffer.plain(l) }

Színek nélkül (a terminálban: a Welcome élénkfehér, a Rules sárga, a Small print cián, a nice élénkfehér, a kind és a brief halvány, a the wiki sárga, a help cián, a listajelek és -számok sárgák, a vonal és a táblázatkeret cián):

TEXT
Welcome
Rules
Small print
Be nice, kind and brief. See the
wiki or run help. This line joins
the paragraph above and wraps at the
width.

│ Quoted text gets a gutter
│ on every line.

• first item
• second item that is long enough to
  wrap onto a second line continued
  here
  • nested
1. one
2. two

────────────────────────────────────

┌──────┬───────┬──────┐
│ Name │ Calls │ Note │
├──────┼───────┼──────┤
│ ra   │    12 │  ok  │
│ zed  │     3 │      │
└──────┴───────┴──────┘

Egy el nem férő táblázat szűkül, aztán a cellái vágódnak (30-as szélesség):

TEXT
┌──────────┬─────────────────┐
│ Door     │ Description     │
├──────────┼─────────────────┤
│ SERMO    │ An AI character │
│ Impostor │ Social deductio │
└──────────┴─────────────────┘

Korlátok

A forrással ellenőrizve; ezek egyikére se építs:

  • A backslash nem állítja meg a kiemelést. Az escape-ek a kiemelési szabályok után érvényesülnek, így a \*not italic\* mégis dőlt lesz, a backslashek pedig megmaradnak: az eredmény egy backslash, egy halvány not italic, majd még egy backslash. Ugyanez a helyzet a \_x\_-szel. Ahol nem illeszkedik kiemelési szabály, ott működik az escape: a 2\*3\*4-ből 2*3*4, a \# no heading-ből # no heading lesz.
  • A kódszakaszokon belül is eltűnnek az escape-ek: a `a\*b` a*b-t mutat.
  • Nincs __félkövér__, ~~áthúzás~~, HTML vagy entitás (úgy jelennek meg, ahogy le vannak írva), setext-címsor (=== egy sor alatt), behúzott kódblokk, kemény sortörés (két sorvégi szóköz) és feladatlista (a - [ ] megtartja a szögletes zárójelet).
  • Képek: a ![alt](src) egy !-et és az alt szöveget mutatja a link színében.
  • Beágyazott idézet: a > > x egy jelet és egy szó szerinti > x-et ad.
  • A harmadiktól a hatodik szintig a címsorok egyformák.
  • A kódblokkok vágódnak, nem tördelődnek; a szélességnél hosszabb szót nem vágja el.
  • A széles karakterek egy oszlopnak számítanak (lásd fent, Karakterszélesség).

Lásd még