feat!: decouple ui.List, ui.Bar, ui.Border, and ui.Gauge from coordinates (#1782)

This commit is contained in:
三咲雅 · Misaki Masa 2024-10-14 19:39:59 +08:00 committed by GitHub
parent 43b5ae0e6c
commit 1acd7ca80f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 156 additions and 137 deletions

View File

@ -11,15 +11,15 @@ function Current:new(area, tab)
end
function Current:empty()
local line
local text
if self._folder.files.filter then
line = ui.Line("No filter results")
text = ui.Text("No filter results")
else
line = ui.Line(self._folder.stage.is_loading and "Loading..." or "No items")
text = ui.Text(self._folder.stage.is_loading and "Loading..." or "No items")
end
return {
ui.Text(line):area(self._area):align(ui.Text.CENTER),
text:area(self._area):align(ui.Text.CENTER),
}
end
@ -31,8 +31,8 @@ function Current:render()
local entities, linemodes = {}, {}
for _, f in ipairs(files) do
linemodes[#linemodes + 1] = Linemode:new(f):render()
entities[#entities + 1] = Entity:new(f):render()
linemodes[#linemodes + 1] = Linemode:new(f):render()
end
return {

View File

@ -95,6 +95,7 @@ function Header:render()
self._right_width = right:width()
local left = self:children_render(self.LEFT)
return {
ui.Text(left):area(self._area),
ui.Text(right):area(self._area):align(ui.Text.RIGHT),

View File

@ -29,7 +29,7 @@ function Marker:render()
w = 1,
h = math.min(1 + last[2] - last[1], self._area.y + self._area.h - y),
}
elements[#elements + 1] = ui.Bar(rect, ui.Bar.LEFT):style(last[3])
elements[#elements + 1] = ui.Bar(ui.Bar.LEFT):area(rect):style(last[3])
end
local last = { 0, 0, nil } -- start, end, style

View File

@ -21,7 +21,7 @@ function Progress:partial_render()
return { ui.Text {} }
end
local gauge = ui.Gauge(self._area)
local gauge = ui.Gauge():area(self._area)
if progress.fail == 0 then
gauge = gauge:gauge_style(THEME.status.progress_normal)
else

View File

@ -11,8 +11,8 @@ end
function Rail:build()
self._base = {
ui.Bar(self._chunks[1], ui.Bar.RIGHT):symbol(THEME.manager.border_symbol):style(THEME.manager.border_style),
ui.Bar(self._chunks[3], ui.Bar.LEFT):symbol(THEME.manager.border_symbol):style(THEME.manager.border_style),
ui.Bar(ui.Bar.RIGHT):area(self._chunks[1]):symbol(THEME.manager.border_symbol):style(THEME.manager.border_style),
ui.Bar(ui.Bar.LEFT):area(self._chunks[3]):symbol(THEME.manager.border_symbol):style(THEME.manager.border_style),
}
self._children = {
Marker:new(self._chunks[1], self._tab.parent),

View File

@ -132,8 +132,10 @@ end
function Status:render()
local left = self:children_render(self.LEFT)
local right = self:children_render(self.RIGHT)
local right_width = right:width()
return {
ui.Text(left):area(self._area),
ui.Text(right):area(self._area):align(ui.Text.RIGHT),

View File

@ -7,9 +7,7 @@ function M:peek()
local files, bound, code = self.list_files({ "-p", tostring(self.file.url) }, self.skip, limit)
if code ~= 0 then
return ya.preview_widgets(self, {
ui.Text(
ui.Line(code == 2 and "File list in this archive is encrypted" or "Spawn `7z` and `7zz` both commands failed")
)
ui.Text(code == 2 and "File list in this archive is encrypted" or "Spawn `7z` and `7zz` both commands failed")
:area(self.area),
})
end
@ -27,9 +25,9 @@ function M:peek()
end
if f.size > 0 then
sizes[#sizes + 1] = ui.Line(string.format(" %s ", ya.readable_size(f.size)))
sizes[#sizes + 1] = string.format(" %s ", ya.readable_size(f.size))
else
sizes[#sizes + 1] = ui.Line("")
sizes[#sizes + 1] = ""
end
end

View File

@ -6,7 +6,7 @@ function M:peek()
ya.manager_emit("peek", { bound, only_if = self.file.url, upper_bound = true })
elseif err and not err:find("cancelled", 1, true) then
ya.preview_widgets(self, {
ui.Text(ui.Line(err):reverse()):area(self.area),
ui.Text(err):area(self.area):reverse(),
})
end
end

View File

@ -1,6 +1,6 @@
local M = {}
function M:msg(s) ya.preview_widgets(self, { ui.Text(ui.Line(s):reverse()):area(self.area):wrap(ui.Text.WRAP) }) end
function M:msg(s) ya.preview_widgets(self, { ui.Text(s):area(self.area):reverse():wrap(ui.Text.WRAP) }) end
function M:peek()
local path = tostring(self.file.url)

View File

@ -4,14 +4,14 @@ function M:peek()
local cmd = os.getenv("YAZI_FILE_ONE") or "file"
local output, code = Command(cmd):args({ "-bL", tostring(self.file.url) }):stdout(Command.PIPED):output()
local p
local text
if output then
p = ui.Text.parse("----- File Type Classification -----\n\n" .. output.stdout):area(self.area)
text = ui.Text.parse("----- File Type Classification -----\n\n" .. output.stdout)
else
p = ui.Text(string.format("Spawn `%s` command returns %s", cmd, code)):area(self.area)
text = ui.Text(string.format("Spawn `%s` command returns %s", cmd, code))
end
ya.preview_widgets(self, { p:wrap(ui.Text.WRAP) })
ya.preview_widgets(self, { text:area(self.area):wrap(ui.Text.WRAP) })
end
function M:seek() end

View File

@ -13,7 +13,7 @@ function M:peek()
if #folder.files == 0 then
return ya.preview_widgets(self, {
ui.Text(ui.Line(folder.stage.is_loading and "Loading..." or "No items")):area(self.area):align(ui.Text.CENTER),
ui.Text(folder.stage.is_loading and "Loading..." or "No items"):area(self.area):align(ui.Text.CENTER),
})
end

View File

@ -3,7 +3,7 @@ use ratatui::widgets::Borders;
use super::{Rect, Renderable};
#[derive(Clone)]
#[derive(Clone, Default)]
pub struct Bar {
area: Rect,
@ -14,14 +14,8 @@ pub struct Bar {
impl Bar {
pub fn install(lua: &Lua, ui: &Table) -> mlua::Result<()> {
let new = lua.create_function(|_, (_, area, direction): (Table, Rect, u8)| {
Ok(Self {
area,
direction: Borders::from_bits_truncate(direction),
symbol: Default::default(),
style: Default::default(),
})
let new = lua.create_function(|_, (_, direction): (Table, u8)| {
Ok(Self { direction: Borders::from_bits_truncate(direction), ..Default::default() })
})?;
let bar = lua.create_table_from([

View File

@ -22,9 +22,8 @@ pub struct Border {
impl Border {
pub fn install(lua: &Lua, ui: &Table) -> mlua::Result<()> {
let new = lua.create_function(|_, (_, area, position): (Table, Rect, u8)| {
let new = lua.create_function(|_, (_, position): (Table, u8)| {
Ok(Border {
area,
position: ratatui::widgets::Borders::from_bits_truncate(position),
..Default::default()
})

View File

@ -1,42 +1,11 @@
use mlua::{FromLua, Lua, Table, UserData};
#[derive(Clone, Copy, FromLua)]
#[derive(Clone, Copy, Default, FromLua)]
pub struct Constraint(pub(super) ratatui::layout::Constraint);
impl Constraint {
pub fn install(lua: &Lua, ui: &Table) -> mlua::Result<()> {
let constraint = lua.create_table_from([
(
"Min",
lua.create_function(|_, n: u16| Ok(Constraint(ratatui::layout::Constraint::Min(n))))?,
),
(
"Max",
lua.create_function(|_, n: u16| Ok(Constraint(ratatui::layout::Constraint::Max(n))))?,
),
(
"Length",
lua.create_function(|_, n: u16| Ok(Constraint(ratatui::layout::Constraint::Length(n))))?,
),
(
"Percentage",
lua.create_function(|_, n: u16| {
Ok(Constraint(ratatui::layout::Constraint::Percentage(n)))
})?,
),
(
"Ratio",
lua.create_function(|_, (a, b): (u32, u32)| {
Ok(Constraint(ratatui::layout::Constraint::Ratio(a, b)))
})?,
),
(
"Fill",
lua.create_function(|_, n: u16| Ok(Constraint(ratatui::layout::Constraint::Fill(n))))?,
),
])?;
ui.raw_set("Constraint", constraint)
pub fn install(_: &Lua, ui: &Table) -> mlua::Result<()> {
ui.raw_set("Constraint", Constraint::default())
}
}
@ -44,4 +13,15 @@ impl From<Constraint> for ratatui::layout::Constraint {
fn from(value: Constraint) -> Self { value.0 }
}
impl UserData for Constraint {}
impl UserData for Constraint {
fn add_methods<'lua, M: mlua::UserDataMethods<'lua, Self>>(methods: &mut M) {
use ratatui::layout::Constraint as C;
methods.add_function("Min", |_, n: u16| Ok(Self(C::Min(n))));
methods.add_function("Max", |_, n: u16| Ok(Self(C::Max(n))));
methods.add_function("Length", |_, n: u16| Ok(Self(C::Length(n))));
methods.add_function("Percentage", |_, n: u16| Ok(Self(C::Percentage(n))));
methods.add_function("Ratio", |_, (a, b): (u32, u32)| Ok(Self(C::Ratio(a, b))));
methods.add_function("Fill", |_, n: u16| Ok(Self(C::Fill(n))));
}
}

View File

@ -16,10 +16,7 @@ pub struct Gauge {
impl Gauge {
pub fn install(lua: &Lua, ui: &Table) -> mlua::Result<()> {
ui.raw_set(
"Gauge",
lua.create_function(|_, area: Rect| Ok(Gauge { area, ..Default::default() }))?,
)
ui.raw_set("Gauge", lua.create_function(|_, ()| Ok(Gauge::default()))?)
}
}

View File

@ -10,49 +10,14 @@ const LEFT: u8 = 0;
const CENTER: u8 = 1;
const RIGHT: u8 = 2;
const EXPECTED: &str = "expected a string, ui.Span, ui.Line, or a table of them";
#[derive(Clone, FromLua)]
pub struct Line(pub(super) ratatui::text::Line<'static>);
impl TryFrom<Table<'_>> for Line {
type Error = mlua::Error;
fn try_from(tb: Table) -> Result<Self, Self::Error> {
let seq: Vec<_> = tb.sequence_values().filter_map(|v| v.ok()).collect();
let mut spans = Vec::with_capacity(seq.len());
for value in seq {
if let Value::UserData(ud) = value {
if let Ok(span) = ud.take::<Span>() {
spans.push(span.0);
} else if let Ok(line) = ud.take::<Line>() {
let style = line.0.style;
spans.extend(line.0.spans.into_iter().map(|mut span| {
span.style = style.patch(span.style);
span
}));
} else {
return Err("expected a table of Spans or Lines".into_lua_err());
}
}
}
Ok(Self(ratatui::text::Line::from(spans)))
}
}
impl TryFrom<mlua::String<'_>> for Line {
type Error = mlua::Error;
fn try_from(s: mlua::String) -> Result<Self, Self::Error> {
Ok(Self(ratatui::text::Line::from(s.to_string_lossy().into_owned())))
}
}
impl Line {
pub fn install(lua: &Lua, ui: &Table) -> mlua::Result<()> {
let new = lua.create_function(|_, (_, value): (Table, Value)| match value {
Value::Table(tb) => Line::try_from(tb),
Value::String(s) => Line::try_from(s),
_ => Err("expected a String, or a table of Spans and Lines".into_lua_err()),
})?;
let new = lua.create_function(|_, (_, value): (Table, Value)| Line::try_from(value))?;
let parse = lua.create_function(|_, code: mlua::String| {
let Some(line) = code.as_bytes().split_inclusive(|&b| b == b'\n').next() else {
@ -81,6 +46,53 @@ impl Line {
}
}
impl TryFrom<Value<'_>> for Line {
type Error = mlua::Error;
fn try_from(value: Value) -> Result<Self, Self::Error> {
Ok(Self(match value {
Value::Table(tb) => return Self::try_from(tb),
Value::String(s) => s.to_string_lossy().into_owned().into(),
Value::UserData(ud) => {
if let Ok(span) = ud.take::<Span>() {
span.0.into()
} else if let Ok(mut line) = ud.take::<Line>() {
line.0.spans.iter_mut().for_each(|s| s.style = line.0.style.patch(s.style));
line.0
} else {
Err(EXPECTED.into_lua_err())?
}
}
_ => Err(EXPECTED.into_lua_err())?,
}))
}
}
impl TryFrom<Table<'_>> for Line {
type Error = mlua::Error;
fn try_from(tb: Table) -> Result<Self, Self::Error> {
let mut spans = Vec::with_capacity(tb.raw_len());
for v in tb.sequence_values() {
match v? {
Value::String(s) => spans.push(s.to_string_lossy().into_owned().into()),
Value::UserData(ud) => {
if let Ok(span) = ud.take::<Span>() {
spans.push(span.0);
} else if let Ok(mut line) = ud.take::<Line>() {
line.0.spans.iter_mut().for_each(|s| s.style = line.0.style.patch(s.style));
spans.extend(line.0.spans);
} else {
return Err(EXPECTED.into_lua_err());
}
}
_ => Err(EXPECTED.into_lua_err())?,
}
}
Ok(Self(spans.into()))
}
}
impl UserData for Line {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
crate::impl_style_method!(methods, 0.style);

View File

@ -1,4 +1,4 @@
use mlua::{Lua, Table, UserData, Value};
use mlua::{ExternalError, Lua, Table, UserData, Value};
use ratatui::widgets::Widget;
use super::{Rect, Renderable, Text};
@ -15,10 +15,13 @@ impl List {
pub fn install(lua: &Lua, ui: &Table) -> mlua::Result<()> {
ui.raw_set(
"List",
lua.create_function(|_, values: Vec<Value>| {
let mut items = Vec::with_capacity(values.len());
for value in values {
items.push(ratatui::widgets::ListItem::new(Text::try_from(value)?));
lua.create_function(|_, tb: Table| {
let mut items = Vec::with_capacity(tb.raw_len());
for v in tb.sequence_values::<Value>() {
match v? {
Value::Table(_) => Err("Nested table not supported".into_lua_err())?,
v => items.push(Text::try_from(v)?),
}
}
Ok(Self { inner: ratatui::widgets::List::new(items), ..Default::default() })

View File

@ -1,17 +1,32 @@
use mlua::{FromLua, Lua, Table, UserData, UserDataMethods};
use mlua::{ExternalError, FromLua, Lua, Table, UserData, UserDataMethods, Value};
use unicode_width::UnicodeWidthChar;
const EXPECTED: &str = "expected a string or ui.Span";
#[derive(Clone, FromLua)]
pub struct Span(pub(super) ratatui::text::Span<'static>);
impl Span {
pub fn install(lua: &Lua, ui: &Table) -> mlua::Result<()> {
ui.raw_set(
"Span",
lua.create_function(|_, content: mlua::String| {
Ok(Self(ratatui::text::Span::raw(content.to_string_lossy().into_owned())))
})?,
)
ui.raw_set("Span", lua.create_function(|_, value: Value| Span::try_from(value))?)
}
}
impl TryFrom<Value<'_>> for Span {
type Error = mlua::Error;
fn try_from(value: Value<'_>) -> Result<Self, Self::Error> {
Ok(Self(match value {
Value::String(s) => s.to_string_lossy().into_owned().into(),
Value::UserData(ud) => {
if let Ok(span) = ud.take::<Span>() {
span.0
} else {
Err(EXPECTED.into_lua_err())?
}
}
_ => Err(EXPECTED.into_lua_err())?,
}))
}
}

View File

@ -14,6 +14,8 @@ pub const WRAP_NO: u8 = 0;
pub const WRAP: u8 = 1;
pub const WRAP_TRIM: u8 = 2;
const EXPECTED: &str = "expected a string, ui.Line, ui.Span or a table of them";
#[derive(Clone, Default, FromLua)]
pub struct Text {
pub area: Rect,
@ -54,29 +56,45 @@ impl TryFrom<Value<'_>> for Text {
type Error = mlua::Error;
fn try_from(value: Value) -> mlua::Result<Self> {
match value {
Value::String(s) => {
Ok(Self { inner: s.to_string_lossy().into_owned().into(), ..Default::default() })
}
let inner = match value {
Value::Table(tb) => return Self::try_from(tb),
Value::String(s) => s.to_string_lossy().into_owned().into(),
Value::UserData(ud) => {
let inner: ratatui::text::Text = if let Ok(line) = ud.take::<Line>() {
if let Ok(line) = ud.take::<Line>() {
line.0.into()
} else if let Ok(span) = ud.take::<Span>() {
span.0.into()
} else {
return Err("expected a String, Line or Span".into_lua_err());
};
Ok(Self { inner, ..Default::default() })
}
Value::Table(tb) => {
let mut lines = Vec::with_capacity(tb.raw_len());
for v in tb.sequence_values::<Value>() {
lines.extend(Self::try_from(v?)?.inner.lines);
Err(EXPECTED.into_lua_err())?
}
Ok(Self { inner: lines.into(), ..Default::default() })
}
_ => Err("expected a String, Line, Span or a Table of them".into_lua_err()),
_ => Err(EXPECTED.into_lua_err())?,
};
Ok(Self { inner, ..Default::default() })
}
}
impl TryFrom<Table<'_>> for Text {
type Error = mlua::Error;
fn try_from(tb: Table<'_>) -> Result<Self, Self::Error> {
let mut lines = Vec::with_capacity(tb.raw_len());
for v in tb.sequence_values() {
match v? {
Value::String(s) => lines.push(s.to_string_lossy().into_owned().into()),
Value::UserData(ud) => {
if let Ok(span) = ud.take::<Span>() {
lines.push(span.0.into());
} else if let Ok(line) = ud.take::<Line>() {
lines.push(line.0);
} else {
return Err(EXPECTED.into_lua_err());
}
}
_ => Err(EXPECTED.into_lua_err())?,
}
}
Ok(Self { inner: lines.into(), ..Default::default() })
}
}
@ -86,7 +104,7 @@ impl From<Text> for ratatui::text::Text<'static> {
impl From<Text> for ratatui::widgets::Paragraph<'static> {
fn from(value: Text) -> Self {
let align = value.inner.alignment.unwrap_or(ratatui::layout::Alignment::Left);
let align = value.inner.alignment.unwrap_or_default();
let mut p = ratatui::widgets::Paragraph::new(value.inner);
if value.wrap != WRAP_NO {