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