mirror of
https://github.com/sxyazi/yazi.git
synced 2024-12-24 17:23:21 +03:00
perf: introduce reflow for the rendering engine (#1863)
This commit is contained in:
parent
81b8c89ec1
commit
c6687237e1
@ -1,6 +1,6 @@
|
||||
use ratatui::layout::Rect;
|
||||
|
||||
#[derive(Clone, Copy, Default)]
|
||||
#[derive(Clone, Copy, Default, PartialEq, Eq)]
|
||||
pub struct Layout {
|
||||
pub current: Rect,
|
||||
pub preview: Rect,
|
||||
|
@ -4,6 +4,7 @@ yazi_macro::mod_flat!(
|
||||
notify
|
||||
plugin
|
||||
quit
|
||||
reflow
|
||||
render
|
||||
resize
|
||||
resume
|
||||
|
56
yazi-fm/src/app/commands/reflow.rs
Normal file
56
yazi-fm/src/app/commands/reflow.rs
Normal file
@ -0,0 +1,56 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use mlua::Value;
|
||||
use ratatui::layout::Position;
|
||||
use tracing::error;
|
||||
use yazi_config::LAYOUT;
|
||||
use yazi_macro::render;
|
||||
use yazi_shared::event::Cmd;
|
||||
|
||||
use crate::{Root, app::App, lives::Lives};
|
||||
|
||||
struct Opt;
|
||||
|
||||
impl From<Cmd> for Opt {
|
||||
fn from(_: Cmd) -> Self { Self }
|
||||
}
|
||||
|
||||
impl From<()> for Opt {
|
||||
fn from(_: ()) -> Self { Self }
|
||||
}
|
||||
|
||||
impl App {
|
||||
#[yazi_codegen::command]
|
||||
pub fn reflow(&mut self, _: Opt) {
|
||||
let Some(size) = self.term.as_ref().and_then(|t| t.size().ok()) else { return };
|
||||
let mut layout = *LAYOUT.load_full();
|
||||
|
||||
let result = Lives::scope(&self.cx, |_| {
|
||||
let comps = Root::reflow((Position::ORIGIN, size).into())?;
|
||||
|
||||
for v in comps.sequence_values::<Value>() {
|
||||
let Value::Table(t) = v? else {
|
||||
error!("`reflow()` must return a table of components");
|
||||
continue;
|
||||
};
|
||||
|
||||
let id: mlua::String = t.get("_id")?;
|
||||
match id.to_str()? {
|
||||
"current" => layout.current = *t.raw_get::<_, yazi_plugin::elements::Rect>("_area")?,
|
||||
"preview" => layout.preview = *t.raw_get::<_, yazi_plugin::elements::Rect>("_area")?,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
|
||||
if layout != *LAYOUT.load_full() {
|
||||
LAYOUT.store(Arc::new(layout));
|
||||
render!();
|
||||
}
|
||||
|
||||
if let Err(e) = result {
|
||||
error!("Failed to `reflow()` the `Root` component:\n{e}");
|
||||
}
|
||||
}
|
||||
}
|
@ -16,7 +16,7 @@ impl App {
|
||||
#[yazi_codegen::command]
|
||||
pub fn resize(&mut self, _: Opt) {
|
||||
self.cx.manager.active_mut().preview.reset();
|
||||
self.render();
|
||||
self.reflow(());
|
||||
|
||||
self.cx.manager.current_mut().sync_page(true);
|
||||
self.cx.manager.hover(None);
|
||||
|
@ -13,7 +13,7 @@ impl Progress {
|
||||
let mut patches = vec![];
|
||||
let mut f = || {
|
||||
let comp: Table = LUA.globals().raw_get("Progress")?;
|
||||
for widget in comp.call_method::<_, Vec<AnyUserData>>("partial_render", ())? {
|
||||
for widget in comp.call_method::<_, Vec<AnyUserData>>("partial_redraw", ())? {
|
||||
let Some(w) = cast_to_renderable(&widget) else { continue };
|
||||
|
||||
let area = w.area();
|
||||
|
@ -12,6 +12,12 @@ pub(super) struct Root<'a> {
|
||||
|
||||
impl<'a> Root<'a> {
|
||||
pub(super) fn new(cx: &'a Ctx) -> Self { Self { cx } }
|
||||
|
||||
pub(super) fn reflow<'lua>(area: Rect) -> mlua::Result<Table<'lua>> {
|
||||
let area = yazi_plugin::elements::Rect::from(area);
|
||||
let root = LUA.globals().raw_get::<_, Table>("Root")?.call_method::<_, Table>("new", area)?;
|
||||
root.call_method("reflow", ())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Widget for Root<'a> {
|
||||
@ -20,11 +26,11 @@ impl<'a> Widget for Root<'a> {
|
||||
let area = yazi_plugin::elements::Rect::from(area);
|
||||
let root = LUA.globals().raw_get::<_, Table>("Root")?.call_method::<_, Table>("new", area)?;
|
||||
|
||||
render_widgets(root.call_method("render", ())?, buf);
|
||||
render_widgets(root.call_method("redraw", ())?, buf);
|
||||
Ok::<_, mlua::Error>(())
|
||||
};
|
||||
if let Err(e) = f() {
|
||||
error!("Failed to render the `Root` component:\n{e}");
|
||||
error!("Failed to redraw the `Root` component:\n{e}");
|
||||
}
|
||||
|
||||
components::Preview::new(self.cx).render(area, buf);
|
||||
|
@ -23,7 +23,9 @@ function Current:empty()
|
||||
}
|
||||
end
|
||||
|
||||
function Current:render()
|
||||
function Current:reflow() return { self } end
|
||||
|
||||
function Current:redraw()
|
||||
local files = self._folder.window
|
||||
if #files == 0 then
|
||||
return self:empty()
|
||||
@ -31,8 +33,8 @@ function Current:render()
|
||||
|
||||
local entities, linemodes = {}, {}
|
||||
for _, f in ipairs(files) do
|
||||
entities[#entities + 1] = Entity:new(f):render()
|
||||
linemodes[#linemodes + 1] = Linemode:new(f):render()
|
||||
entities[#entities + 1] = Entity:new(f):redraw()
|
||||
linemodes[#linemodes + 1] = Linemode:new(f):redraw()
|
||||
end
|
||||
|
||||
return {
|
||||
|
@ -76,7 +76,7 @@ function Entity:symlink()
|
||||
return to and ui.Line(" -> " .. tostring(to)):italic() or ui.Line {}
|
||||
end
|
||||
|
||||
function Entity:render()
|
||||
function Entity:redraw()
|
||||
local lines = {}
|
||||
for _, c in ipairs(self._children) do
|
||||
lines[#lines + 1] = (type(c[1]) == "string" and self[c[1]] or c[1])(self)
|
||||
|
@ -95,11 +95,13 @@ function Header:tabs()
|
||||
return ui.Line(spans)
|
||||
end
|
||||
|
||||
function Header:render()
|
||||
local right = self:children_render(self.RIGHT)
|
||||
function Header:reflow() return { self } end
|
||||
|
||||
function Header:redraw()
|
||||
local right = self:children_redraw(self.RIGHT)
|
||||
self._right_width = right:width()
|
||||
|
||||
local left = self:children_render(self.LEFT)
|
||||
local left = self:children_redraw(self.LEFT)
|
||||
|
||||
return {
|
||||
ui.Text(left):area(self._area),
|
||||
@ -135,7 +137,7 @@ function Header:children_remove(id, side)
|
||||
end
|
||||
end
|
||||
|
||||
function Header:children_render(side)
|
||||
function Header:children_redraw(side)
|
||||
local lines = {}
|
||||
for _, c in ipairs(side == self.RIGHT and self._right or self._left) do
|
||||
lines[#lines + 1] = (type(c[1]) == "string" and self[c[1]] or c[1])(self)
|
||||
|
@ -62,7 +62,7 @@ function Linemode:owner()
|
||||
return ui.Line(string.format("%s:%s", user or "-", group or "-"))
|
||||
end
|
||||
|
||||
function Linemode:render()
|
||||
function Linemode:redraw()
|
||||
local lines = {}
|
||||
for _, c in ipairs(self._children) do
|
||||
lines[#lines + 1] = (type(c[1]) == "string" and self[c[1]] or c[1])(self)
|
||||
|
@ -9,7 +9,7 @@ function Marker:new(area, folder)
|
||||
}, { __index = self })
|
||||
end
|
||||
|
||||
function Marker:render()
|
||||
function Marker:redraw()
|
||||
if self._area.w * self._area.h == 0 then
|
||||
return {}
|
||||
elseif not self._folder or #self._folder.window == 0 then
|
||||
|
@ -10,18 +10,20 @@ function Parent:new(area, tab)
|
||||
}, { __index = self })
|
||||
end
|
||||
|
||||
function Parent:render()
|
||||
function Parent:reflow() return { self } end
|
||||
|
||||
function Parent:redraw()
|
||||
if not self._folder then
|
||||
return {}
|
||||
end
|
||||
|
||||
local items = {}
|
||||
local entities = {}
|
||||
for _, f in ipairs(self._folder.window) do
|
||||
items[#items + 1] = Entity:new(f):render()
|
||||
entities[#entities + 1] = Entity:new(f):redraw()
|
||||
end
|
||||
|
||||
return {
|
||||
ui.List(items):area(self._area),
|
||||
ui.List(entities):area(self._area),
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -10,7 +10,9 @@ function Preview:new(area, tab)
|
||||
}, { __index = self })
|
||||
end
|
||||
|
||||
function Preview:render() return {} end
|
||||
function Preview:reflow() return { self } end
|
||||
|
||||
function Preview:redraw() return {} end
|
||||
|
||||
-- Mouse events
|
||||
function Preview:click(event, up)
|
||||
|
@ -2,20 +2,20 @@ Progress = {
|
||||
_area = ui.Rect.default, -- TODO: remove this
|
||||
}
|
||||
|
||||
function Progress:render(area, offset)
|
||||
function Progress:redraw(area, offset)
|
||||
self._area = ui.Rect {
|
||||
x = math.max(0, area.w - offset - 21),
|
||||
y = area.y,
|
||||
w = ya.clamp(0, area.w - offset - 1, 20),
|
||||
h = math.min(1, area.h),
|
||||
}
|
||||
return self:partial_render()
|
||||
return self:partial_redraw()
|
||||
end
|
||||
|
||||
-- Progress bars usually need frequent updates to report the latest task progress.
|
||||
-- We use `partial_render()` to partially render it when there is progress change,
|
||||
-- which has almost no cost compared to a full render by `render()`.
|
||||
function Progress:partial_render()
|
||||
-- We use `partial_redraw()` to partially redraw it when there is progress change,
|
||||
-- which has almost no cost compared to a full redraw by `redraw()`.
|
||||
function Progress:partial_redraw()
|
||||
local progress = cx.tasks.progress
|
||||
if progress.total == 0 then
|
||||
return { ui.Text {} }
|
||||
|
@ -20,12 +20,14 @@ function Rail:build()
|
||||
}
|
||||
end
|
||||
|
||||
function Rail:render()
|
||||
local children = self._base or {}
|
||||
function Rail:reflow() return { self } end
|
||||
|
||||
function Rail:redraw()
|
||||
local elements = self._base or {}
|
||||
for _, child in ipairs(self._children) do
|
||||
children = ya.list_merge(children, ya.render_with(child))
|
||||
elements = ya.list_merge(elements, ya.redraw_with(child))
|
||||
end
|
||||
return children
|
||||
return elements
|
||||
end
|
||||
|
||||
-- Mouse events
|
||||
|
@ -29,12 +29,20 @@ function Root:build()
|
||||
}
|
||||
end
|
||||
|
||||
function Root:render()
|
||||
local children = self._base or {}
|
||||
function Root:reflow()
|
||||
local components = { self }
|
||||
for _, child in ipairs(self._children) do
|
||||
children = ya.list_merge(children, ya.render_with(child))
|
||||
components = ya.list_merge(components, child:reflow())
|
||||
end
|
||||
return children
|
||||
return components
|
||||
end
|
||||
|
||||
function Root:redraw()
|
||||
local elements = self._base or {}
|
||||
for _, child in ipairs(self._children) do
|
||||
elements = ya.list_merge(elements, ya.redraw_with(child))
|
||||
end
|
||||
return elements
|
||||
end
|
||||
|
||||
-- Mouse events
|
||||
|
@ -131,16 +131,18 @@ function Status:position()
|
||||
}
|
||||
end
|
||||
|
||||
function Status:render()
|
||||
local left = self:children_render(self.LEFT)
|
||||
function Status:reflow() return { self } end
|
||||
|
||||
local right = self:children_render(self.RIGHT)
|
||||
function Status:redraw()
|
||||
local left = self:children_redraw(self.LEFT)
|
||||
|
||||
local right = self:children_redraw(self.RIGHT)
|
||||
local right_width = right:width()
|
||||
|
||||
return {
|
||||
ui.Text(left):area(self._area),
|
||||
ui.Text(right):area(self._area):align(ui.Text.RIGHT),
|
||||
table.unpack(Progress:render(self._area, right_width)),
|
||||
table.unpack(Progress:redraw(self._area, right_width)),
|
||||
}
|
||||
end
|
||||
|
||||
@ -172,7 +174,7 @@ function Status:children_remove(id, side)
|
||||
end
|
||||
end
|
||||
|
||||
function Status:children_render(side)
|
||||
function Status:children_redraw(side)
|
||||
local lines = {}
|
||||
for _, c in ipairs(side == self.RIGHT and self._right or self._left) do
|
||||
lines[#lines + 1] = (type(c[1]) == "string" and self[c[1]] or c[1])(self)
|
||||
|
@ -29,12 +29,20 @@ function Tab:build()
|
||||
}
|
||||
end
|
||||
|
||||
function Tab:render()
|
||||
local children = self._base or {}
|
||||
function Tab:reflow()
|
||||
local components = { self }
|
||||
for _, child in ipairs(self._children) do
|
||||
children = ya.list_merge(children, ya.render_with(child))
|
||||
components = ya.list_merge(components, child:reflow())
|
||||
end
|
||||
return children
|
||||
return components
|
||||
end
|
||||
|
||||
function Tab:redraw()
|
||||
local elements = self._base or {}
|
||||
for _, child in ipairs(self._children) do
|
||||
elements = ya.list_merge(elements, ya.redraw_with(child))
|
||||
end
|
||||
return elements
|
||||
end
|
||||
|
||||
-- Mouse events
|
||||
|
@ -17,14 +17,14 @@ function M:peek()
|
||||
})
|
||||
end
|
||||
|
||||
local items = {}
|
||||
local entities = {}
|
||||
for _, f in ipairs(folder.window) do
|
||||
items[#items + 1] = Entity:new(f):render()
|
||||
entities[#entities + 1] = Entity:new(f):redraw()
|
||||
end
|
||||
|
||||
ya.preview_widgets(self, {
|
||||
ui.List(items):area(self.area),
|
||||
table.unpack(Marker:new(self.area, folder):render()),
|
||||
ui.List(entities):area(self.area),
|
||||
table.unpack(Marker:new(self.area, folder):redraw()),
|
||||
})
|
||||
end
|
||||
|
||||
|
@ -37,7 +37,7 @@ impl Utils {
|
||||
)?;
|
||||
|
||||
ya.raw_set(
|
||||
"render_with",
|
||||
"redraw_with",
|
||||
lua.create_function(|lua, c: Table| {
|
||||
let id: mlua::String = c.get("_id")?;
|
||||
let id = id.to_str()?;
|
||||
@ -58,9 +58,9 @@ impl Utils {
|
||||
_ => {}
|
||||
}
|
||||
|
||||
match c.call_method::<_, Table>("render", ()) {
|
||||
match c.call_method::<_, Table>("redraw", ()) {
|
||||
Err(e) => {
|
||||
error!("Failed to `render()` the `{id}` component:\n{e}");
|
||||
error!("Failed to `redraw()` the `{id}` component:\n{e}");
|
||||
lua.create_table()
|
||||
}
|
||||
ok => ok,
|
||||
|
Loading…
Reference in New Issue
Block a user