Skip to content

Commit

Permalink
Merge pull request #2 from thatstoasty/feature/place
Browse files Browse the repository at this point in the history
Add whitespace renderer and updated layout example.
  • Loading branch information
thatstoasty authored Apr 15, 2024
2 parents 4827967 + 02f4344 commit a305d34
Show file tree
Hide file tree
Showing 36 changed files with 1,166 additions and 208 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ Ported from/Inspired by: <https://github.com/charmbracelet/lipgloss/tree/master>

If you're a Go developer, please check out their CLI tooling and libraries. They're unmatched!

For bugs and todos, see the bottom of the readme. At the moment, characters with a printable length greater than 1 ARE NOT supported.

![Lip Gloss example](https://github.com/thatstoasty/mog/blob/main/layout.png)

Lip Gloss takes an expressive, declarative approach to terminal rendering.
Expand Down Expand Up @@ -458,3 +460,8 @@ TODO:
- Decompose style render mega function and mega class into smaller ones.
- Figure out capturing variables in table style functions. Using escaping and capturing crashes, and creating the style each time the function is called is slow.
- Fix table top and bottom rendering. Fire emoji border example renders those lengths incorrectly.
- Code cannot handle characters with a printable length greater than 1.

Notes:

- ANSI256's support of setting both foreground and background colors is limited. It's possible to set both, but often the foreground color will be ignored.
6 changes: 3 additions & 3 deletions examples/fire.mojo
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ from mog import position

fn main() raises:
# The page style
var fire_style = Style.new().padding(1).border(rounded_border()).border_foreground(
"#eb4034"
).foreground("#eb4034")
var fire_style = Style.new().padding(1).border(
rounded_border()
).border_foreground("#eb4034").foreground("#eb4034")

# gradient colors
var colors = List[String](
Expand Down
170 changes: 112 additions & 58 deletions examples/readme/layout.mojo
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,22 @@ from mog.style import Style
from mog.extensions import repeat
from mog.size import get_width
from mog import position
from mog.whitespace import (
place,
with_whitespace_chars,
with_whitespace_foreground,
)
import mog


fn main() raises:
# The page style
var builder = StringBuilder()
var doc_style = Style.new().padding(1, 2, 1, 2).border(rounded_border()).width(
106
).border_foreground(mog.Color("#383838"))
alias width = 96
alias column_width = 30
alias subtle = mog.AdaptiveColor(light="#D9DCCF", dark="#383838")
alias highlight = mog.AdaptiveColor(light="#874BFD", dark="#7D56F4")
alias special = mog.AdaptiveColor(light="#43BF6D", dark="#73F59F")
alias width = 96
alias column_width = 30
alias subtle = mog.AdaptiveColor(light="#D9DCCF", dark="#383838")
alias highlight = mog.AdaptiveColor(light="#874BFD", dark="#7D56F4")
alias special = mog.AdaptiveColor(light="#43BF6D", dark="#73F59F")

# Tabs.

fn build_tabs() raises -> String:
var active_tab_border = Border(
top="",
bottom=" ",
Expand All @@ -46,14 +46,14 @@ fn main() raises:
)

var tab_style = Style.new().border(tab_border).border_foreground(
mog.Color("#7D56F4")
highlight
).padding(0, 1)

var active_tab = tab_style.copy().border(active_tab_border, True)

var tab_gap = tab_style.copy().border_top(False).border_left(False).border_right(
var tab_gap = tab_style.copy().border_top(False).border_left(
False
)
).border_right(False)

var row = join_horizontal(
position.top,
Expand All @@ -64,10 +64,10 @@ fn main() raises:
tab_style.render("Prism"),
)
var gap = tab_gap.render(repeat(" ", max(0, width - get_width(row) - 2)))
_ = builder.write_string(join_horizontal(position.bottom, row, gap))
_ = builder.write_string("\n\n")
return join_horizontal(position.bottom, row, gap)

# Title

fn build_description() raises -> String:
var divider = Style.new().padding(0, 1).foreground(subtle).render("")

var url = Style.new().foreground(special)
Expand All @@ -76,21 +76,22 @@ fn main() raises:
normal_border(), True, False, False, False
).border_foreground(subtle)

var desc = join_vertical(
return join_vertical(
position.left,
desc_style.render(
"Style Definitions for Nice Terminal Layouts.\nInspired by"
" charmbracelet/lipgloss"
),
info_style.render(
"From Mikhail" + divider + url.render("https://github.com/thatstoasty/mog")
"From Mikhail"
+ divider
+ url.render("https://github.com/thatstoasty/mog")
),
)
_ = builder.write_string(desc + "\n\n")

# Dialog box
# TODO: Temporarily the full length of the doc until the renderer funcs are added
var dialog_box_style = Style.new().width(width).alignment(position.center).border(

fn build_dialog_box() raises -> String:
var dialog_box_style = Style.new().alignment(position.center).border(
rounded_border()
).border_foreground(mog.Color("#874BFD")).padding(1, 0)

Expand All @@ -108,29 +109,42 @@ fn main() raises:
var question = Style.new().width(50).alignment(position.center).render(
"Are you sure you want to eat marmalade?"
)

var buttons = join_horizontal(position.top, ok_button, cancel_button)
var ui = join_vertical(position.center, question, buttons)

_ = builder.write_string(dialog_box_style.render(ui) + "\n\n")
# TODO: Cannot handle unicode characters with a length greater than 1. For example: east asian characters like Kanji.
return place(
width,
9,
position.center,
position.center,
dialog_box_style.render(ui),
with_whitespace_chars["⣾⣽⣻⢿⡿⣟⣯⣷"](),
with_whitespace_foreground[subtle](),
)

# List

fn build_lists() raises -> String:
var list_style = Style.new().border(
normal_border(), False, True, False, False
).border_foreground(mog.Color("#383838")).margin_right(2).height(8).width(
column_width + 3
).border_foreground(subtle).margin_right(2).height(8).width(
column_width + 1
)

var list_header = Style.new().border(
normal_border(), False, False, True, False
).border_foreground(mog.Color("#383838")).margin_right(2)
).border_foreground(subtle).margin_right(2)

var list_item = Style.new().padding_left(2)

var check_mark = Style.new().foreground(mog.Color("#73F59F")).padding_right(
1
).render("")
var check_mark = Style.new().foreground(special).padding_right(1).render(
""
)

var list_done = Style.new().crossout().foreground(mog.Color("#696969"))
var list_done = Style.new().crossout().foreground(
mog.AdaptiveColor(light="#969B86", dark="#696969")
)

var lists = join_horizontal(
position.top,
Expand All @@ -146,7 +160,7 @@ fn main() raises:
),
),
list_style.copy()
.width(column_width + 2)
.width(column_width)
.render(
join_vertical(
position.left,
Expand All @@ -159,7 +173,7 @@ fn main() raises:
),
),
list_style.copy()
.width(column_width)
.width(column_width - 1)
.render(
join_vertical(
position.left,
Expand All @@ -173,34 +187,38 @@ fn main() raises:
),
)

_ = builder.write_string(join_horizontal(position.top, lists))
_ = builder.write_string("\n")
return join_horizontal(position.top, lists)

# History
var history_style = Style.new().height(20).width(column_width).padding(1, 2).margin(
1, 3, 0, 0
).alignment(position.left).border(hidden_border()).background(mog.Color("#9846eb"))

fn build_history() raises -> String:
var history_style = Style.new().height(20).width(column_width).padding(
1, 2
).margin(1, 3, 0, 0).alignment(position.left).foreground(
mog.Color("#FFFDF5")
).background(
highlight
)

alias history_a = "The Romans learned from the Greeks that quinces slowly cooked with honey would “set” when cool. The Apicius gives a recipe for preserving whole quinces, stems and leaves attached, in a bath of honey diluted with defrutum: Roman marmalade. Preserves of quince and lemon appear (along with rose, apple, plum and pear) in the Book of ceremonies of the Byzantine Emperor Constantine VII Porphyrogennetos."
alias history_b = "Medieval quince preserves, which went by the French name cotignac, produced in a clear version and a fruit pulp version, began to lose their medieval seasoning of spices in the 16th century. In the 17th century, La Varenne provided recipes for both thick and clear cotignac."
alias history_c = "In 1524, Henry VIII, King of England, received a “box of marmalade” from Mr. Hull of Exeter. This was probably marmelada, a solid quince paste from Portugal, still made and sold in southern Europe today. It became a favourite treat of Anne Boleyn and her ladies in waiting."

_ = builder.write_string(
join_horizontal(
position.top,
history_style.copy().alignment(position.right).render(history_a),
history_style.copy().alignment(position.center).render(history_b),
history_style.copy().margin_right(0).render(history_c),
)
return join_horizontal(
position.top,
history_style.copy().alignment(position.right).render(history_a),
history_style.copy().alignment(position.center).render(history_b),
history_style.copy().margin_right(0).render(history_c),
)
_ = builder.write_string("\n\n")

# Status bar
var status_nugget_style = Style.new().foreground(mog.Color("#FFFDF5")).padding(0, 1)

var status_bar_style = Style.new().foreground(mog.Color("#C1C6B2")).background(
mog.Color("#353533")
).width(width)
fn build_status_bar() raises -> String:
var status_nugget_style = Style.new().foreground(
mog.Color("#FFFDF5")
).padding(0, 1)

var status_bar_style = Style.new().foreground(
mog.Color("#C1C6B2")
).background(mog.Color("#353533"))

var status_style = Style.new().foreground(mog.Color("#FFFDF5")).background(
mog.Color("#FF5F87")
Expand All @@ -212,13 +230,18 @@ fn main() raises:
).horizontal_alignment(position.right)

var status_text_style = status_bar_style.copy().padding_left(1)
var fish_cake_style = status_nugget_style.copy().background(mog.Color("#6124DF"))
var fish_cake_style = status_nugget_style.copy().background(
mog.Color("#6124DF")
)

var status_key = status_style.render("STATUS")
var encoding = encoding_style.render("UTF-8")
var fish_cake = fish_cake_style.render("🍥 Fish Cake")
var fish_cake = fish_cake_style.render("Fish Cake")
var status_val = status_text_style.copy().width(
width - get_width(status_key) - get_width(encoding) - get_width(fish_cake)
width
- get_width(status_key)
- get_width(encoding)
- get_width(fish_cake)
).render("Ravishing")

var bar = join_horizontal(
Expand All @@ -229,7 +252,38 @@ fn main() raises:
fish_cake,
)

_ = builder.write_string(status_bar_style.width(width).render(bar))
return status_bar_style.width(width).render(bar)


fn main() raises:
# The page style
var builder = StringBuilder()
var doc_style = Style.new().padding(1, 2, 1, 2).border(
rounded_border()
).border_foreground(subtle)

# Tabs.
_ = builder.write_string(build_tabs())
_ = builder.write_string("\n\n")

# Title
_ = builder.write_string(build_description())
_ = builder.write_string("\n\n")

# Dialog box
_ = builder.write_string(build_dialog_box())
_ = builder.write_string("\n\n")

# List
_ = builder.write_string(build_lists())
_ = builder.write_string("\n")

# History
_ = builder.write_string(build_history())
_ = builder.write_string("\n\n")

# Status bar
_ = builder.write_string(build_status_bar())

# Render the final document with doc_style
print(doc_style.render(str(builder)))
4 changes: 3 additions & 1 deletion external/gojo/builtins/bytes.mojo
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ fn has_suffix(bytes: List[Byte], suffix: List[Byte]) -> Bool:
True if the List[Byte] struct ends with suffix; otherwise, False.
"""
var len_comparison = len(bytes) >= len(suffix)
var suffix_comparison = equals(bytes[len(bytes) - len(suffix) : len(bytes)], suffix)
var suffix_comparison = equals(
bytes[len(bytes) - len(suffix) : len(bytes)], suffix
)
return len_comparison and suffix_comparison


Expand Down
10 changes: 6 additions & 4 deletions external/gojo/bytes/buffer.mojo
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,9 @@ struct Buffer(

# all bytes should have been written, by definition of write method in io.Writer
if bytes_written != bytes_to_write:
return Result(total_bytes_written, WrappedError(ERR_SHORT_WRITE))
return Result(
total_bytes_written, WrappedError(ERR_SHORT_WRITE)
)

# Buffer is now empty; reset.
self.reset()
Expand Down Expand Up @@ -608,7 +610,7 @@ fn new_buffer() -> Buffer:
sufficient to initialize a [Buffer].
"""
var b = List[Byte](capacity=BUFFER_SIZE)
return Buffer(b ^)
return Buffer(b^)


fn new_buffer(owned buf: List[Byte]) -> Buffer:
Expand All @@ -628,7 +630,7 @@ fn new_buffer(owned buf: List[Byte]) -> Buffer:
Returns:
A new [Buffer] initialized with the provided bytes.
"""
return Buffer(buf ^)
return Buffer(buf^)


fn new_buffer(owned s: String) -> Buffer:
Expand All @@ -646,4 +648,4 @@ fn new_buffer(owned s: String) -> Buffer:
A new [Buffer] initialized with the provided string.
"""
var bytes_buffer = List[Byte](s.as_bytes())
return Buffer(bytes_buffer ^)
return Buffer(bytes_buffer^)
12 changes: 9 additions & 3 deletions external/gojo/bytes/reader.mojo
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,9 @@ struct Reader(
"""
# cannot modify state - see io.ReaderAt
if off < 0:
return Result(0, WrappedError("bytes.Reader.read_at: negative offset"))
return Result(
0, WrappedError("bytes.Reader.read_at: negative offset")
)

if off >= Int64(len(self.buffer)):
return Result(0, WrappedError(io.EOF))
Expand Down Expand Up @@ -99,7 +101,9 @@ struct Reader(
Complements [Reader.read_byte] in implementing the [io.ByteScanner] Interface.
"""
if self.index <= 0:
return WrappedError("bytes.Reader.unread_byte: at beginning of slice")
return WrappedError(
"bytes.Reader.unread_byte: at beginning of slice"
)

self.prev_rune = -1
self.index -= 1
Expand Down Expand Up @@ -154,7 +158,9 @@ struct Reader(
elif whence == io.SEEK_END:
position = len(self.buffer) + offset
else:
return Result(Int64(0), WrappedError("bytes.Reader.seek: invalid whence"))
return Result(
Int64(0), WrappedError("bytes.Reader.seek: invalid whence")
)

if position < 0:
return Result(
Expand Down
Loading

0 comments on commit a305d34

Please sign in to comment.