From 08e1f6aa2d109467c53e2e5add10276d9fd8f475 Mon Sep 17 00:00:00 2001 From: Arijit Basu Date: Sun, 12 Feb 2023 15:16:59 +0530 Subject: [PATCH] Allow custom title and ui config in custom layout. (#589) * Allow custom title and ui config in custom layout. Adds the following layouts: - Static - Dynamic Deprecates `CustomContent` (but won't be removed to maintain compatibility). Closes: https://github.com/sayanarijit/xplr/issues/563 * Delete init.lua * Update docs/en/src/layout.md * Update docs/en/src/layout.md * Rename - Paragraph => CustomParagraph - List => CustomList - Table => CustomTable Also update init.lua * Fix clippy errs * Fix doc links --- docs/en/src/borders.md | 6 + docs/en/src/layout.md | 246 ++++++++++++++++++----------------------- src/compat.rs | 224 +++++++++++++++++++++++++++++++++++++ src/init.lua | 24 ++-- src/lib.rs | 1 + src/ui.rs | 224 +++++++++++++------------------------ 6 files changed, 425 insertions(+), 300 deletions(-) create mode 100644 src/compat.rs diff --git a/docs/en/src/borders.md b/docs/en/src/borders.md index 713fac7..3ed204f 100644 --- a/docs/en/src/borders.md +++ b/docs/en/src/borders.md @@ -20,6 +20,10 @@ A border can be one of the following: - Double - Thick +### Border Style + +The [style][1] of the borders. + ## Example ```lua @@ -28,3 +32,5 @@ xplr.config.general.panel_ui.default.border_type = "Thick" xplr.config.general.panel_ui.default.border_style.fg = "Black" xplr.config.general.panel_ui.default.border_style.bg = "Gray" ``` + +[1]: style.md#style diff --git a/docs/en/src/layout.md b/docs/en/src/layout.md index e187e00..e2d09d1 100644 --- a/docs/en/src/layout.md +++ b/docs/en/src/layout.md @@ -32,56 +32,61 @@ A layout can be one of the following: - [Selection][11] - [HelpMenu][12] - [SortAndFilter][13] -- [CustomContent][25] +- [Static][25] +- [Dynamic][26] - [Horizontal][14] - [Vertical][16] +- CustomContent (deprecated, use `Static` or `Dynamic`) ### Nothing This layout contains a blank panel. -Example: "Nothing" +Type: "Nothing" ### Table -This layout contains the table displaying the files and directories in the -current directory. +This layout contains the table displaying the files and directories in the current +directory. ### InputAndLogs This layout contains the panel displaying the input prompt and logs. -Example: "InputAndLogs" +Type: "InputAndLogs" ### Selection This layout contains the panel displaying the selected paths. -Example: "Selection" +Type: "Selection" ### HelpMenu This layout contains the panel displaying the help menu for the current mode in real-time. -Example: "HelpMenu" +Type: "HelpMenu" ### SortAndFilter -This layout contains the panel displaying the pipeline of sorters and filters -applied of the list of paths being displayed. +This layout contains the panel displaying the pipeline of sorters and filters applied on +the list of paths being displayed. -Example: "SortAndFilter" +Type: "SortAndFilter" -### Custom Content +### Static -Custom content is a special layout to render something custom. -It contains the following information: +This is a custom layout to render static content. + +Type: { Static = [Custom Panel][27] } + +### Dynamic -- [title][33] -- [body][34] +This is a custom layout to render dynamic content using a function defined in +[xplr.fn][28] that takes [Content Renderer Argument][36] and returns [Custom Panel][27]. -Example: { CustomContent = { title = [title][33], body = [body][34] } +Type: { Dynamic = [Content Renderer][35] } ### Horizontal @@ -92,7 +97,7 @@ It contains the following information: - [config][15] - [splits][17] -Example: { Horizontal = { config = [config][15], splits = [splits][17] } +Type: { Horizontal = { config = [config][15], splits = [splits][17] } ### Vertical @@ -103,7 +108,7 @@ It contains the following information: - [config][15] - [splits][17] -Example: { Vertical = { config = [config][15], splits = [splits][17] } +Type: { Vertical = { config = [config][15], splits = [splits][17] } ## Layout Config @@ -166,187 +171,153 @@ Type: list of [Layout][3] The list of child layouts to fit into the parent layout. -## title - -Type: nullable string - -The title of the panel. - -## body +## Custom Panel -Type: [Content Body][26] +Custom panel can be one of the following: -The body of the panel. +- [CustomParagraph][29] +- [CustomList][30] +- [CustomTable][31] -## Content Body - -Content body can be one of the following: - -- [StaticParagraph][27] -- [DynamicParagraph][28] -- [StaticList][29] -- [DynamicList][30] -- [StaticTable][31] -- [DynamicTable][32] - -## Static Paragraph +### CustomParagraph A paragraph to render. It contains the following fields: -- **render** (string): The string to render. +- **ui** (nullable [Panel UI Config][32]): Optional UI config for the panel. +- **body** (string): The string to render. #### Example: Render a custom static paragraph ```lua xplr.config.layouts.builtin.default = { - CustomContent = { - title = "custom title", - body = { - StaticParagraph = { render = "custom body" }, + Static = { + CustomParagraph = { + ui = { title = { format = " custom title " } }, + body = "custom body", }, }, } ``` -## Dynamic Paragraph - -A [Lua function][35] to render a custom paragraph. -It contains the following fields: - -- **render** (string): The [lua function][35] that returns the paragraph to - render. - #### Example: Render a custom dynamic paragraph ```lua -xplr.config.layouts.builtin.default = { - CustomContent = { - title = "custom title", - body = { DynamicParagraph = { render = "custom.render_layout" } }, - }, -} +xplr.config.layouts.builtin.default = { Dynamic = "custom.render_layout" } xplr.fn.custom.render_layout = function(ctx) - return ctx.app.pwd + return { + CustomParagraph = { + ui = { title = { format = ctx.app.pwd } }, + body = xplr.util.to_yaml(ctx.app.focused_node), + }, + } end ``` -## Static List +### CustomList A list to render. It contains the following fields: -- **render** (list of string): The list to render. +- **ui** (nullable [Panel UI Config][32]): Optional UI config for the panel. +- **body** (list of string): The list of strings to display. #### Example: Render a custom static list ```lua xplr.config.layouts.builtin.default = { - CustomContent = { - title = "custom title", - body = { - StaticList = { render = { "1", "2", "3" } }, + Static = { + CustomList = { + ui = { title = { format = " custom title " } }, + body = { "1", "2", "3" }, }, }, } ``` -## Dynamic List - -A [Lua function][35] to render a custom list. -It contains the following fields: - -- **render** (string): The [lua function][35] that returns the list to render. - #### Example: Render a custom dynamic list ```lua -xplr.config.layouts.builtin.default = { - CustomContent = { - title = "custom title", - body = { DynamicList = { render = "custom.render_layout" } }, - }, -} +xplr.config.layouts.builtin.default = { Dynamic = "custom.render_layout" } xplr.fn.custom.render_layout = function(ctx) return { - ctx.app.pwd, - ctx.app.version, - tostring(ctx.app.pid), + CustomList = { + ui = { title = { format = ctx.app.pwd } }, + body = { + (ctx.app.focused_node or {}).relative_path or "", + ctx.app.version, + tostring(ctx.app.pid), + }, + }, } end ``` -## Static Table +## CustomTable -A table to render. It contains the following fields: +A custom table to render. It contains the following fields: +- **ui** (nullable [Panel UI Config][32]): Optional UI config for the panel. - **widths** (list of [Constraint][22]): Width of the columns. - **col_spacing** (nullable int): Spacing between columns. Defaults to 1. -- **render** (list of list of string): The rows and columns to render. +- **body** (list of list of string): The rows and columns to render. #### Example: Render a custom static table ```lua xplr.config.layouts.builtin.default = { - CustomContent = { - title = "custom title", - body = { - StaticTable = { - widths = { - { Percentage = 50 }, - { Percentage = 50 }, - }, - col_spacing = 1, - render = { - { "a", "b" }, - { "c", "d" }, - }, + Static = { + CustomTable = { + ui = { title = { format = " custom title " } }, + widths = { + { Percentage = 50 }, + { Percentage = 50 }, + }, + body = { + { "a", "b" }, + { "c", "d" }, }, }, }, } ``` -## Dynamic Table - -A [Lua function][35] to render a custom table. -It contains the following fields: - -- **widths** (list of [Constraint][22]): Width of the columns. -- **col_spacing** (nullable int): Spacing between columns. Defaults to 1. -- **render** (string): The [lua function][35] that returns the table to render. - #### Example: Render a custom dynamic table ```lua -xplr.config.layouts.builtin.default = { - CustomContent = { - title = "custom title", - body = { - DynamicTable = { - widths = { - { Percentage = 50 }, - { Percentage = 50 }, - }, - col_spacing = 1, - render = "custom.render_layout", - }, - }, - }, -} +xplr.config.layouts.builtin.default = {Dynamic = "custom.render_layout" } xplr.fn.custom.render_layout = function(ctx) return { - { "", "" }, - { "Layout height", tostring(ctx.layout_size.height) }, - { "Layout width", tostring(ctx.layout_size.width) }, - { "", "" }, - { "Screen height", tostring(ctx.screen_size.height) }, - { "Screen width", tostring(ctx.screen_size.width) }, + CustomTable = { + ui = { title = { format = ctx.app.pwd } }, + widths = { + { Percentage = 50 }, + { Percentage = 50 }, + }, + body = { + { "", "" }, + { "Layout height", tostring(ctx.layout_size.height) }, + { "Layout width", tostring(ctx.layout_size.width) }, + { "", "" }, + { "Screen height", tostring(ctx.screen_size.height) }, + { "Screen width", tostring(ctx.screen_size.width) }, + }, + }, } end ``` +## Panel UI Config + +It contains the following optional fields: + +- **title** ({ format = "string", style = [Style][33] }): the title of the panel. +- **style** ([Style][33]): The style of the panel body. +- **borders** (nullable list of [Border][34]): The shape of the borders. +- **border_type** ([Border Type][54]): The type of the borders. +- **border_style** ([Style][33]): The style of the borders. + ## Content Renderer It is a Lua function that receives [a special argument][36] as input and @@ -421,16 +392,16 @@ Hence, only the following fields are avilable. [22]: #constraint [23]: https://s6.gifyu.com/images/layout.png [24]: https://gifyu.com/image/1X38 -[25]: #custom-content -[26]: #content-body -[27]: #static-paragraph -[28]: #dynamic-paragraph -[29]: #static-list -[30]: #dynamic-list -[31]: #static-table -[32]: #dynamic-table -[33]: #title -[34]: #body +[25]: #static +[26]: #dynamic +[27]: #custom-panel +[28]: configuration.md#function +[29]: #customparagraph +[30]: #customlist +[31]: #customtable +[32]: #panel-ui-config +[33]: style.md#style +[34]: borders.md#border [35]: #content-renderer [36]: #content-renderer-argument [37]: #size @@ -450,3 +421,4 @@ Hence, only the following fields are avilable. [51]: layouts.md [52]: lua-function-calls.md#vroot [53]: lua-function-calls.md#initial_pwd +[54]: borders.md#border-type diff --git a/src/compat.rs b/src/compat.rs new file mode 100644 index 0000000..7a05e3a --- /dev/null +++ b/src/compat.rs @@ -0,0 +1,224 @@ +// Things of the past, mostly bad decisions, which cannot erased, stays in this +// haunted module. + +use crate::app; +use crate::lua; +use crate::ui::block; +use crate::ui::string_to_text; +use crate::ui::Constraint; +use crate::ui::ContentRendererArg; +use mlua::Lua; +use serde::{Deserialize, Serialize}; +use tui::backend::Backend; +use tui::layout::Constraint as TuiConstraint; +use tui::layout::Rect as TuiRect; +use tui::widgets::Cell; +use tui::widgets::List; +use tui::widgets::ListItem; +use tui::widgets::Paragraph; +use tui::widgets::Row; +use tui::widgets::Table; +use tui::Frame; + +/// A cursed enum from crate::ui. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(deny_unknown_fields)] +pub enum ContentBody { + /// A paragraph to render + StaticParagraph { render: String }, + + /// A Lua function that returns a paragraph to render + DynamicParagraph { render: String }, + + /// List to render + StaticList { render: Vec }, + + /// A Lua function that returns lines to render + DynamicList { render: String }, + + /// A table to render + StaticTable { + widths: Vec, + col_spacing: Option, + render: Vec>, + }, + + /// A Lua function that returns a table to render + DynamicTable { + widths: Vec, + col_spacing: Option, + render: String, + }, +} + +/// A cursed struct from crate::ui. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(deny_unknown_fields)] +pub struct CustomContent { + pub title: Option, + pub body: ContentBody, +} + +/// A cursed function from crate::ui. +pub fn draw_custom_content( + f: &mut Frame, + screen_size: TuiRect, + layout_size: TuiRect, + app: &app::App, + content: CustomContent, + lua: &Lua, +) { + let config = app.config.general.panel_ui.default.clone(); + let title = content.title; + let body = content.body; + + match body { + ContentBody::StaticParagraph { render } => { + let render = string_to_text(render); + let content = Paragraph::new(render).block(block( + config, + title.map(|t| format!(" {t} ")).unwrap_or_default(), + )); + f.render_widget(content, layout_size); + } + + ContentBody::DynamicParagraph { render } => { + let ctx = ContentRendererArg { + app: app.to_lua_ctx_light(), + layout_size: layout_size.into(), + screen_size: screen_size.into(), + }; + + let render = lua::serialize(lua, &ctx) + .map(|arg| { + lua::call(lua, &render, arg).unwrap_or_else(|e| format!("{e:?}")) + }) + .unwrap_or_else(|e| e.to_string()); + + let render = string_to_text(render); + + let content = Paragraph::new(render).block(block( + config, + title.map(|t| format!(" {t} ")).unwrap_or_default(), + )); + f.render_widget(content, layout_size); + } + + ContentBody::StaticList { render } => { + let items = render + .into_iter() + .map(string_to_text) + .map(ListItem::new) + .collect::>(); + + let content = List::new(items).block(block( + config, + title.map(|t| format!(" {t} ")).unwrap_or_default(), + )); + f.render_widget(content, layout_size); + } + + ContentBody::DynamicList { render } => { + let ctx = ContentRendererArg { + app: app.to_lua_ctx_light(), + layout_size: layout_size.into(), + screen_size: screen_size.into(), + }; + + let items = lua::serialize(lua, &ctx) + .map(|arg| { + lua::call(lua, &render, arg) + .unwrap_or_else(|e| vec![format!("{e:?}")]) + }) + .unwrap_or_else(|e| vec![e.to_string()]) + .into_iter() + .map(string_to_text) + .map(ListItem::new) + .collect::>(); + + let content = List::new(items).block(block( + config, + title.map(|t| format!(" {t} ")).unwrap_or_default(), + )); + f.render_widget(content, layout_size); + } + + ContentBody::StaticTable { + widths, + col_spacing, + render, + } => { + let rows = render + .into_iter() + .map(|cols| { + Row::new( + cols.into_iter() + .map(string_to_text) + .map(Cell::from) + .collect::>(), + ) + }) + .collect::>(); + + let widths = widths + .into_iter() + .map(|w| w.to_tui(screen_size, layout_size)) + .collect::>(); + + let content = Table::new(rows) + .widths(&widths) + .column_spacing(col_spacing.unwrap_or(1)) + .block(block( + config, + title.map(|t| format!(" {t} ")).unwrap_or_default(), + )); + + f.render_widget(content, layout_size); + } + + ContentBody::DynamicTable { + widths, + col_spacing, + render, + } => { + let ctx = ContentRendererArg { + app: app.to_lua_ctx_light(), + layout_size: layout_size.into(), + screen_size: screen_size.into(), + }; + + let rows = lua::serialize(lua, &ctx) + .map(|arg| { + lua::call(lua, &render, arg) + .unwrap_or_else(|e| vec![vec![format!("{e:?}")]]) + }) + .unwrap_or_else(|e| vec![vec![e.to_string()]]) + .into_iter() + .map(|cols| { + Row::new( + cols.into_iter() + .map(string_to_text) + .map(Cell::from) + .collect::>(), + ) + }) + .collect::>(); + + let widths = widths + .into_iter() + .map(|w| w.to_tui(screen_size, layout_size)) + .collect::>(); + + let mut content = Table::new(rows).widths(&widths).block(block( + config, + title.map(|t| format!(" {t} ")).unwrap_or_default(), + )); + + if let Some(col_spacing) = col_spacing { + content = content.column_spacing(col_spacing); + }; + + f.render_widget(content, layout_size); + } + } +} diff --git a/src/init.lua b/src/init.lua index 217d0ae..3db59e5 100644 --- a/src/init.lua +++ b/src/init.lua @@ -1317,11 +1317,10 @@ xplr.config.modes.builtin.debug_error = { }, splits = { { - CustomContent = { - title = "debug error", - body = { - StaticParagraph = { - render = [[ + Static = { + CustomParagraph = { + ui = { title = { format = "debug error" } }, + body = [[ Some errors occurred during startup. If you think this is a bug, please report it at: @@ -1333,8 +1332,7 @@ xplr.config.modes.builtin.debug_error = { To disable this mode, set `xplr.config.general.disable_debug_error_mode` to `true` in your config file. - ]], - }, + ]], }, }, }, @@ -1373,11 +1371,10 @@ xplr.config.modes.builtin.debug_error = { xplr.config.modes.builtin.recover = { name = "recover", layout = { - CustomContent = { - title = " recover ", - body = { - StaticParagraph = { - render = [[ + Static = { + CustomParagraph = { + ui = { title = { format = "recover" } }, + body = [[ You pressed an invalid key and went into "recover" mode. This mode saves you from performing unwanted actions. @@ -1386,8 +1383,7 @@ xplr.config.modes.builtin.recover = { To disable this mode, set `xplr.config.general.enable_recover_mode` to `false` in your config file. - ]], - }, + ]], }, }, }, diff --git a/src/lib.rs b/src/lib.rs index ce1a5a0..eecb3c5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,7 @@ pub mod app; pub mod cli; +pub mod compat; pub mod config; pub mod directory_buffer; pub mod event_reader; diff --git a/src/ui.rs b/src/ui.rs index f22fb0d..75dade1 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -1,5 +1,6 @@ use crate::app::{HelpMenuLine, NodeFilterApplicable, NodeSorterApplicable}; use crate::app::{Node, ResolvedNode}; +use crate::compat::{draw_custom_content, CustomContent}; use crate::config::PanelUiConfig; use crate::lua; use crate::permissions::Permissions; @@ -38,7 +39,7 @@ fn read_only_indicator(app: &app::App) -> &str { } } -fn string_to_text<'a>(string: String) -> Text<'a> { +pub fn string_to_text<'a>(string: String) -> Text<'a> { if *NO_COLOR { Text::raw(string) } else { @@ -77,31 +78,23 @@ impl LayoutOptions { #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(deny_unknown_fields)] -pub enum ContentBody { - /// A paragraph to render - StaticParagraph { render: String }, - - /// A Lua function that returns a paragraph to render - DynamicParagraph { render: String }, - - /// List to render - StaticList { render: Vec }, - - /// A Lua function that returns lines to render - DynamicList { render: String }, - - /// A table to render - StaticTable { - widths: Vec, - col_spacing: Option, - render: Vec>, +pub enum CustomPanel { + CustomParagraph { + #[serde(default)] + ui: PanelUiConfig, + body: String, }, - - /// A Lua function that returns a table to render - DynamicTable { + CustomList { + #[serde(default)] + ui: PanelUiConfig, + body: Vec, + }, + CustomTable { + #[serde(default)] + ui: PanelUiConfig, widths: Vec, col_spacing: Option, - render: String, + body: Vec>, }, } @@ -114,10 +107,8 @@ pub enum Layout { Selection, HelpMenu, SortAndFilter, - CustomContent { - title: Option, - body: ContentBody, - }, + Static(Box), + Dynamic(String), Horizontal { config: LayoutOptions, splits: Vec, @@ -126,6 +117,9 @@ pub enum Layout { config: LayoutOptions, splits: Vec, }, + + /// For compatibility only. A better choice is Static or Dymanic layout. + CustomContent(Box), } impl Default for Layout { @@ -640,7 +634,7 @@ impl NodeUiMetadata { } } -fn block<'a>(config: PanelUiConfig, default_title: String) -> Block<'a> { +pub fn block<'a>(config: PanelUiConfig, default_title: String) -> Block<'a> { Block::default() .borders(TuiBorders::from_bits_truncate( config @@ -770,7 +764,7 @@ fn draw_table( .filter_map(|c| { c.format.as_ref().map(|f| { let out = lua::call(lua, f, v.clone()) - .unwrap_or_else(|e| e.to_string()); + .unwrap_or_else(|e| format!("{e:?}")); string_to_text(out) }) }) @@ -876,8 +870,8 @@ fn draw_selection( .as_ref() .map(|f| { lua::serialize::(lua, n) - .map(|n| lua::call(lua, f, n).unwrap_or_else(|e| e.to_string())) - .unwrap_or_else(|e| e.to_string()) + .and_then(|n| lua::call(lua, f, n)) + .unwrap_or_else(|e| format!("{e:?}")) }) .unwrap_or_else(|| n.absolute_path.clone()); string_to_text(out) @@ -1211,94 +1205,68 @@ pub fn draw_nothing( f.render_widget(nothing, layout_size); } -pub fn draw_custom_content( +pub fn draw_dynamic( f: &mut Frame, screen_size: TuiRect, layout_size: TuiRect, app: &app::App, - title: Option, - body: ContentBody, + func: &str, lua: &Lua, ) { - let config = app.config.general.panel_ui.default.clone(); - - match body { - ContentBody::StaticParagraph { render } => { - let render = string_to_text(render); - let content = Paragraph::new(render).block(block( - config, - title.map(|t| format!(" {t} ")).unwrap_or_default(), - )); - f.render_widget(content, layout_size); - } - - ContentBody::DynamicParagraph { render } => { - let ctx = ContentRendererArg { - app: app.to_lua_ctx_light(), - layout_size: layout_size.into(), - screen_size: screen_size.into(), - }; - - let render = lua::serialize(lua, &ctx) - .map(|arg| { - lua::call(lua, &render, arg).unwrap_or_else(|e| format!("{e:?}")) - }) - .unwrap_or_else(|e| e.to_string()); - - let render = string_to_text(render); + let ctx = ContentRendererArg { + app: app.to_lua_ctx_light(), + layout_size: layout_size.into(), + screen_size: screen_size.into(), + }; - let content = Paragraph::new(render).block(block( - config, - title.map(|t| format!(" {t} ")).unwrap_or_default(), - )); - f.render_widget(content, layout_size); - } + let panel: CustomPanel = lua::serialize(lua, &ctx) + .and_then(|arg| lua::call(lua, func, arg)) + .unwrap_or_else(|e| CustomPanel::CustomParagraph { + ui: app.config.general.panel_ui.default.clone(), + body: format!("{e:?}"), + }); - ContentBody::StaticList { render } => { - let items = render - .into_iter() - .map(string_to_text) - .map(ListItem::new) - .collect::>(); + draw_static(f, screen_size, layout_size, app, panel, lua); +} - let content = List::new(items).block(block( - config, - title.map(|t| format!(" {t} ")).unwrap_or_default(), - )); +pub fn draw_static( + f: &mut Frame, + screen_size: TuiRect, + layout_size: TuiRect, + app: &app::App, + panel: CustomPanel, + _lua: &Lua, +) { + let defaultui = app.config.general.panel_ui.default.clone(); + match panel { + CustomPanel::CustomParagraph { ui, body } => { + let config = defaultui.extend(&ui); + let body = string_to_text(body); + let content = Paragraph::new(body).block(block(config, "".into())); f.render_widget(content, layout_size); } - ContentBody::DynamicList { render } => { - let ctx = ContentRendererArg { - app: app.to_lua_ctx_light(), - layout_size: layout_size.into(), - screen_size: screen_size.into(), - }; + CustomPanel::CustomList { ui, body } => { + let config = defaultui.extend(&ui); - let items = lua::serialize(lua, &ctx) - .map(|arg| { - lua::call(lua, &render, arg) - .unwrap_or_else(|e| vec![format!("{e:?}")]) - }) - .unwrap_or_else(|e| vec![e.to_string()]) + let items = body .into_iter() .map(string_to_text) .map(ListItem::new) .collect::>(); - let content = List::new(items).block(block( - config, - title.map(|t| format!(" {t} ")).unwrap_or_default(), - )); + let content = List::new(items).block(block(config, "".into())); f.render_widget(content, layout_size); } - ContentBody::StaticTable { + CustomPanel::CustomTable { + ui, widths, col_spacing, - render, + body, } => { - let rows = render + let config = defaultui.extend(&ui); + let rows = body .into_iter() .map(|cols| { Row::new( @@ -1318,55 +1286,7 @@ pub fn draw_custom_content( let content = Table::new(rows) .widths(&widths) .column_spacing(col_spacing.unwrap_or(1)) - .block(block( - config, - title.map(|t| format!(" {t} ")).unwrap_or_default(), - )); - - f.render_widget(content, layout_size); - } - - ContentBody::DynamicTable { - widths, - col_spacing, - render, - } => { - let ctx = ContentRendererArg { - app: app.to_lua_ctx_light(), - layout_size: layout_size.into(), - screen_size: screen_size.into(), - }; - - let rows = lua::serialize(lua, &ctx) - .map(|arg| { - lua::call(lua, &render, arg) - .unwrap_or_else(|e| vec![vec![format!("{e:?}")]]) - }) - .unwrap_or_else(|e| vec![vec![e.to_string()]]) - .into_iter() - .map(|cols| { - Row::new( - cols.into_iter() - .map(string_to_text) - .map(Cell::from) - .collect::>(), - ) - }) - .collect::>(); - - let widths = widths - .into_iter() - .map(|w| w.to_tui(screen_size, layout_size)) - .collect::>(); - - let mut content = Table::new(rows).widths(&widths).block(block( - config, - title.map(|t| format!(" {t} ")).unwrap_or_default(), - )); - - if let Some(col_spacing) = col_spacing { - content = content.column_spacing(col_spacing); - }; + .block(block(config, "".into())); f.render_widget(content, layout_size); } @@ -1394,9 +1314,9 @@ impl From for Rect { #[derive(Debug, Clone, Serialize)] pub struct ContentRendererArg { - app: app::LuaContextLight, - screen_size: Rect, - layout_size: Rect, + pub app: app::LuaContextLight, + pub screen_size: Rect, + pub layout_size: Rect, } pub fn draw_layout( @@ -1422,8 +1342,14 @@ pub fn draw_layout( draw_logs(f, screen_size, layout_size, app, lua); }; } - Layout::CustomContent { title, body } => { - draw_custom_content(f, screen_size, layout_size, app, title, body, lua) + Layout::Static(panel) => { + draw_static(f, screen_size, layout_size, app, *panel, lua) + } + Layout::Dynamic(ref func) => { + draw_dynamic(f, screen_size, layout_size, app, func, lua) + } + Layout::CustomContent(content) => { + draw_custom_content(f, screen_size, layout_size, app, *content, lua) } Layout::Horizontal { config, splits } => { let chunks = TuiLayout::default()